From c2eeeb41aa8f4236879f1208a9af278e3ac38138 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Fri, 5 May 2023 11:40:34 -0700 Subject: [PATCH 1/9] Offset isn't optional - it'll always come back for a ReceivedEventData since it's one of the mandatory fields. --- sdk/messaging/azeventhubs/event_data.go | 4 ++-- sdk/messaging/azeventhubs/event_data_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/messaging/azeventhubs/event_data.go b/sdk/messaging/azeventhubs/event_data.go index 9300aa51f7c6..88ff8f6cfa22 100644 --- a/sdk/messaging/azeventhubs/event_data.go +++ b/sdk/messaging/azeventhubs/event_data.go @@ -52,7 +52,7 @@ type ReceivedEventData struct { PartitionKey *string // Offset is the offset of the event. - Offset *int64 + Offset int64 // RawAMQPMessage is the AMQP message, as received by the client. This can be useful to get access // to properties that are not exposed by ReceivedEventData such as payloads encoded into the @@ -177,7 +177,7 @@ func updateFromAMQPAnnotations(src *amqp.Message, dest *ReceivedEventData) error case offsetNumberAnnotation: if offsetStr, ok := v.(string); ok { if offset, err := strconv.ParseInt(offsetStr, 10, 64); err == nil { - dest.Offset = &offset + dest.Offset = offset continue } } diff --git a/sdk/messaging/azeventhubs/event_data_test.go b/sdk/messaging/azeventhubs/event_data_test.go index bbc25e4cdcdd..b4ee83974841 100644 --- a/sdk/messaging/azeventhubs/event_data_test.go +++ b/sdk/messaging/azeventhubs/event_data_test.go @@ -99,7 +99,7 @@ func TestEventData_newReceivedEventData(t *testing.T) { SystemProperties: map[string]any{ "hello": "world", }, - Offset: to.Ptr[int64](102), + Offset: int64(102), PartitionKey: to.Ptr("partition key"), RawAMQPMessage: &AMQPAnnotatedMessage{ Properties: &AMQPAnnotatedMessageProperties{ From dcac7ac1519c7f5035908de1303a1e756bb695da Mon Sep 17 00:00:00 2001 From: Richard Park Date: Fri, 5 May 2023 13:34:50 -0700 Subject: [PATCH 2/9] - Make CheckpointStore a struct with func()'s instead of an interface. - Offset is not an optional field, shouldn't have been a pointer - Adding more logging into the checkpoint store. --- sdk/messaging/azeventhubs/checkpoint_store.go | 16 +++---- .../azeventhubs/checkpoints/blob_store.go | 20 +++++++-- .../checkpoints/blob_store_test.go | 4 +- .../consumer_client_internal_test.go | 2 +- sdk/messaging/azeventhubs/event_data_test.go | 2 +- .../example_checkpoint_migration_test.go | 2 +- ...example_consuming_with_checkpoints_test.go | 4 +- .../inmemory_checkpoint_store_test.go | 17 +++++-- .../stress/tests/processor_stress_tester.go | 4 +- .../internal/eh/stress/tests/shared.go | 2 +- sdk/messaging/azeventhubs/processor.go | 6 +-- .../azeventhubs/processor_load_balancer.go | 24 ++++++++-- .../processor_load_balancers_test.go | 44 +++++++++---------- .../azeventhubs/processor_partition_client.go | 19 +++++--- sdk/messaging/azeventhubs/processor_test.go | 14 +++--- .../azeventhubs/processor_unit_test.go | 18 ++++---- 16 files changed, 122 insertions(+), 76 deletions(-) diff --git a/sdk/messaging/azeventhubs/checkpoint_store.go b/sdk/messaging/azeventhubs/checkpoint_store.go index 3d321839251b..d72f2242858b 100644 --- a/sdk/messaging/azeventhubs/checkpoint_store.go +++ b/sdk/messaging/azeventhubs/checkpoint_store.go @@ -11,19 +11,19 @@ import ( ) // CheckpointStore is used by multiple consumers to coordinate progress and ownership for partitions. -type CheckpointStore interface { +type CheckpointStore struct { // ClaimOwnership attempts to claim ownership of the partitions in partitionOwnership and returns // the actual partitions that were claimed. - ClaimOwnership(ctx context.Context, partitionOwnership []Ownership, options *ClaimOwnershipOptions) ([]Ownership, error) + ClaimOwnership func(ctx context.Context, partitionOwnership []Ownership, options *ClaimOwnershipOptions) ([]Ownership, error) // ListCheckpoints lists all the available checkpoints. - ListCheckpoints(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *ListCheckpointsOptions) ([]Checkpoint, error) + ListCheckpoints func(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *ListCheckpointsOptions) ([]Checkpoint, error) // ListOwnership lists all ownerships. - ListOwnership(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *ListOwnershipOptions) ([]Ownership, error) + ListOwnership func(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *ListOwnershipOptions) ([]Ownership, error) - // UpdateCheckpoint updates a specific checkpoint with a sequence and offset. - UpdateCheckpoint(ctx context.Context, checkpoint Checkpoint, options *UpdateCheckpointOptions) error + // SetCheckpoint creates or updates a specific checkpoint with a sequence and offset. + SetCheckpoint func(ctx context.Context, checkpoint Checkpoint, options *SetCheckpointOptions) error } // Ownership tracks which consumer owns a particular partition. @@ -59,8 +59,8 @@ type ListOwnershipOptions struct { // For future expansion } -// UpdateCheckpointOptions contains optional parameters for the UpdateCheckpoint function -type UpdateCheckpointOptions struct { +// SetCheckpointOptions contains optional parameters for the UpdateCheckpoint function +type SetCheckpointOptions struct { // For future expansion } diff --git a/sdk/messaging/azeventhubs/checkpoints/blob_store.go b/sdk/messaging/azeventhubs/checkpoints/blob_store.go index e8a134643603..e99bc844837e 100644 --- a/sdk/messaging/azeventhubs/checkpoints/blob_store.go +++ b/sdk/messaging/azeventhubs/checkpoints/blob_store.go @@ -15,6 +15,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror" @@ -36,9 +37,16 @@ type BlobStoreOptions struct { // NewBlobStore creates a checkpoint store that stores ownership and checkpoints in // Azure Blob storage. // NOTE: the container must exist before the checkpoint store can be used. -func NewBlobStore(containerClient *container.Client, options *BlobStoreOptions) (*BlobStore, error) { - return &BlobStore{ +func NewBlobStore(containerClient *container.Client, options *BlobStoreOptions) (*azeventhubs.CheckpointStore, error) { + bs := &BlobStore{ cc: containerClient, + } + + return &azeventhubs.CheckpointStore{ + ClaimOwnership: bs.ClaimOwnership, + ListCheckpoints: bs.ListCheckpoints, + ListOwnership: bs.ListOwnership, + SetCheckpoint: bs.SetCheckpoint, }, nil } @@ -63,6 +71,8 @@ func (b *BlobStore) ClaimOwnership(ctx context.Context, partitionOwnership []aze if bloberror.HasCode(err, bloberror.ConditionNotMet, // updated before we could update it bloberror.BlobAlreadyExists) { // created before we could create it + + log.Writef(azeventhubs.EventConsumer, "[%s] skipping %s because: %s", po.OwnerID, po.PartitionID, err) continue } @@ -180,10 +190,10 @@ func (b *BlobStore) ListOwnership(ctx context.Context, fullyQualifiedNamespace s return ownerships, nil } -// UpdateCheckpoint updates a specific checkpoint with a sequence and offset. +// SetCheckpoint updates a specific checkpoint with a sequence and offset. // // NOTE: This function doesn't attempt to prevent simultaneous checkpoint updates - ownership is assumed. -func (b *BlobStore) UpdateCheckpoint(ctx context.Context, checkpoint azeventhubs.Checkpoint, options *azeventhubs.UpdateCheckpointOptions) error { +func (b *BlobStore) SetCheckpoint(ctx context.Context, checkpoint azeventhubs.Checkpoint, options *azeventhubs.SetCheckpointOptions) error { blobName, err := nameForCheckpointBlob(checkpoint) if err != nil { @@ -199,6 +209,7 @@ func (b *BlobStore) setOwnershipMetadata(ctx context.Context, blobName string, o blobClient := b.cc.NewBlockBlobClient(blobName) if ownership.ETag != nil { + log.Writef(azeventhubs.EventConsumer, "[%s] claiming ownership for %s with etag %s", ownership.OwnerID, ownership.PartitionID, string(*ownership.ETag)) setMetadataResp, err := blobClient.SetMetadata(ctx, blobMetadata, &blob.SetMetadataOptions{ AccessConditions: &blob.AccessConditions{ ModifiedAccessConditions: &blob.ModifiedAccessConditions{ @@ -214,6 +225,7 @@ func (b *BlobStore) setOwnershipMetadata(ctx context.Context, blobName string, o return setMetadataResp.LastModified, *setMetadataResp.ETag, nil } + log.Writef(azeventhubs.EventConsumer, "[%s] claiming ownership for %s with NO etags", ownership.PartitionID, ownership.OwnerID) uploadResp, err := blobClient.Upload(ctx, streaming.NopCloser(bytes.NewReader([]byte{})), &blockblob.UploadOptions{ Metadata: blobMetadata, AccessConditions: &blob.AccessConditions{ diff --git a/sdk/messaging/azeventhubs/checkpoints/blob_store_test.go b/sdk/messaging/azeventhubs/checkpoints/blob_store_test.go index 4605fdcb33ba..49923e1f46a5 100644 --- a/sdk/messaging/azeventhubs/checkpoints/blob_store_test.go +++ b/sdk/messaging/azeventhubs/checkpoints/blob_store_test.go @@ -33,7 +33,7 @@ func TestBlobStore_Checkpoints(t *testing.T) { require.NoError(t, err) require.Empty(t, checkpoints) - err = store.UpdateCheckpoint(context.Background(), azeventhubs.Checkpoint{ + err = store.SetCheckpoint(context.Background(), azeventhubs.Checkpoint{ ConsumerGroup: "$Default", EventHubName: "event-hub-name", FullyQualifiedNamespace: "ns.servicebus.windows.net", @@ -57,7 +57,7 @@ func TestBlobStore_Checkpoints(t *testing.T) { // There's a code path to allow updating the blob after it's been created but without an etag // in which case it just updates it. - err = store.UpdateCheckpoint(context.Background(), azeventhubs.Checkpoint{ + err = store.SetCheckpoint(context.Background(), azeventhubs.Checkpoint{ ConsumerGroup: "$Default", EventHubName: "event-hub-name", FullyQualifiedNamespace: "ns.servicebus.windows.net", diff --git a/sdk/messaging/azeventhubs/consumer_client_internal_test.go b/sdk/messaging/azeventhubs/consumer_client_internal_test.go index dedd628be219..50a2d06ae62b 100644 --- a/sdk/messaging/azeventhubs/consumer_client_internal_test.go +++ b/sdk/messaging/azeventhubs/consumer_client_internal_test.go @@ -126,7 +126,7 @@ func TestConsumerClient_Recovery(t *testing.T) { require.NoError(t, err) require.EqualValues(t, 1, len(events)) - t.Logf("[%s] Received seq:%d, offset:%d", sr.PartitionID, events[0].SequenceNumber, *events[0].Offset) + t.Logf("[%s] Received seq:%d, offset:%d", sr.PartitionID, events[0].SequenceNumber, events[0].Offset) require.Equal(t, fmt.Sprintf("event 1 for partition %s", sr.PartitionID), string(events[0].Body)) }(i, sr) diff --git a/sdk/messaging/azeventhubs/event_data_test.go b/sdk/messaging/azeventhubs/event_data_test.go index b4ee83974841..7e749069c62c 100644 --- a/sdk/messaging/azeventhubs/event_data_test.go +++ b/sdk/messaging/azeventhubs/event_data_test.go @@ -20,7 +20,7 @@ func TestEventData_Annotations(t *testing.T) { require.Empty(t, re.Body) require.Nil(t, re.EnqueuedTime) require.Equal(t, int64(0), re.SequenceNumber) - require.Nil(t, re.Offset) + require.Zero(t, re.Offset) require.Nil(t, re.PartitionKey) }) diff --git a/sdk/messaging/azeventhubs/example_checkpoint_migration_test.go b/sdk/messaging/azeventhubs/example_checkpoint_migration_test.go index 2692d47c1fe4..937c364a8ae6 100644 --- a/sdk/messaging/azeventhubs/example_checkpoint_migration_test.go +++ b/sdk/messaging/azeventhubs/example_checkpoint_migration_test.go @@ -102,7 +102,7 @@ func Example_migrateCheckpoints() { newCheckpoint.Offset = &offset newCheckpoint.SequenceNumber = &oldCheckpoint.Checkpoint.SequenceNumber - if err := newCheckpointStore.UpdateCheckpoint(context.Background(), newCheckpoint, nil); err != nil { + if err := newCheckpointStore.SetCheckpoint(context.Background(), newCheckpoint, nil); err != nil { panic(err) } } diff --git a/sdk/messaging/azeventhubs/example_consuming_with_checkpoints_test.go b/sdk/messaging/azeventhubs/example_consuming_with_checkpoints_test.go index 1e4243cbafc1..70b37eeea2d0 100644 --- a/sdk/messaging/azeventhubs/example_consuming_with_checkpoints_test.go +++ b/sdk/messaging/azeventhubs/example_consuming_with_checkpoints_test.go @@ -136,7 +136,7 @@ func processEventsForPartition(partitionClient *azeventhubs.ProcessorPartitionCl // Updates the checkpoint with the latest event received. If processing needs to restart // it will restart from this point, automatically. - if err := partitionClient.UpdateCheckpoint(context.TODO(), events[len(events)-1]); err != nil { + if err := partitionClient.UpdateCheckpoint(context.TODO(), events[len(events)-1], nil); err != nil { return err } } @@ -154,7 +154,7 @@ func shutdownPartitionResources(partitionClient *azeventhubs.ProcessorPartitionC defer partitionClient.Close(context.TODO()) } -func createClientsForExample(eventHubConnectionString, eventHubName, storageConnectionString, storageContainerName string) (*azeventhubs.ConsumerClient, *checkpoints.BlobStore, error) { +func createClientsForExample(eventHubConnectionString, eventHubName, storageConnectionString, storageContainerName string) (*azeventhubs.ConsumerClient, *azeventhubs.CheckpointStore, error) { // NOTE: the storageContainerName must exist before the checkpoint store can be used. azBlobContainerClient, err := container.NewClientFromConnectionString(storageConnectionString, storageContainerName, nil) diff --git a/sdk/messaging/azeventhubs/inmemory_checkpoint_store_test.go b/sdk/messaging/azeventhubs/inmemory_checkpoint_store_test.go index ea3aa5677ee6..28a29ddd7de4 100644 --- a/sdk/messaging/azeventhubs/inmemory_checkpoint_store_test.go +++ b/sdk/messaging/azeventhubs/inmemory_checkpoint_store_test.go @@ -23,7 +23,7 @@ func Test_InMemoryCheckpointStore_Checkpoints(t *testing.T) { require.Empty(t, checkpoints) for i := int64(0); i < 5; i++ { - err = store.UpdateCheckpoint(context.Background(), Checkpoint{ + err = store.SetCheckpoint(context.Background(), Checkpoint{ FullyQualifiedNamespace: "ns", EventHubName: "eh", ConsumerGroup: "cg", @@ -167,13 +167,24 @@ type testCheckpointStore struct { ownershipMu sync.RWMutex ownerships map[string]Ownership + + store *CheckpointStore } func newCheckpointStoreForTest() *testCheckpointStore { - return &testCheckpointStore{ + cps := &testCheckpointStore{ checkpoints: map[string]Checkpoint{}, ownerships: map[string]Ownership{}, } + + cps.store = &CheckpointStore{ + ClaimOwnership: cps.ClaimOwnership, + ListCheckpoints: cps.ListCheckpoints, + ListOwnership: cps.ListOwnership, + SetCheckpoint: cps.SetCheckpoint, + } + + return cps } func (cps *testCheckpointStore) ExpireOwnership(o Ownership) { @@ -269,7 +280,7 @@ func (cps *testCheckpointStore) ListOwnership(ctx context.Context, fullyQualifie return ownerships, nil } -func (cps *testCheckpointStore) UpdateCheckpoint(ctx context.Context, checkpoint Checkpoint, options *UpdateCheckpointOptions) error { +func (cps *testCheckpointStore) SetCheckpoint(ctx context.Context, checkpoint Checkpoint, options *SetCheckpointOptions) error { cps.checkpointsMu.Lock() defer cps.checkpointsMu.Unlock() diff --git a/sdk/messaging/azeventhubs/internal/eh/stress/tests/processor_stress_tester.go b/sdk/messaging/azeventhubs/internal/eh/stress/tests/processor_stress_tester.go index a6d2398cb66a..3c46310561dd 100644 --- a/sdk/messaging/azeventhubs/internal/eh/stress/tests/processor_stress_tester.go +++ b/sdk/messaging/azeventhubs/internal/eh/stress/tests/processor_stress_tester.go @@ -43,7 +43,7 @@ type processorStressTest struct { prefetch int32 sleepAfterFn func() - checkpointStore azeventhubs.CheckpointStore + checkpointStore *azeventhubs.CheckpointStore } func newProcessorStressTest(args []string) (*processorStressTest, error) { @@ -285,7 +285,7 @@ func (inf *processorStressTest) receiveForever(ctx context.Context, partClient * if len(events) > 0 { // we're okay, let's update our checkpoint - if err := partClient.UpdateCheckpoint(ctx, events[len(events)-1]); err != nil { + if err := partClient.UpdateCheckpoint(ctx, events[len(events)-1], nil); err != nil { logger("Fatal error updating checkpoint: %s", err) inf.TC.TrackException(err) panic(err) diff --git a/sdk/messaging/azeventhubs/internal/eh/stress/tests/shared.go b/sdk/messaging/azeventhubs/internal/eh/stress/tests/shared.go index 4f291a73c28c..5e5e210c9245 100644 --- a/sdk/messaging/azeventhubs/internal/eh/stress/tests/shared.go +++ b/sdk/messaging/azeventhubs/internal/eh/stress/tests/shared.go @@ -318,7 +318,7 @@ func initCheckpointStore(ctx context.Context, containerName string, testData *st newCheckpoint.SequenceNumber = &partProps.LastEnqueuedSequenceNumber } - if err = cps.UpdateCheckpoint(ctx, newCheckpoint, nil); err != nil { + if err = cps.SetCheckpoint(ctx, newCheckpoint, nil); err != nil { return nil, err } diff --git a/sdk/messaging/azeventhubs/processor.go b/sdk/messaging/azeventhubs/processor.go index 5eae14183d4c..c34e469b4258 100644 --- a/sdk/messaging/azeventhubs/processor.go +++ b/sdk/messaging/azeventhubs/processor.go @@ -88,7 +88,7 @@ type StartPositions struct { type Processor struct { ownershipUpdateInterval time.Duration defaultStartPositions StartPositions - checkpointStore CheckpointStore + checkpointStore *CheckpointStore prefetch int32 // consumerClient is actually a *azeventhubs.ConsumerClient @@ -114,11 +114,11 @@ type consumerClientForProcessor interface { // type or the [example_consuming_with_checkpoints_test.go] for an example. // // [example_consuming_with_checkpoints_test.go]: https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/messaging/azeventhubs/example_consuming_with_checkpoints_test.go -func NewProcessor(consumerClient *ConsumerClient, checkpointStore CheckpointStore, options *ProcessorOptions) (*Processor, error) { +func NewProcessor(consumerClient *ConsumerClient, checkpointStore *CheckpointStore, options *ProcessorOptions) (*Processor, error) { return newProcessorImpl(consumerClient, checkpointStore, options) } -func newProcessorImpl(consumerClient consumerClientForProcessor, checkpointStore CheckpointStore, options *ProcessorOptions) (*Processor, error) { +func newProcessorImpl(consumerClient consumerClientForProcessor, checkpointStore *CheckpointStore, options *ProcessorOptions) (*Processor, error) { if options == nil { options = &ProcessorOptions{} } diff --git a/sdk/messaging/azeventhubs/processor_load_balancer.go b/sdk/messaging/azeventhubs/processor_load_balancer.go index 99f396cfd652..1fb28f05c67f 100644 --- a/sdk/messaging/azeventhubs/processor_load_balancer.go +++ b/sdk/messaging/azeventhubs/processor_load_balancer.go @@ -8,13 +8,14 @@ import ( "fmt" "math" "math/rand" + "strings" "time" "github.com/Azure/azure-sdk-for-go/sdk/internal/log" ) type processorLoadBalancer struct { - checkpointStore CheckpointStore + checkpointStore *CheckpointStore details consumerClientDetails strategy ProcessorStrategy partitionExpirationDuration time.Duration @@ -23,7 +24,7 @@ type processorLoadBalancer struct { rnd *rand.Rand } -func newProcessorLoadBalancer(checkpointStore CheckpointStore, details consumerClientDetails, strategy ProcessorStrategy, partitionExpiration time.Duration) *processorLoadBalancer { +func newProcessorLoadBalancer(checkpointStore *CheckpointStore, details consumerClientDetails, strategy ProcessorStrategy, partitionExpiration time.Duration) *processorLoadBalancer { return &processorLoadBalancer{ checkpointStore: checkpointStore, details: details, @@ -109,7 +110,24 @@ func (lb *processorLoadBalancer) LoadBalance(ctx context.Context, partitionIDs [ } } - return lb.checkpointStore.ClaimOwnership(ctx, ownerships, nil) + actual, err := lb.checkpointStore.ClaimOwnership(ctx, ownerships, nil) + + if err != nil { + return nil, err + } + + log.Writef(EventConsumer, "[%0.5s] Asked for %s, got %s", lb.details.ClientID, extractPartition(ownerships), extractPartition(actual)) + return actual, nil +} + +func extractPartition(all []Ownership) string { + var parts []string + + for _, o := range all { + parts = append(parts, o.PartitionID) + } + + return strings.Join(parts, ",") } // getAvailablePartitions finds all partitions that are either completely unowned _or_ diff --git a/sdk/messaging/azeventhubs/processor_load_balancers_test.go b/sdk/messaging/azeventhubs/processor_load_balancers_test.go index ee9b56a86ded..3cc7e286fe3f 100644 --- a/sdk/messaging/azeventhubs/processor_load_balancers_test.go +++ b/sdk/messaging/azeventhubs/processor_load_balancers_test.go @@ -20,7 +20,7 @@ func TestProcessorLoadBalancers_Greedy_EnoughUnownedPartitions(t *testing.T) { }, nil) require.NoError(t, err) - lb := newProcessorLoadBalancer(cps, newTestConsumerDetails("new-client"), ProcessorStrategyGreedy, time.Hour) + lb := newProcessorLoadBalancer(cps.store, newTestConsumerDetails("new-client"), ProcessorStrategyGreedy, time.Hour) // "0" and "3" are already claimed, so we'll pick up the 2 free partitions. ownerships, err := lb.LoadBalance(context.Background(), []string{"0", "1", "2", "3"}) @@ -42,7 +42,7 @@ func TestProcessorLoadBalancers_Balanced_UnownedPartitions(t *testing.T) { }, nil) require.NoError(t, err) - lb := newProcessorLoadBalancer(cps, newTestConsumerDetails("new-client"), ProcessorStrategyBalanced, time.Hour) + lb := newProcessorLoadBalancer(cps.store, newTestConsumerDetails("new-client"), ProcessorStrategyBalanced, time.Hour) // "0" and "3" are already claimed, so we'll pick up one partition each time we load balance ownerships, err := lb.LoadBalance(context.Background(), []string{"0", "1", "2", "3"}) @@ -53,7 +53,7 @@ func TestProcessorLoadBalancers_Balanced_UnownedPartitions(t *testing.T) { require.NoError(t, err) require.Equal(t, 2, len(ownerships)) - requireBalanced(t, cps, 4, 2) + requireBalanced(t, cps.store, 4, 2) } func TestProcessorLoadBalancers_Greedy_ForcedToSteal(t *testing.T) { @@ -79,7 +79,7 @@ func TestProcessorLoadBalancers_Greedy_ForcedToSteal(t *testing.T) { }, nil) require.NoError(t, err) - lb := newProcessorLoadBalancer(cps, newTestConsumerDetails(stealingClientID), ProcessorStrategyGreedy, time.Hour) + lb := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(stealingClientID), ProcessorStrategyGreedy, time.Hour) ownerships, err := lb.LoadBalance(context.Background(), []string{"0", "1", "2", "3", "4"}) require.NoError(t, err) @@ -122,13 +122,13 @@ func TestProcessorLoadBalancers_AnyStrategy_GrabExpiredPartition(t *testing.T) { // expire the middle partition (simulating that ClientC died, so nobody's updated it's ownership in awhile) cps.ExpireOwnership(middleOwnership) - lb := newProcessorLoadBalancer(cps, newTestConsumerDetails(clientB), strategy, time.Hour) + lb := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(clientB), strategy, time.Hour) ownerships, err := lb.LoadBalance(context.Background(), []string{"0", "1", "2", "3", "4"}) require.NoError(t, err) require.NotEmpty(t, mapToStrings(ownerships, extractPartitionID)) - requireBalanced(t, cps, 5, 2) + requireBalanced(t, cps.store, 5, 2) }) } } @@ -151,21 +151,21 @@ func TestProcessorLoadBalancers_AnyStrategy_FullyBalancedOdd(t *testing.T) { require.NoError(t, err) { - lbB := newProcessorLoadBalancer(cps, newTestConsumerDetails(clientB), strategy, time.Hour) + lbB := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(clientB), strategy, time.Hour) ownerships, err := lbB.LoadBalance(context.Background(), []string{"0", "1", "2", "3", "4"}) require.NoError(t, err) require.Equal(t, []string{"3", "4"}, mapToStrings(ownerships, extractPartitionID)) - requireBalanced(t, cps, 5, 2) + requireBalanced(t, cps.store, 5, 2) } { - lbA := newProcessorLoadBalancer(cps, newTestConsumerDetails(clientA), strategy, time.Hour) + lbA := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(clientA), strategy, time.Hour) ownerships, err := lbA.LoadBalance(context.Background(), []string{"0", "1", "2", "3", "4"}) require.NoError(t, err) require.Equal(t, []string{"0", "1", "2"}, mapToStrings(ownerships, extractPartitionID)) - requireBalanced(t, cps, 5, 2) + requireBalanced(t, cps.store, 5, 2) } }) } @@ -188,21 +188,21 @@ func TestProcessorLoadBalancers_AnyStrategy_FullyBalancedEven(t *testing.T) { require.NoError(t, err) { - lbB := newProcessorLoadBalancer(cps, newTestConsumerDetails(clientB), strategy, time.Hour) + lbB := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(clientB), strategy, time.Hour) ownerships, err := lbB.LoadBalance(context.Background(), []string{"0", "1", "2", "3"}) require.NoError(t, err) require.Equal(t, []string{"2", "3"}, mapToStrings(ownerships, extractPartitionID)) - requireBalanced(t, cps, 4, 2) + requireBalanced(t, cps.store, 4, 2) } { - lbA := newProcessorLoadBalancer(cps, newTestConsumerDetails(clientA), strategy, time.Hour) + lbA := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(clientA), strategy, time.Hour) ownerships, err := lbA.LoadBalance(context.Background(), []string{"0", "1", "2", "3"}) require.NoError(t, err) require.Equal(t, []string{"0", "1"}, mapToStrings(ownerships, extractPartitionID)) - requireBalanced(t, cps, 4, 2) + requireBalanced(t, cps.store, 4, 2) } }) } @@ -225,13 +225,13 @@ func TestProcessorLoadBalancers_Any_GrabExtraPartitionBecauseAboveMax(t *testing }, nil) require.NoError(t, err) - lb := newProcessorLoadBalancer(cps, newTestConsumerDetails(clientB), strategy, time.Hour) + lb := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(clientB), strategy, time.Hour) ownerships, err := lb.LoadBalance(context.Background(), []string{"0", "1", "2", "3", "4"}) require.NoError(t, err) require.NotEmpty(t, mapToStrings(ownerships, extractPartitionID)) - requireBalanced(t, cps, 5, 2) + requireBalanced(t, cps.store, 5, 2) }) } } @@ -254,7 +254,7 @@ func TestProcessorLoadBalancers_AnyStrategy_StealsToBalance(t *testing.T) { require.NoError(t, err) { - tooManyLB := newProcessorLoadBalancer(cps, newTestConsumerDetails(lotsClientID), strategy, time.Hour) + tooManyLB := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(lotsClientID), strategy, time.Hour) require.NoError(t, err) ownerships, err := tooManyLB.LoadBalance(context.Background(), allPartitionIDs) @@ -266,7 +266,7 @@ func TestProcessorLoadBalancers_AnyStrategy_StealsToBalance(t *testing.T) { } { - tooFewLB := newProcessorLoadBalancer(cps, newTestConsumerDetails(littleClientID), strategy, time.Hour) + tooFewLB := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(littleClientID), strategy, time.Hour) require.NoError(t, err) // either strategy will balance here by stealing. @@ -275,7 +275,7 @@ func TestProcessorLoadBalancers_AnyStrategy_StealsToBalance(t *testing.T) { require.Equal(t, 2, len(ownerships)) } - requireBalanced(t, cps, len(allPartitionIDs), 2) + requireBalanced(t, cps.store, len(allPartitionIDs), 2) }) } } @@ -283,12 +283,12 @@ func TestProcessorLoadBalancers_AnyStrategy_StealsToBalance(t *testing.T) { func TestProcessorLoadBalancers_InvalidStrategy(t *testing.T) { cps := newCheckpointStoreForTest() - lb := newProcessorLoadBalancer(cps, newTestConsumerDetails("does not matter"), "", time.Hour) + lb := newProcessorLoadBalancer(cps.store, newTestConsumerDetails("does not matter"), "", time.Hour) ownerships, err := lb.LoadBalance(context.Background(), []string{"0"}) require.Nil(t, ownerships) require.EqualError(t, err, "invalid load balancing strategy ''") - lb = newProcessorLoadBalancer(cps, newTestConsumerDetails("does not matter"), "super-greedy", time.Hour) + lb = newProcessorLoadBalancer(cps.store, newTestConsumerDetails("does not matter"), "super-greedy", time.Hour) ownerships, err = lb.LoadBalance(context.Background(), []string{"0"}) require.Nil(t, ownerships) require.EqualError(t, err, "invalid load balancing strategy 'super-greedy'") @@ -328,7 +328,7 @@ func newTestConsumerDetails(clientID string) consumerClientDetails { } } -func requireBalanced(t *testing.T, cps CheckpointStore, totalPartitions int, numConsumers int) { +func requireBalanced(t *testing.T, cps *CheckpointStore, totalPartitions int, numConsumers int) { ownerships, err := cps.ListOwnership(context.Background(), testEventHubFQDN, testEventHubName, testConsumerGroup, nil) require.NoError(t, err) diff --git a/sdk/messaging/azeventhubs/processor_partition_client.go b/sdk/messaging/azeventhubs/processor_partition_client.go index da0f4eb402b8..fca813e95291 100644 --- a/sdk/messaging/azeventhubs/processor_partition_client.go +++ b/sdk/messaging/azeventhubs/processor_partition_client.go @@ -20,7 +20,7 @@ import "context" type ProcessorPartitionClient struct { partitionID string innerClient *PartitionClient - checkpointStore CheckpointStore + checkpointStore *CheckpointStore cleanupFn func() consumerClientDetails consumerClientDetails } @@ -33,16 +33,18 @@ func (c *ProcessorPartitionClient) ReceiveEvents(ctx context.Context, count int, return c.innerClient.ReceiveEvents(ctx, count, options) } -// UpdateCheckpoint updates the checkpoint store. This ensure that if the Processor is restarted it will // start from after this point. -func (p *ProcessorPartitionClient) UpdateCheckpoint(ctx context.Context, latestEvent *ReceivedEventData) error { - return p.checkpointStore.UpdateCheckpoint(ctx, Checkpoint{ +func (p *ProcessorPartitionClient) UpdateCheckpoint(ctx context.Context, latestEvent *ReceivedEventData, options *UpdateCheckpointOptions) error { + seq := latestEvent.SequenceNumber + offset := latestEvent.Offset + + return p.checkpointStore.SetCheckpoint(ctx, Checkpoint{ ConsumerGroup: p.consumerClientDetails.ConsumerGroup, EventHubName: p.consumerClientDetails.EventHubName, FullyQualifiedNamespace: p.consumerClientDetails.FullyQualifiedNamespace, PartitionID: p.partitionID, - SequenceNumber: &latestEvent.SequenceNumber, - Offset: latestEvent.Offset, + SequenceNumber: &seq, + Offset: &offset, }, nil) } @@ -63,3 +65,8 @@ func (c *ProcessorPartitionClient) Close(ctx context.Context) error { return nil } + +// UpdateCheckpointOptions contains optional parameters for the [ProcessorPartitionClient.UpdateCheckpoint] function. +type UpdateCheckpointOptions struct { + // For future expansion +} diff --git a/sdk/messaging/azeventhubs/processor_test.go b/sdk/messaging/azeventhubs/processor_test.go index 136c3391066a..f6bef27beae1 100644 --- a/sdk/messaging/azeventhubs/processor_test.go +++ b/sdk/messaging/azeventhubs/processor_test.go @@ -95,9 +95,7 @@ func TestProcessor_Contention(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - processor, err := azeventhubs.NewProcessor(consumerClient, checkpointStore, &azeventhubs.ProcessorOptions{ - UpdateInterval: 10 * time.Second, - }) + processor, err := azeventhubs.NewProcessor(consumerClient, checkpointStore, nil) require.NoError(t, err) processors = append(processors, testData{ @@ -142,8 +140,8 @@ func TestProcessor_Contention(t *testing.T) { nextCtx, cancelNext := context.WithCancel(context.Background()) defer cancelNext() - // arbitrary, but basically if we go 20 seconds without a new partition acquisition we're probably balanced. - const idleInterval = 20 * time.Second + // arbitrary interval, we just want to give enough time that things seem balanced. + const idleInterval = 10 * time.Second active := time.AfterFunc(idleInterval, cancelNext) for { @@ -160,7 +158,7 @@ func TestProcessor_Contention(t *testing.T) { active.Reset(time.Minute) } - t.Logf("%s hasn't received a new partition in %sseconds", procStuff.name, idleInterval/time.Second) + t.Logf("%s hasn't received a new partition in %s", procStuff.name, idleInterval) }(client) } @@ -403,7 +401,7 @@ func processEventsForTest(t *testing.T, producerClient *azeventhubs.ProducerClie t.Logf("Updating checkpoint for partition %s", partitionClient.PartitionID()) - if err := partitionClient.UpdateCheckpoint(context.TODO(), events[len(events)-1]); err != nil { + if err := partitionClient.UpdateCheckpoint(context.TODO(), events[len(events)-1], nil); err != nil { return err } @@ -415,7 +413,7 @@ func processEventsForTest(t *testing.T, producerClient *azeventhubs.ProducerClie } } -func printOwnerships(ctx context.Context, t *testing.T, cps azeventhubs.CheckpointStore, testParams test.ConnectionParamsForTest, partitionIDs []string, expectedConsumers int) { +func printOwnerships(ctx context.Context, t *testing.T, cps *azeventhubs.CheckpointStore, testParams test.ConnectionParamsForTest, partitionIDs []string, expectedConsumers int) { max := len(partitionIDs) / expectedConsumers if len(partitionIDs)%expectedConsumers > 0 { diff --git a/sdk/messaging/azeventhubs/processor_unit_test.go b/sdk/messaging/azeventhubs/processor_unit_test.go index ff257b58d408..cb2142b6ce44 100644 --- a/sdk/messaging/azeventhubs/processor_unit_test.go +++ b/sdk/messaging/azeventhubs/processor_unit_test.go @@ -20,7 +20,7 @@ import ( func TestUnit_Processor_loadBalancing(t *testing.T) { cps := newCheckpointStoreForTest() - firstProcessor := newProcessorForTest(t, "first-processor", cps) + firstProcessor := newProcessorForTest(t, "first-processor", cps.store) newTestOwnership := func(base Ownership) Ownership { base.ConsumerGroup = "consumer-group" base.EventHubName = "event-hub" @@ -80,7 +80,7 @@ func TestUnit_Processor_loadBalancing(t *testing.T) { // 1 of those partitions is owned by our client ("first-processor") // 2 are still available. - secondProcessor := newProcessorForTest(t, "second-processor", cps) + secondProcessor := newProcessorForTest(t, "second-processor", cps.store) // when we ask for available partitions we take into account the owners that are // present in the checkpoint store and ourselves, since we're about to try to claim @@ -133,7 +133,7 @@ func TestUnit_Processor_loadBalancing(t *testing.T) { func TestUnit_Processor_Run(t *testing.T) { cps := newCheckpointStoreForTest() - processor, err := newProcessorImpl(simpleFakeConsumerClient(), cps, &ProcessorOptions{ + processor, err := newProcessorImpl(simpleFakeConsumerClient(), cps.store, &ProcessorOptions{ PartitionExpirationDuration: time.Hour, }) @@ -180,7 +180,7 @@ func TestUnit_Processor_Run_singleConsumerPerPartition(t *testing.T) { }, } - processor, err := newProcessorImpl(cc, cps, &ProcessorOptions{ + processor, err := newProcessorImpl(cc, cps.store, &ProcessorOptions{ PartitionExpirationDuration: time.Hour, }) require.NoError(t, err) @@ -224,7 +224,7 @@ func TestUnit_Processor_Run_singleConsumerPerPartition(t *testing.T) { func TestUnit_Processor_Run_startPosition(t *testing.T) { cps := newCheckpointStoreForTest() - err := cps.UpdateCheckpoint(context.Background(), Checkpoint{ + err := cps.SetCheckpoint(context.Background(), Checkpoint{ ConsumerGroup: "consumer-group", EventHubName: "event-hub", FullyQualifiedNamespace: "fqdn", @@ -242,7 +242,7 @@ func TestUnit_Processor_Run_startPosition(t *testing.T) { return newFakePartitionClient(partitionID, offsetExpr), nil } - processor, err := newProcessorImpl(fakeConsumerClient, cps, &ProcessorOptions{ + processor, err := newProcessorImpl(fakeConsumerClient, cps.store, &ProcessorOptions{ PartitionExpirationDuration: time.Hour, }) require.NoError(t, err) @@ -266,7 +266,7 @@ func TestUnit_Processor_Run_startPosition(t *testing.T) { err = partClient.UpdateCheckpoint(context.Background(), &ReceivedEventData{ SequenceNumber: 405, - }) + }, nil) require.NoError(t, err) checkpoints, err = cps.ListCheckpoints(context.Background(), processor.consumerClientDetails.FullyQualifiedNamespace, @@ -298,7 +298,7 @@ func TestUnit_Processor_Run_cancellation(t *testing.T) { FullyQualifiedNamespace: "fqdn", ClientID: "my-client-id", }, - }, cps, &ProcessorOptions{ + }, cps.store, &ProcessorOptions{ PartitionExpirationDuration: time.Hour, }) @@ -329,7 +329,7 @@ func updateDynamicData(t *testing.T, src Ownership, expected Ownership, allParti return expected } -func newProcessorForTest(t *testing.T, clientID string, cps CheckpointStore) *Processor { +func newProcessorForTest(t *testing.T, clientID string, cps *CheckpointStore) *Processor { processor, err := newProcessorImpl(&fakeConsumerClient{ details: consumerClientDetails{ ConsumerGroup: "consumer-group", From 08b20bace01911add8671ca8a906dbf88d2a6517 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Fri, 5 May 2023 13:43:43 -0700 Subject: [PATCH 3/9] We call the outer function a bit, no reason to iterate the partition IDs unless we're actually logging --- sdk/messaging/azeventhubs/processor_load_balancer.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sdk/messaging/azeventhubs/processor_load_balancer.go b/sdk/messaging/azeventhubs/processor_load_balancer.go index 1fb28f05c67f..63788c830d5f 100644 --- a/sdk/messaging/azeventhubs/processor_load_balancer.go +++ b/sdk/messaging/azeventhubs/processor_load_balancer.go @@ -116,11 +116,14 @@ func (lb *processorLoadBalancer) LoadBalance(ctx context.Context, partitionIDs [ return nil, err } - log.Writef(EventConsumer, "[%0.5s] Asked for %s, got %s", lb.details.ClientID, extractPartition(ownerships), extractPartition(actual)) + if log.Should(EventConsumer) { + log.Writef(EventConsumer, "[%0.5s] Asked for %s, got %s", lb.details.ClientID, partitionsForOwnerships(ownerships), partitionsForOwnerships(actual)) + } + return actual, nil } -func extractPartition(all []Ownership) string { +func partitionsForOwnerships(all []Ownership) string { var parts []string for _, o := range all { From 63a3eb783106faf262b3b6d7fa0d92d97b03b627 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Fri, 5 May 2023 13:56:16 -0700 Subject: [PATCH 4/9] Looks like a doc comment got clipped out. Updated. --- sdk/messaging/azeventhubs/processor_partition_client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/messaging/azeventhubs/processor_partition_client.go b/sdk/messaging/azeventhubs/processor_partition_client.go index fca813e95291..35360032c93e 100644 --- a/sdk/messaging/azeventhubs/processor_partition_client.go +++ b/sdk/messaging/azeventhubs/processor_partition_client.go @@ -33,7 +33,8 @@ func (c *ProcessorPartitionClient) ReceiveEvents(ctx context.Context, count int, return c.innerClient.ReceiveEvents(ctx, count, options) } -// start from after this point. +// UpdateCheckpoint updates the checkpoint in the CheckpointStore. New Processors will resume after +// this checkpoint for this partition. func (p *ProcessorPartitionClient) UpdateCheckpoint(ctx context.Context, latestEvent *ReceivedEventData, options *UpdateCheckpointOptions) error { seq := latestEvent.SequenceNumber offset := latestEvent.Offset From 5c6de1e07ff179af93a65a9b6ecae3cca95aa828 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Fri, 5 May 2023 14:21:26 -0700 Subject: [PATCH 5/9] Now that we're just creating a *CheckpointStore we don't actually have to export the blobStore type anymore. --- .../azeventhubs/checkpoints/blob_store.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sdk/messaging/azeventhubs/checkpoints/blob_store.go b/sdk/messaging/azeventhubs/checkpoints/blob_store.go index e99bc844837e..4957d7ff17de 100644 --- a/sdk/messaging/azeventhubs/checkpoints/blob_store.go +++ b/sdk/messaging/azeventhubs/checkpoints/blob_store.go @@ -23,8 +23,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" ) -// BlobStore is a CheckpointStore implementation that uses Azure Blob storage. -type BlobStore struct { +// blobStore is a CheckpointStore implementation that uses Azure Blob storage. +type blobStore struct { cc *container.Client } @@ -38,7 +38,7 @@ type BlobStoreOptions struct { // Azure Blob storage. // NOTE: the container must exist before the checkpoint store can be used. func NewBlobStore(containerClient *container.Client, options *BlobStoreOptions) (*azeventhubs.CheckpointStore, error) { - bs := &BlobStore{ + bs := &blobStore{ cc: containerClient, } @@ -55,7 +55,7 @@ func NewBlobStore(containerClient *container.Client, options *BlobStoreOptions) // // If we fail to claim ownership because of another update then it will be omitted from the // returned slice of [Ownership]'s. It is not considered an error. -func (b *BlobStore) ClaimOwnership(ctx context.Context, partitionOwnership []azeventhubs.Ownership, options *azeventhubs.ClaimOwnershipOptions) ([]azeventhubs.Ownership, error) { +func (b *blobStore) ClaimOwnership(ctx context.Context, partitionOwnership []azeventhubs.Ownership, options *azeventhubs.ClaimOwnershipOptions) ([]azeventhubs.Ownership, error) { var ownerships []azeventhubs.Ownership // TODO: in parallel? @@ -90,7 +90,7 @@ func (b *BlobStore) ClaimOwnership(ctx context.Context, partitionOwnership []aze } // ListCheckpoints lists all the available checkpoints. -func (b *BlobStore) ListCheckpoints(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *azeventhubs.ListCheckpointsOptions) ([]azeventhubs.Checkpoint, error) { +func (b *blobStore) ListCheckpoints(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *azeventhubs.ListCheckpointsOptions) ([]azeventhubs.Checkpoint, error) { prefix, err := prefixForCheckpointBlobs(azeventhubs.Checkpoint{ FullyQualifiedNamespace: fullyQualifiedNamespace, EventHubName: eventHubName, @@ -141,7 +141,7 @@ func (b *BlobStore) ListCheckpoints(ctx context.Context, fullyQualifiedNamespace var partitionIDRegexp = regexp.MustCompile("[^/]+?$") // ListOwnership lists all ownerships. -func (b *BlobStore) ListOwnership(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *azeventhubs.ListOwnershipOptions) ([]azeventhubs.Ownership, error) { +func (b *blobStore) ListOwnership(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *azeventhubs.ListOwnershipOptions) ([]azeventhubs.Ownership, error) { prefix, err := prefixForOwnershipBlobs(azeventhubs.Ownership{ FullyQualifiedNamespace: fullyQualifiedNamespace, EventHubName: eventHubName, @@ -193,7 +193,7 @@ func (b *BlobStore) ListOwnership(ctx context.Context, fullyQualifiedNamespace s // SetCheckpoint updates a specific checkpoint with a sequence and offset. // // NOTE: This function doesn't attempt to prevent simultaneous checkpoint updates - ownership is assumed. -func (b *BlobStore) SetCheckpoint(ctx context.Context, checkpoint azeventhubs.Checkpoint, options *azeventhubs.SetCheckpointOptions) error { +func (b *blobStore) SetCheckpoint(ctx context.Context, checkpoint azeventhubs.Checkpoint, options *azeventhubs.SetCheckpointOptions) error { blobName, err := nameForCheckpointBlob(checkpoint) if err != nil { @@ -204,7 +204,7 @@ func (b *BlobStore) SetCheckpoint(ctx context.Context, checkpoint azeventhubs.Ch return err } -func (b *BlobStore) setOwnershipMetadata(ctx context.Context, blobName string, ownership azeventhubs.Ownership) (*time.Time, azcore.ETag, error) { +func (b *blobStore) setOwnershipMetadata(ctx context.Context, blobName string, ownership azeventhubs.Ownership) (*time.Time, azcore.ETag, error) { blobMetadata := newOwnershipBlobMetadata(ownership) blobClient := b.cc.NewBlockBlobClient(blobName) @@ -247,7 +247,7 @@ func (b *BlobStore) setOwnershipMetadata(ctx context.Context, blobName string, o // // NOTE: unlike [setOwnershipMetadata] this function doesn't attempt to prevent simultaneous // checkpoint updates - ownership is assumed. -func (b *BlobStore) setCheckpointMetadata(ctx context.Context, blobName string, checkpoint azeventhubs.Checkpoint) (*time.Time, azcore.ETag, error) { +func (b *blobStore) setCheckpointMetadata(ctx context.Context, blobName string, checkpoint azeventhubs.Checkpoint) (*time.Time, azcore.ETag, error) { blobMetadata := newCheckpointBlobMetadata(checkpoint) blobClient := b.cc.NewBlockBlobClient(blobName) From 1b191505190675213cbb211efad0ce6c179ccdc2 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Fri, 5 May 2023 16:48:23 -0700 Subject: [PATCH 6/9] Removing the vendored go-amqp! --- sdk/messaging/azeventhubs/go.mod | 1 + sdk/messaging/azeventhubs/go.sum | 2 + .../azeventhubs/internal/go-amqp/LICENSE | 22 - .../azeventhubs/internal/go-amqp/conn.go | 1135 --------- .../azeventhubs/internal/go-amqp/const.go | 96 - .../azeventhubs/internal/go-amqp/creditor.go | 119 - .../azeventhubs/internal/go-amqp/errors.go | 107 - .../go-amqp/internal/bitmap/bitmap.go | 99 - .../go-amqp/internal/buffer/buffer.go | 180 -- .../internal/go-amqp/internal/debug/debug.go | 20 - .../go-amqp/internal/debug/debug_debug.go | 51 - .../go-amqp/internal/encoding/decode.go | 1150 --------- .../go-amqp/internal/encoding/encode.go | 573 ----- .../go-amqp/internal/encoding/types.go | 2155 ----------------- .../go-amqp/internal/frames/frames.go | 1543 ------------ .../go-amqp/internal/frames/parsing.go | 162 -- .../internal/go-amqp/internal/queue/queue.go | 164 -- .../go-amqp/internal/shared/shared.go | 36 - .../azeventhubs/internal/go-amqp/link.go | 390 --- .../internal/go-amqp/link_options.go | 241 -- .../azeventhubs/internal/go-amqp/message.go | 492 ---- .../azeventhubs/internal/go-amqp/receiver.go | 897 ------- .../azeventhubs/internal/go-amqp/sasl.go | 262 -- .../azeventhubs/internal/go-amqp/sender.go | 476 ---- .../azeventhubs/internal/go-amqp/session.go | 792 ------ 25 files changed, 3 insertions(+), 11162 deletions(-) delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/LICENSE delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/conn.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/const.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/creditor.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/errors.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/internal/bitmap/bitmap.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer/buffer.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/internal/debug/debug.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/internal/debug/debug_debug.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding/decode.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding/encode.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding/types.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/internal/frames/frames.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/internal/frames/parsing.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/internal/queue/queue.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/internal/shared/shared.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/link.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/link_options.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/message.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/receiver.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/sasl.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/sender.go delete mode 100644 sdk/messaging/azeventhubs/internal/go-amqp/session.go diff --git a/sdk/messaging/azeventhubs/go.mod b/sdk/messaging/azeventhubs/go.mod index e158f2a2713d..893c06dc1db4 100644 --- a/sdk/messaging/azeventhubs/go.mod +++ b/sdk/messaging/azeventhubs/go.mod @@ -16,6 +16,7 @@ require ( require ( code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect + github.com/Azure/go-amqp v1.0.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gofrs/uuid v3.3.0+incompatible // indirect diff --git a/sdk/messaging/azeventhubs/go.sum b/sdk/messaging/azeventhubs/go.sum index 20d483a3af2e..a09abd6c3dc0 100644 --- a/sdk/messaging/azeventhubs/go.sum +++ b/sdk/messaging/azeventhubs/go.sum @@ -10,6 +10,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.0. github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.0.0/go.mod h1:Y3gnVwfaz8h6L1YHar+NfWORtBoVUSB5h4GlGkdeF7Q= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag= +github.com/Azure/go-amqp v1.0.0 h1:QfCugi1M+4F2JDTRgVnRw7PYXLXZ9hmqk3+9+oJh3OA= +github.com/Azure/go-amqp v1.0.0/go.mod h1:+bg0x3ce5+Q3ahCEXnCsGG3ETpDQe3MEVnOuT2ywPwc= github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7KY7zH2mqygeUD0t8hNFXe08p1Pb3/jKE= github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/LICENSE b/sdk/messaging/azeventhubs/internal/go-amqp/LICENSE deleted file mode 100644 index 387b3e7e0f3b..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ - MIT License - - Copyright (C) 2017 Kale Blankenship - Portions Copyright (C) Microsoft Corporation - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/conn.go b/sdk/messaging/azeventhubs/internal/go-amqp/conn.go deleted file mode 100644 index 519f9d4cc5de..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/conn.go +++ /dev/null @@ -1,1135 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "bytes" - "context" - "crypto/tls" - "errors" - "fmt" - "math" - "net" - "net/url" - "sync" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/bitmap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/debug" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/frames" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/shared" -) - -// Default connection options -const ( - defaultIdleTimeout = 1 * time.Minute - defaultMaxFrameSize = 65536 - defaultMaxSessions = 65536 - defaultWriteTimeout = 30 * time.Second -) - -// ConnOptions contains the optional settings for configuring an AMQP connection. -type ConnOptions struct { - // ContainerID sets the container-id to use when opening the connection. - // - // A container ID will be randomly generated if this option is not used. - ContainerID string - - // HostName sets the hostname sent in the AMQP - // Open frame and TLS ServerName (if not otherwise set). - HostName string - - // IdleTimeout specifies the maximum period between - // receiving frames from the peer. - // - // Specify a value less than zero to disable idle timeout. - // - // Default: 1 minute (60000000000). - IdleTimeout time.Duration - - // MaxFrameSize sets the maximum frame size that - // the connection will accept. - // - // Must be 512 or greater. - // - // Default: 512. - MaxFrameSize uint32 - - // MaxSessions sets the maximum number of channels. - // The value must be greater than zero. - // - // Default: 65535. - MaxSessions uint16 - - // Properties sets an entry in the connection properties map sent to the server. - Properties map[string]any - - // SASLType contains the specified SASL authentication mechanism. - SASLType SASLType - - // TLSConfig sets the tls.Config to be used during - // TLS negotiation. - // - // This option is for advanced usage, in most scenarios - // providing a URL scheme of "amqps://" is sufficient. - TLSConfig *tls.Config - - // WriteTimeout controls the write deadline when writing AMQP frames to the - // underlying net.Conn and no caller provided context.Context is available or - // the context contains no deadline (e.g. context.Background()). - // The timeout is set per write. - // - // Setting to a value less than zero means no timeout is set, so writes - // defer to the underlying behavior of net.Conn with no write deadline. - // - // Default: 30s - WriteTimeout time.Duration - - // test hook - dialer dialer -} - -// Dial connects to an AMQP server. -// -// If the addr includes a scheme, it must be "amqp", "amqps", or "amqp+ssl". -// If no port is provided, 5672 will be used for "amqp" and 5671 for "amqps" or "amqp+ssl". -// -// If username and password information is not empty it's used as SASL PLAIN -// credentials, equal to passing ConnSASLPlain option. -// -// opts: pass nil to accept the default values. -func Dial(ctx context.Context, addr string, opts *ConnOptions) (*Conn, error) { - c, err := dialConn(ctx, addr, opts) - if err != nil { - return nil, err - } - err = c.start(ctx) - if err != nil { - return nil, err - } - return c, nil -} - -// NewConn establishes a new AMQP client connection over conn. -// opts: pass nil to accept the default values. -func NewConn(ctx context.Context, conn net.Conn, opts *ConnOptions) (*Conn, error) { - c, err := newConn(conn, opts) - if err != nil { - return nil, err - } - err = c.start(ctx) - if err != nil { - return nil, err - } - return c, nil -} - -// Conn is an AMQP connection. -type Conn struct { - net net.Conn // underlying connection - dialer dialer // used for testing purposes, it allows faking dialing TCP/TLS endpoints - writeTimeout time.Duration // controls write deadline in absense of a context - - // TLS - tlsNegotiation bool // negotiate TLS - tlsComplete bool // TLS negotiation complete - tlsConfig *tls.Config // TLS config, default used if nil (ServerName set to Client.hostname) - - // SASL - saslHandlers map[encoding.Symbol]stateFunc // map of supported handlers keyed by SASL mechanism, SASL not negotiated if nil - saslComplete bool // SASL negotiation complete; internal *except* for SASL auth methods - - // local settings - maxFrameSize uint32 // max frame size to accept - channelMax uint16 // maximum number of channels to allow - hostname string // hostname of remote server (set explicitly or parsed from URL) - idleTimeout time.Duration // maximum period between receiving frames - properties map[encoding.Symbol]any // additional properties sent upon connection open - containerID string // set explicitly or randomly generated - - // peer settings - peerIdleTimeout time.Duration // maximum period between sending frames - peerMaxFrameSize uint32 // maximum frame size peer will accept - - // conn state - done chan struct{} // indicates the connection has terminated - doneErr error // contains the error state returned from Close(); DO NOT TOUCH outside of conn.go until done has been closed! - - // connReader and connWriter management - rxtxExit chan struct{} // signals connReader and connWriter to exit - closeOnce sync.Once // ensures that close() is only called once - - // session tracking - channels *bitmap.Bitmap - sessionsByChannel map[uint16]*Session - sessionsByChannelMu sync.RWMutex - - abandonedSessionsMu sync.Mutex - abandonedSessions []*Session - - // connReader - rxBuf buffer.Buffer // incoming bytes buffer - rxDone chan struct{} // closed when connReader exits - rxErr error // contains last error reading from c.net; DO NOT TOUCH outside of connReader until rxDone has been closed! - - // connWriter - txFrame chan frameEnvelope // AMQP frames to be sent by connWriter - txBuf buffer.Buffer // buffer for marshaling frames before transmitting - txDone chan struct{} // closed when connWriter exits - txErr error // contains last error writing to c.net; DO NOT TOUCH outside of connWriter until txDone has been closed! -} - -// used to abstract the underlying dialer for testing purposes -type dialer interface { - NetDialerDial(ctx context.Context, c *Conn, host, port string) error - TLSDialWithDialer(ctx context.Context, c *Conn, host, port string) error -} - -// implements the dialer interface -type defaultDialer struct{} - -func (defaultDialer) NetDialerDial(ctx context.Context, c *Conn, host, port string) (err error) { - dialer := &net.Dialer{} - c.net, err = dialer.DialContext(ctx, "tcp", net.JoinHostPort(host, port)) - return -} - -func (defaultDialer) TLSDialWithDialer(ctx context.Context, c *Conn, host, port string) (err error) { - dialer := &tls.Dialer{Config: c.tlsConfig} - c.net, err = dialer.DialContext(ctx, "tcp", net.JoinHostPort(host, port)) - return -} - -func dialConn(ctx context.Context, addr string, opts *ConnOptions) (*Conn, error) { - u, err := url.Parse(addr) - if err != nil { - return nil, err - } - host, port := u.Hostname(), u.Port() - if port == "" { - port = "5672" - if u.Scheme == "amqps" || u.Scheme == "amqp+ssl" { - port = "5671" - } - } - - var cp ConnOptions - if opts != nil { - cp = *opts - } - - // prepend SASL credentials when the user/pass segment is not empty - if u.User != nil { - pass, _ := u.User.Password() - cp.SASLType = SASLTypePlain(u.User.Username(), pass) - } - - if cp.HostName == "" { - cp.HostName = host - } - - c, err := newConn(nil, &cp) - if err != nil { - return nil, err - } - - switch u.Scheme { - case "amqp", "": - err = c.dialer.NetDialerDial(ctx, c, host, port) - case "amqps", "amqp+ssl": - c.initTLSConfig() - c.tlsNegotiation = false - err = c.dialer.TLSDialWithDialer(ctx, c, host, port) - default: - err = fmt.Errorf("unsupported scheme %q", u.Scheme) - } - - if err != nil { - return nil, err - } - return c, nil -} - -func newConn(netConn net.Conn, opts *ConnOptions) (*Conn, error) { - c := &Conn{ - dialer: defaultDialer{}, - net: netConn, - maxFrameSize: defaultMaxFrameSize, - peerMaxFrameSize: defaultMaxFrameSize, - channelMax: defaultMaxSessions - 1, // -1 because channel-max starts at zero - idleTimeout: defaultIdleTimeout, - containerID: shared.RandString(40), - done: make(chan struct{}), - rxtxExit: make(chan struct{}), - rxDone: make(chan struct{}), - txFrame: make(chan frameEnvelope), - txDone: make(chan struct{}), - sessionsByChannel: map[uint16]*Session{}, - writeTimeout: defaultWriteTimeout, - } - - // apply options - if opts == nil { - opts = &ConnOptions{} - } - - if opts.WriteTimeout > 0 { - c.writeTimeout = opts.WriteTimeout - } else if opts.WriteTimeout < 0 { - c.writeTimeout = 0 - } - if opts.ContainerID != "" { - c.containerID = opts.ContainerID - } - if opts.HostName != "" { - c.hostname = opts.HostName - } - if opts.IdleTimeout > 0 { - c.idleTimeout = opts.IdleTimeout - } else if opts.IdleTimeout < 0 { - c.idleTimeout = 0 - } - if opts.MaxFrameSize > 0 && opts.MaxFrameSize < 512 { - return nil, fmt.Errorf("invalid MaxFrameSize value %d", opts.MaxFrameSize) - } else if opts.MaxFrameSize > 512 { - c.maxFrameSize = opts.MaxFrameSize - } - if opts.MaxSessions > 0 { - c.channelMax = opts.MaxSessions - } - if opts.SASLType != nil { - if err := opts.SASLType(c); err != nil { - return nil, err - } - } - if opts.Properties != nil { - c.properties = make(map[encoding.Symbol]any) - for key, val := range opts.Properties { - c.properties[encoding.Symbol(key)] = val - } - } - if opts.TLSConfig != nil { - c.tlsConfig = opts.TLSConfig.Clone() - } - if opts.dialer != nil { - c.dialer = opts.dialer - } - return c, nil -} - -func (c *Conn) initTLSConfig() { - // create a new config if not already set - if c.tlsConfig == nil { - c.tlsConfig = new(tls.Config) - } - - // TLS config must have ServerName or InsecureSkipVerify set - if c.tlsConfig.ServerName == "" && !c.tlsConfig.InsecureSkipVerify { - c.tlsConfig.ServerName = c.hostname - } -} - -// start establishes the connection and begins multiplexing network IO. -// It is an error to call Start() on a connection that's been closed. -func (c *Conn) start(ctx context.Context) (err error) { - // if the context has a deadline or is cancellable, start the interruptor goroutine. - // this will close the underlying net.Conn in response to the context. - - if ctx.Done() != nil { - done := make(chan struct{}) - interruptRes := make(chan error, 1) - - defer func() { - close(done) - if ctxErr := <-interruptRes; ctxErr != nil { - // return context error to caller - err = ctxErr - } - }() - - go func() { - select { - case <-ctx.Done(): - c.closeDuringStart() - interruptRes <- ctx.Err() - case <-done: - interruptRes <- nil - } - }() - } - - if err = c.startImpl(ctx); err != nil { - return err - } - - // we can't create the channel bitmap until the connection has been established. - // this is because our peer can tell us the max channels they support. - c.channels = bitmap.New(uint32(c.channelMax)) - - go c.connWriter() - go c.connReader() - - return -} - -func (c *Conn) startImpl(ctx context.Context) error { - // set connection establishment deadline as required - if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() { - _ = c.net.SetDeadline(deadline) - - // remove connection establishment deadline - defer func() { - _ = c.net.SetDeadline(time.Time{}) - }() - } - - // run connection establishment state machine - for state := c.negotiateProto; state != nil; { - var err error - state, err = state(ctx) - // check if err occurred - if err != nil { - c.closeDuringStart() - return err - } - } - - return nil -} - -// Close closes the connection. -func (c *Conn) Close() error { - c.close() - - // wait until the reader/writer goroutines have exited before proceeding. - // this is to prevent a race between calling Close() and a reader/writer - // goroutine calling close() due to a terminal error. - <-c.txDone - <-c.rxDone - - var connErr *ConnError - if errors.As(c.doneErr, &connErr) && connErr.RemoteErr == nil && connErr.inner == nil { - // an empty ConnectionError means the connection was closed by the caller - return nil - } - - // there was an error during shut-down or connReader/connWriter - // experienced a terminal error - return c.doneErr -} - -// close is called once, either from Close() or when connReader/connWriter exits -func (c *Conn) close() { - c.closeOnce.Do(func() { - defer close(c.done) - - close(c.rxtxExit) - - // wait for writing to stop, allows it to send the final close frame - <-c.txDone - - closeErr := c.net.Close() - - // check rxDone after closing net, otherwise may block - // for up to c.idleTimeout - <-c.rxDone - - if errors.Is(c.rxErr, net.ErrClosed) { - // this is the expected error when the connection is closed, swallow it - c.rxErr = nil - } - - if c.txErr == nil && c.rxErr == nil && closeErr == nil { - // if there are no errors, it means user initiated close() and we shut down cleanly - c.doneErr = &ConnError{} - } else if amqpErr, ok := c.rxErr.(*Error); ok { - // we experienced a peer-initiated close that contained an Error. return it - c.doneErr = &ConnError{RemoteErr: amqpErr} - } else if c.txErr != nil { - // c.txErr is already wrapped in a ConnError - c.doneErr = c.txErr - } else if c.rxErr != nil { - c.doneErr = &ConnError{inner: c.rxErr} - } else { - c.doneErr = &ConnError{inner: closeErr} - } - }) -} - -// closeDuringStart is a special close to be used only during startup (i.e. c.start() and any of its children) -func (c *Conn) closeDuringStart() { - c.closeOnce.Do(func() { - c.net.Close() - }) -} - -// NewSession starts a new session on the connection. -// - ctx controls waiting for the peer to acknowledge the session -// - opts contains optional values, pass nil to accept the defaults -// -// If the context's deadline expires or is cancelled before the operation -// completes, an error is returned. If the Session was successfully -// created, it will be cleaned up in future calls to NewSession. -func (c *Conn) NewSession(ctx context.Context, opts *SessionOptions) (*Session, error) { - // clean up any abandoned sessions first - if err := c.freeAbandonedSessions(ctx); err != nil { - return nil, err - } - - session, err := c.newSession(opts) - if err != nil { - return nil, err - } - - if err := session.begin(ctx); err != nil { - c.abandonSession(session) - return nil, err - } - - return session, nil -} - -func (c *Conn) freeAbandonedSessions(ctx context.Context) error { - c.abandonedSessionsMu.Lock() - defer c.abandonedSessionsMu.Unlock() - - debug.Log(3, "TX (Conn %p): cleaning up %d abandoned sessions", c, len(c.abandonedSessions)) - - for _, s := range c.abandonedSessions { - fr := frames.PerformEnd{} - if err := s.txFrameAndWait(ctx, &fr); err != nil { - return err - } - } - - c.abandonedSessions = nil - return nil -} - -func (c *Conn) newSession(opts *SessionOptions) (*Session, error) { - c.sessionsByChannelMu.Lock() - defer c.sessionsByChannelMu.Unlock() - - // create the next session to allocate - // note that channel always start at 0 - channel, ok := c.channels.Next() - if !ok { - if err := c.Close(); err != nil { - return nil, err - } - return nil, &ConnError{inner: fmt.Errorf("reached connection channel max (%d)", c.channelMax)} - } - session := newSession(c, uint16(channel), opts) - c.sessionsByChannel[session.channel] = session - - return session, nil -} - -func (c *Conn) deleteSession(s *Session) { - c.sessionsByChannelMu.Lock() - defer c.sessionsByChannelMu.Unlock() - - delete(c.sessionsByChannel, s.channel) - c.channels.Remove(uint32(s.channel)) -} - -func (c *Conn) abandonSession(s *Session) { - c.abandonedSessionsMu.Lock() - defer c.abandonedSessionsMu.Unlock() - c.abandonedSessions = append(c.abandonedSessions, s) -} - -// connReader reads from the net.Conn, decodes frames, and either handles -// them here as appropriate or sends them to the session.rx channel. -func (c *Conn) connReader() { - defer func() { - close(c.rxDone) - c.close() - }() - - var sessionsByRemoteChannel = make(map[uint16]*Session) - var err error - for { - if err != nil { - debug.Log(1, "RX (connReader %p): terminal error: %v", c, err) - c.rxErr = err - return - } - - var fr frames.Frame - fr, err = c.readFrame() - if err != nil { - continue - } - - debug.Log(1, "RX (connReader %p): %s", c, fr) - - var ( - session *Session - ok bool - ) - - switch body := fr.Body.(type) { - // Server initiated close. - case *frames.PerformClose: - // connWriter will send the close performative ack on its way out. - // it's a SHOULD though, not a MUST. - if body.Error == nil { - return - } - err = body.Error - continue - - // RemoteChannel should be used when frame is Begin - case *frames.PerformBegin: - if body.RemoteChannel == nil { - // since we only support remotely-initiated sessions, this is an error - // TODO: it would be ideal to not have this kill the connection - err = fmt.Errorf("%T: nil RemoteChannel", fr.Body) - continue - } - c.sessionsByChannelMu.RLock() - session, ok = c.sessionsByChannel[*body.RemoteChannel] - c.sessionsByChannelMu.RUnlock() - if !ok { - // this can happen if NewSession() exits due to the context expiring/cancelled - // before the begin ack is received. - err = fmt.Errorf("unexpected remote channel number %d", *body.RemoteChannel) - continue - } - - session.remoteChannel = fr.Channel - sessionsByRemoteChannel[fr.Channel] = session - - case *frames.PerformEnd: - session, ok = sessionsByRemoteChannel[fr.Channel] - if !ok { - err = fmt.Errorf("%T: didn't find channel %d in sessionsByRemoteChannel (PerformEnd)", fr.Body, fr.Channel) - continue - } - // we MUST remove the remote channel from our map as soon as we receive - // the ack (i.e. before passing it on to the session mux) on the session - // ending since the numbers are recycled. - delete(sessionsByRemoteChannel, fr.Channel) - c.deleteSession(session) - - default: - // pass on performative to the correct session - session, ok = sessionsByRemoteChannel[fr.Channel] - if !ok { - err = fmt.Errorf("%T: didn't find channel %d in sessionsByRemoteChannel", fr.Body, fr.Channel) - continue - } - } - - q := session.rxQ.Acquire() - q.Enqueue(fr.Body) - session.rxQ.Release(q) - debug.Log(2, "RX (connReader %p): mux frame to Session (%p): %s", c, session, fr) - } -} - -// readFrame reads a complete frame from c.net. -// it assumes that any read deadline has already been applied. -// used externally by SASL only. -func (c *Conn) readFrame() (frames.Frame, error) { - switch { - // Cheaply reuse free buffer space when fully read. - case c.rxBuf.Len() == 0: - c.rxBuf.Reset() - - // Prevent excessive/unbounded growth by shifting data to beginning of buffer. - case int64(c.rxBuf.Size()) > int64(c.maxFrameSize): - c.rxBuf.Reclaim() - } - - var ( - currentHeader frames.Header // keep track of the current header, for frames split across multiple TCP packets - frameInProgress bool // true if in the middle of receiving data for currentHeader - ) - - for { - // need to read more if buf doesn't contain the complete frame - // or there's not enough in buf to parse the header - if frameInProgress || c.rxBuf.Len() < frames.HeaderSize { - // we MUST reset the idle timeout before each read from net.Conn - if c.idleTimeout > 0 { - _ = c.net.SetReadDeadline(time.Now().Add(c.idleTimeout)) - } - err := c.rxBuf.ReadFromOnce(c.net) - if err != nil { - return frames.Frame{}, err - } - } - - // read more if buf doesn't contain enough to parse the header - if c.rxBuf.Len() < frames.HeaderSize { - continue - } - - // parse the header if a frame isn't in progress - if !frameInProgress { - var err error - currentHeader, err = frames.ParseHeader(&c.rxBuf) - if err != nil { - return frames.Frame{}, err - } - frameInProgress = true - } - - // check size is reasonable - if currentHeader.Size > math.MaxInt32 { // make max size configurable - return frames.Frame{}, errors.New("payload too large") - } - - bodySize := int64(currentHeader.Size - frames.HeaderSize) - - // the full frame hasn't been received, keep reading - if int64(c.rxBuf.Len()) < bodySize { - continue - } - frameInProgress = false - - // check if body is empty (keepalive) - if bodySize == 0 { - debug.Log(3, "RX (connReader %p): received keep-alive frame", c) - continue - } - - // parse the frame - b, ok := c.rxBuf.Next(bodySize) - if !ok { - return frames.Frame{}, fmt.Errorf("buffer EOF; requested bytes: %d, actual size: %d", bodySize, c.rxBuf.Len()) - } - - parsedBody, err := frames.ParseBody(buffer.New(b)) - if err != nil { - return frames.Frame{}, err - } - - return frames.Frame{Channel: currentHeader.Channel, Body: parsedBody}, nil - } -} - -// frameEnvelope is used when sending a frame to connWriter to be written to net.Conn -type frameEnvelope struct { - Ctx context.Context - Frame frames.Frame - - // optional channel that is closed on successful write to net.Conn or contains the write error - // NOTE: use a buffered channel of size 1 when populating - Sent chan error -} - -func (c *Conn) connWriter() { - defer func() { - close(c.txDone) - c.close() - }() - - var ( - // keepalives are sent at a rate of 1/2 idle timeout - keepaliveInterval = c.peerIdleTimeout / 2 - // 0 disables keepalives - keepalivesEnabled = keepaliveInterval > 0 - // set if enable, nil if not; nil channels block forever - keepalive <-chan time.Time - ) - - if keepalivesEnabled { - ticker := time.NewTicker(keepaliveInterval) - defer ticker.Stop() - keepalive = ticker.C - } - - var err error - for { - if err != nil { - debug.Log(1, "TX (connWriter %p): terminal error: %v", c, err) - c.txErr = err - return - } - - select { - // frame write request - case env := <-c.txFrame: - timeout, ctxErr := c.getWriteTimeout(env.Ctx) - if ctxErr != nil { - debug.Log(1, "TX (connWriter %p) deadline exceeded: %s", c, env.Frame) - if env.Sent != nil { - env.Sent <- ctxErr - } - continue - } - - debug.Log(1, "TX (connWriter %p) timeout %s: %s", c, timeout, env.Frame) - err = c.writeFrame(timeout, env.Frame) - if env.Sent != nil { - if err == nil { - close(env.Sent) - } else { - env.Sent <- err - } - } - - // keepalive timer - case <-keepalive: - debug.Log(3, "TX (connWriter %p): sending keep-alive frame", c) - _ = c.net.SetWriteDeadline(time.Now().Add(c.writeTimeout)) - if _, err = c.net.Write(keepaliveFrame); err != nil { - err = &ConnError{inner: err} - } - // It would be slightly more efficient in terms of network - // resources to reset the timer each time a frame is sent. - // However, keepalives are small (8 bytes) and the interval - // is usually on the order of minutes. It does not seem - // worth it to add extra operations in the write path to - // avoid. (To properly reset a timer it needs to be stopped, - // possibly drained, then reset.) - - // connection complete - case <-c.rxtxExit: - // send close performative. note that the spec says we - // SHOULD wait for the ack but we don't HAVE to, in order - // to be resilient to bad actors etc. so we just send - // the close performative and exit. - fr := frames.Frame{ - Type: frames.TypeAMQP, - Body: &frames.PerformClose{}, - } - debug.Log(1, "TX (connWriter %p): %s", c, fr) - c.txErr = c.writeFrame(c.writeTimeout, fr) - return - } - } -} - -// writeFrame writes a frame to the network. -// used externally by SASL only. -// - timeout - the write deadline to set. zero means no deadline -// -// errors are wrapped in a ConnError as they can be returned to outside callers. -func (c *Conn) writeFrame(timeout time.Duration, fr frames.Frame) error { - // writeFrame into txBuf - c.txBuf.Reset() - err := frames.Write(&c.txBuf, fr) - if err != nil { - return &ConnError{inner: err} - } - - // validate the frame isn't exceeding peer's max frame size - requiredFrameSize := c.txBuf.Len() - if uint64(requiredFrameSize) > uint64(c.peerMaxFrameSize) { - return &ConnError{inner: fmt.Errorf("%T frame size %d larger than peer's max frame size %d", fr, requiredFrameSize, c.peerMaxFrameSize)} - } - - if timeout == 0 { - _ = c.net.SetWriteDeadline(time.Time{}) - } else if timeout > 0 { - _ = c.net.SetWriteDeadline(time.Now().Add(timeout)) - } - - // write to network - n, err := c.net.Write(c.txBuf.Bytes()) - if l := c.txBuf.Len(); n > 0 && n < l && err != nil { - debug.Log(1, "TX (writeFrame %p): wrote %d bytes less than len %d: %v", c, n, l, err) - } - if err != nil { - err = &ConnError{inner: err} - } - return err -} - -// writeProtoHeader writes an AMQP protocol header to the -// network -func (c *Conn) writeProtoHeader(pID protoID) error { - _, err := c.net.Write([]byte{'A', 'M', 'Q', 'P', byte(pID), 1, 0, 0}) - return err -} - -// keepaliveFrame is an AMQP frame with no body, used for keepalives -var keepaliveFrame = []byte{0x00, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00} - -// SendFrame is used by sessions and links to send frames across the network. -// - ctx is used to provide the write deadline -// - fr is the frame to write to net.Conn -// - sent is the optional channel that will contain the error if the write fails -func (c *Conn) sendFrame(ctx context.Context, fr frames.Frame, sent chan error) { - select { - case c.txFrame <- frameEnvelope{Ctx: ctx, Frame: fr, Sent: sent}: - debug.Log(2, "TX (Conn %p): mux frame to connWriter: %s", c, fr) - case <-c.done: - if sent != nil { - sent <- c.doneErr - } - } -} - -// stateFunc is a state in a state machine. -// -// The state is advanced by returning the next state. -// The state machine concludes when nil is returned. -type stateFunc func(context.Context) (stateFunc, error) - -// negotiateProto determines which proto to negotiate next. -// used externally by SASL only. -func (c *Conn) negotiateProto(ctx context.Context) (stateFunc, error) { - // in the order each must be negotiated - switch { - case c.tlsNegotiation && !c.tlsComplete: - return c.exchangeProtoHeader(protoTLS) - case c.saslHandlers != nil && !c.saslComplete: - return c.exchangeProtoHeader(protoSASL) - default: - return c.exchangeProtoHeader(protoAMQP) - } -} - -type protoID uint8 - -// protocol IDs received in protoHeaders -const ( - protoAMQP protoID = 0x0 - protoTLS protoID = 0x2 - protoSASL protoID = 0x3 -) - -// exchangeProtoHeader performs the round trip exchange of protocol -// headers, validation, and returns the protoID specific next state. -func (c *Conn) exchangeProtoHeader(pID protoID) (stateFunc, error) { - // write the proto header - if err := c.writeProtoHeader(pID); err != nil { - return nil, err - } - - // read response header - p, err := c.readProtoHeader() - if err != nil { - return nil, err - } - - if pID != p.ProtoID { - return nil, fmt.Errorf("unexpected protocol header %#00x, expected %#00x", p.ProtoID, pID) - } - - // go to the proto specific state - switch pID { - case protoAMQP: - return c.openAMQP, nil - case protoTLS: - return c.startTLS, nil - case protoSASL: - return c.negotiateSASL, nil - default: - return nil, fmt.Errorf("unknown protocol ID %#02x", p.ProtoID) - } -} - -// readProtoHeader reads a protocol header packet from c.rxProto. -func (c *Conn) readProtoHeader() (protoHeader, error) { - const protoHeaderSize = 8 - - // only read from the network once our buffer has been exhausted. - // TODO: this preserves existing behavior as some tests rely on this - // implementation detail (it lets you replay a stream of bytes). we - // might want to consider removing this and fixing the tests as the - // protocol doesn't actually work this way. - if c.rxBuf.Len() == 0 { - for { - err := c.rxBuf.ReadFromOnce(c.net) - if err != nil { - return protoHeader{}, err - } - - // read more if buf doesn't contain enough to parse the header - if c.rxBuf.Len() >= protoHeaderSize { - break - } - } - } - - buf, ok := c.rxBuf.Next(protoHeaderSize) - if !ok { - return protoHeader{}, errors.New("invalid protoHeader") - } - // bounds check hint to compiler; see golang.org/issue/14808 - _ = buf[protoHeaderSize-1] - - if !bytes.Equal(buf[:4], []byte{'A', 'M', 'Q', 'P'}) { - return protoHeader{}, fmt.Errorf("unexpected protocol %q", buf[:4]) - } - - p := protoHeader{ - ProtoID: protoID(buf[4]), - Major: buf[5], - Minor: buf[6], - Revision: buf[7], - } - - if p.Major != 1 || p.Minor != 0 || p.Revision != 0 { - return protoHeader{}, fmt.Errorf("unexpected protocol version %d.%d.%d", p.Major, p.Minor, p.Revision) - } - - return p, nil -} - -// startTLS wraps the conn with TLS and returns to Client.negotiateProto -func (c *Conn) startTLS(ctx context.Context) (stateFunc, error) { - c.initTLSConfig() - - _ = c.net.SetReadDeadline(time.Time{}) // clear timeout - - // wrap existing net.Conn and perform TLS handshake - tlsConn := tls.Client(c.net, c.tlsConfig) - if err := tlsConn.HandshakeContext(ctx); err != nil { - return nil, err - } - - // swap net.Conn - c.net = tlsConn - c.tlsComplete = true - - // go to next protocol - return c.negotiateProto, nil -} - -// openAMQP round trips the AMQP open performative -func (c *Conn) openAMQP(ctx context.Context) (stateFunc, error) { - // send open frame - open := &frames.PerformOpen{ - ContainerID: c.containerID, - Hostname: c.hostname, - MaxFrameSize: c.maxFrameSize, - ChannelMax: c.channelMax, - IdleTimeout: c.idleTimeout / 2, // per spec, advertise half our idle timeout - Properties: c.properties, - } - fr := frames.Frame{ - Type: frames.TypeAMQP, - Body: open, - Channel: 0, - } - debug.Log(1, "TX (openAMQP %p): %s", c, fr) - timeout, err := c.getWriteTimeout(ctx) - if err != nil { - return nil, err - } - if err = c.writeFrame(timeout, fr); err != nil { - return nil, err - } - - // get the response - fr, err = c.readSingleFrame() - if err != nil { - return nil, err - } - debug.Log(1, "RX (openAMQP %p): %s", c, fr) - o, ok := fr.Body.(*frames.PerformOpen) - if !ok { - return nil, fmt.Errorf("openAMQP: unexpected frame type %T", fr.Body) - } - - // update peer settings - if o.MaxFrameSize > 0 { - c.peerMaxFrameSize = o.MaxFrameSize - } - if o.IdleTimeout > 0 { - // TODO: reject very small idle timeouts - c.peerIdleTimeout = o.IdleTimeout - } - if o.ChannelMax < c.channelMax { - c.channelMax = o.ChannelMax - } - - // connection established, exit state machine - return nil, nil -} - -// negotiateSASL returns the SASL handler for the first matched -// mechanism specified by the server -func (c *Conn) negotiateSASL(context.Context) (stateFunc, error) { - // read mechanisms frame - fr, err := c.readSingleFrame() - if err != nil { - return nil, err - } - debug.Log(1, "RX (negotiateSASL %p): %s", c, fr) - sm, ok := fr.Body.(*frames.SASLMechanisms) - if !ok { - return nil, fmt.Errorf("negotiateSASL: unexpected frame type %T", fr.Body) - } - - // return first match in c.saslHandlers based on order received - for _, mech := range sm.Mechanisms { - if state, ok := c.saslHandlers[mech]; ok { - return state, nil - } - } - - // no match - return nil, fmt.Errorf("no supported auth mechanism (%v)", sm.Mechanisms) // TODO: send "auth not supported" frame? -} - -// saslOutcome processes the SASL outcome frame and return Client.negotiateProto -// on success. -// -// SASL handlers return this stateFunc when the mechanism specific negotiation -// has completed. -// used externally by SASL only. -func (c *Conn) saslOutcome(context.Context) (stateFunc, error) { - // read outcome frame - fr, err := c.readSingleFrame() - if err != nil { - return nil, err - } - debug.Log(1, "RX (saslOutcome %p): %s", c, fr) - so, ok := fr.Body.(*frames.SASLOutcome) - if !ok { - return nil, fmt.Errorf("saslOutcome: unexpected frame type %T", fr.Body) - } - - // check if auth succeeded - if so.Code != encoding.CodeSASLOK { - return nil, fmt.Errorf("SASL PLAIN auth failed with code %#00x: %s", so.Code, so.AdditionalData) // implement Stringer for so.Code - } - - // return to c.negotiateProto - c.saslComplete = true - return c.negotiateProto, nil -} - -// readSingleFrame is used during connection establishment to read a single frame. -// -// After setup, conn.connReader handles incoming frames. -func (c *Conn) readSingleFrame() (frames.Frame, error) { - fr, err := c.readFrame() - if err != nil { - return frames.Frame{}, err - } - - return fr, nil -} - -// getWriteTimeout returns the timeout as calculated from the context's deadline -// or the default write timeout if the context has no deadline. -// if the context has timed out or was cancelled, an error is returned. -func (c *Conn) getWriteTimeout(ctx context.Context) (time.Duration, error) { - if deadline, ok := ctx.Deadline(); ok { - until := time.Until(deadline) - if until <= 0 { - return 0, context.DeadlineExceeded - } - return until, nil - } - return c.writeTimeout, nil -} - -type protoHeader struct { - ProtoID protoID - Major uint8 - Minor uint8 - Revision uint8 -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/const.go b/sdk/messaging/azeventhubs/internal/go-amqp/const.go deleted file mode 100644 index fee0b5041525..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/const.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding" - -// Sender Settlement Modes -const ( - // Sender will send all deliveries initially unsettled to the receiver. - SenderSettleModeUnsettled SenderSettleMode = encoding.SenderSettleModeUnsettled - - // Sender will send all deliveries settled to the receiver. - SenderSettleModeSettled SenderSettleMode = encoding.SenderSettleModeSettled - - // Sender MAY send a mixture of settled and unsettled deliveries to the receiver. - SenderSettleModeMixed SenderSettleMode = encoding.SenderSettleModeMixed -) - -// SenderSettleMode specifies how the sender will settle messages. -type SenderSettleMode = encoding.SenderSettleMode - -func senderSettleModeValue(m *SenderSettleMode) SenderSettleMode { - if m == nil { - return SenderSettleModeMixed - } - return *m -} - -// Receiver Settlement Modes -const ( - // Receiver is the first to consider the message as settled. - // Once the corresponding disposition frame is sent, the message - // is considered to be settled. - ReceiverSettleModeFirst ReceiverSettleMode = encoding.ReceiverSettleModeFirst - - // Receiver is the second to consider the message as settled. - // Once the corresponding disposition frame is sent, the settlement - // is considered in-flight and the message will not be considered as - // settled until the sender replies acknowledging the settlement. - ReceiverSettleModeSecond ReceiverSettleMode = encoding.ReceiverSettleModeSecond -) - -// ReceiverSettleMode specifies how the receiver will settle messages. -type ReceiverSettleMode = encoding.ReceiverSettleMode - -func receiverSettleModeValue(m *ReceiverSettleMode) ReceiverSettleMode { - if m == nil { - return ReceiverSettleModeFirst - } - return *m -} - -// Durability Policies -const ( - // No terminus state is retained durably. - DurabilityNone Durability = encoding.DurabilityNone - - // Only the existence and configuration of the terminus is - // retained durably. - DurabilityConfiguration Durability = encoding.DurabilityConfiguration - - // In addition to the existence and configuration of the - // terminus, the unsettled state for durable messages is - // retained durably. - DurabilityUnsettledState Durability = encoding.DurabilityUnsettledState -) - -// Durability specifies the durability of a link. -type Durability = encoding.Durability - -// Expiry Policies -const ( - // The expiry timer starts when terminus is detached. - ExpiryPolicyLinkDetach ExpiryPolicy = encoding.ExpiryLinkDetach - - // The expiry timer starts when the most recently - // associated session is ended. - ExpiryPolicySessionEnd ExpiryPolicy = encoding.ExpirySessionEnd - - // The expiry timer starts when most recently associated - // connection is closed. - ExpiryPolicyConnectionClose ExpiryPolicy = encoding.ExpiryConnectionClose - - // The terminus never expires. - ExpiryPolicyNever ExpiryPolicy = encoding.ExpiryNever -) - -// ExpiryPolicy specifies when the expiry timer of a terminus -// starts counting down from the timeout value. -// -// If the link is subsequently re-attached before the terminus is expired, -// then the count down is aborted. If the conditions for the -// terminus-expiry-policy are subsequently re-met, the expiry timer restarts -// from its originally configured timeout value. -type ExpiryPolicy = encoding.ExpiryPolicy diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/creditor.go b/sdk/messaging/azeventhubs/internal/go-amqp/creditor.go deleted file mode 100644 index 184702bca7d2..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/creditor.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) Microsoft Corporation - -package amqp - -import ( - "context" - "errors" - "sync" -) - -type creditor struct { - mu sync.Mutex - - // future values for the next flow frame. - pendingDrain bool - creditsToAdd uint32 - - // drained is set when a drain is active and we're waiting - // for the corresponding flow from the remote. - drained chan struct{} -} - -var ( - errLinkDraining = errors.New("link is currently draining, no credits can be added") - errAlreadyDraining = errors.New("drain already in process") -) - -// EndDrain ends the current drain, unblocking any active Drain calls. -func (mc *creditor) EndDrain() { - mc.mu.Lock() - defer mc.mu.Unlock() - - if mc.drained != nil { - close(mc.drained) - mc.drained = nil - } -} - -// FlowBits gets gets the proper values for the next flow frame -// and resets the internal state. -// Returns: -// -// (drain: true, credits: 0) if a flow is needed (drain) -// (drain: false, credits > 0) if a flow is needed (issue credit) -// (drain: false, credits == 0) if no flow needed. -func (mc *creditor) FlowBits(currentCredits uint32) (bool, uint32) { - mc.mu.Lock() - defer mc.mu.Unlock() - - drain := mc.pendingDrain - var credits uint32 - - if mc.pendingDrain { - // only send one drain request - mc.pendingDrain = false - } - - // either: - // drain is true (ie, we're going to send a drain frame, and the credits for it should be 0) - // mc.creditsToAdd == 0 (no flow frame needed, no new credits are being issued) - if drain || mc.creditsToAdd == 0 { - credits = 0 - } else { - credits = mc.creditsToAdd + currentCredits - } - - mc.creditsToAdd = 0 - - return drain, credits -} - -// Drain initiates a drain and blocks until EndDrain is called. -// If the context's deadline expires or is cancelled before the operation -// completes, the drain might not have happened. -func (mc *creditor) Drain(ctx context.Context, r *Receiver) error { - mc.mu.Lock() - - if mc.drained != nil { - mc.mu.Unlock() - return errAlreadyDraining - } - - mc.drained = make(chan struct{}) - // use a local copy to avoid racing with EndDrain() - drained := mc.drained - mc.pendingDrain = true - - mc.mu.Unlock() - - // cause mux() to check our flow conditions. - select { - case r.receiverReady <- struct{}{}: - default: - } - - // send drain, wait for responding flow frame - select { - case <-drained: - return nil - case <-r.l.done: - return r.l.doneErr - case <-ctx.Done(): - return ctx.Err() - } -} - -// IssueCredit queues up additional credits to be requested at the next -// call of FlowBits() -func (mc *creditor) IssueCredit(credits uint32) error { - mc.mu.Lock() - defer mc.mu.Unlock() - - if mc.drained != nil { - return errLinkDraining - } - - mc.creditsToAdd += credits - return nil -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/errors.go b/sdk/messaging/azeventhubs/internal/go-amqp/errors.go deleted file mode 100644 index 515a7c36bca3..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/errors.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding" -) - -// ErrCond is an AMQP defined error condition. -// See http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-amqp-error for info on their meaning. -type ErrCond = encoding.ErrCond - -// Error Conditions -const ( - // AMQP Errors - ErrCondDecodeError ErrCond = "amqp:decode-error" - ErrCondFrameSizeTooSmall ErrCond = "amqp:frame-size-too-small" - ErrCondIllegalState ErrCond = "amqp:illegal-state" - ErrCondInternalError ErrCond = "amqp:internal-error" - ErrCondInvalidField ErrCond = "amqp:invalid-field" - ErrCondNotAllowed ErrCond = "amqp:not-allowed" - ErrCondNotFound ErrCond = "amqp:not-found" - ErrCondNotImplemented ErrCond = "amqp:not-implemented" - ErrCondPreconditionFailed ErrCond = "amqp:precondition-failed" - ErrCondResourceDeleted ErrCond = "amqp:resource-deleted" - ErrCondResourceLimitExceeded ErrCond = "amqp:resource-limit-exceeded" - ErrCondResourceLocked ErrCond = "amqp:resource-locked" - ErrCondUnauthorizedAccess ErrCond = "amqp:unauthorized-access" - - // Connection Errors - ErrCondConnectionForced ErrCond = "amqp:connection:forced" - ErrCondConnectionRedirect ErrCond = "amqp:connection:redirect" - ErrCondFramingError ErrCond = "amqp:connection:framing-error" - - // Session Errors - ErrCondErrantLink ErrCond = "amqp:session:errant-link" - ErrCondHandleInUse ErrCond = "amqp:session:handle-in-use" - ErrCondUnattachedHandle ErrCond = "amqp:session:unattached-handle" - ErrCondWindowViolation ErrCond = "amqp:session:window-violation" - - // Link Errors - ErrCondDetachForced ErrCond = "amqp:link:detach-forced" - ErrCondLinkRedirect ErrCond = "amqp:link:redirect" - ErrCondMessageSizeExceeded ErrCond = "amqp:link:message-size-exceeded" - ErrCondStolen ErrCond = "amqp:link:stolen" - ErrCondTransferLimitExceeded ErrCond = "amqp:link:transfer-limit-exceeded" -) - -// Error is an AMQP error. -type Error = encoding.Error - -// LinkError is returned by methods on Sender/Receiver when the link has closed. -type LinkError struct { - // RemoteErr contains any error information provided by the peer if the peer detached the link. - RemoteErr *Error - - inner error -} - -// Error implements the error interface for LinkError. -func (e *LinkError) Error() string { - if e.RemoteErr == nil && e.inner == nil { - return "amqp: link closed" - } else if e.RemoteErr != nil { - return e.RemoteErr.Error() - } - return e.inner.Error() -} - -// ConnError is returned by methods on Conn and propagated to Session and Senders/Receivers -// when the connection has been closed. -type ConnError struct { - // RemoteErr contains any error information provided by the peer if the peer closed the AMQP connection. - RemoteErr *Error - - inner error -} - -// Error implements the error interface for ConnectionError. -func (e *ConnError) Error() string { - if e.RemoteErr == nil && e.inner == nil { - return "amqp: connection closed" - } else if e.RemoteErr != nil { - return e.RemoteErr.Error() - } - return e.inner.Error() -} - -// SessionError is returned by methods on Session and propagated to Senders/Receivers -// when the session has been closed. -type SessionError struct { - // RemoteErr contains any error information provided by the peer if the peer closed the session. - RemoteErr *Error - - inner error -} - -// Error implements the error interface for SessionError. -func (e *SessionError) Error() string { - if e.RemoteErr == nil && e.inner == nil { - return "amqp: session closed" - } else if e.RemoteErr != nil { - return e.RemoteErr.Error() - } - return e.inner.Error() -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/internal/bitmap/bitmap.go b/sdk/messaging/azeventhubs/internal/go-amqp/internal/bitmap/bitmap.go deleted file mode 100644 index d4d682e9199e..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/internal/bitmap/bitmap.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package bitmap - -import ( - "math/bits" -) - -// bitmap is a lazily initialized bitmap -type Bitmap struct { - max uint32 - bits []uint64 -} - -func New(max uint32) *Bitmap { - return &Bitmap{max: max} -} - -// add sets n in the bitmap. -// -// bits will be expanded as needed. -// -// If n is greater than max, the call has no effect. -func (b *Bitmap) Add(n uint32) { - if n > b.max { - return - } - - var ( - idx = n / 64 - offset = n % 64 - ) - - if l := len(b.bits); int(idx) >= l { - b.bits = append(b.bits, make([]uint64, int(idx)-l+1)...) - } - - b.bits[idx] |= 1 << offset -} - -// remove clears n from the bitmap. -// -// If n is not set or greater than max the call has not effect. -func (b *Bitmap) Remove(n uint32) { - var ( - idx = n / 64 - offset = n % 64 - ) - - if int(idx) >= len(b.bits) { - return - } - - b.bits[idx] &= ^uint64(1 << offset) -} - -// next sets and returns the lowest unset bit in the bitmap. -// -// bits will be expanded if necessary. -// -// If there are no unset bits below max, the second return -// value will be false. -func (b *Bitmap) Next() (uint32, bool) { - // find the first unset bit - for i, v := range b.bits { - // skip if all bits are set - if v == ^uint64(0) { - continue - } - - var ( - offset = bits.TrailingZeros64(^v) // invert and count zeroes - next = uint32(i*64 + offset) - ) - - // check if in bounds - if next > b.max { - return next, false - } - - // set bit - b.bits[i] |= 1 << uint32(offset) - return next, true - } - - // no unset bits in the current slice, - // check if the full range has been allocated - if uint64(len(b.bits)*64) > uint64(b.max) { - return 0, false - } - - // full range not allocated, append entry with first - // bit set - b.bits = append(b.bits, 1) - - // return the value of the first bit - return uint32(len(b.bits)-1) * 64, true -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer/buffer.go b/sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer/buffer.go deleted file mode 100644 index b82e5fab76a6..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer/buffer.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package buffer - -import ( - "encoding/binary" - "io" -) - -// buffer is similar to bytes.Buffer but specialized for this package -type Buffer struct { - b []byte - i int -} - -func New(b []byte) *Buffer { - return &Buffer{b: b} -} - -func (b *Buffer) Next(n int64) ([]byte, bool) { - if b.readCheck(n) { - buf := b.b[b.i:len(b.b)] - b.i = len(b.b) - return buf, false - } - - buf := b.b[b.i : b.i+int(n)] - b.i += int(n) - return buf, true -} - -func (b *Buffer) Skip(n int) { - b.i += n -} - -func (b *Buffer) Reset() { - b.b = b.b[:0] - b.i = 0 -} - -// reclaim shifts used buffer space to the beginning of the -// underlying slice. -func (b *Buffer) Reclaim() { - l := b.Len() - copy(b.b[:l], b.b[b.i:]) - b.b = b.b[:l] - b.i = 0 -} - -func (b *Buffer) readCheck(n int64) bool { - return int64(b.i)+n > int64(len(b.b)) -} - -func (b *Buffer) ReadByte() (byte, error) { - if b.readCheck(1) { - return 0, io.EOF - } - - byte_ := b.b[b.i] - b.i++ - return byte_, nil -} - -func (b *Buffer) PeekByte() (byte, error) { - if b.readCheck(1) { - return 0, io.EOF - } - - return b.b[b.i], nil -} - -func (b *Buffer) ReadUint16() (uint16, error) { - if b.readCheck(2) { - return 0, io.EOF - } - - n := binary.BigEndian.Uint16(b.b[b.i:]) - b.i += 2 - return n, nil -} - -func (b *Buffer) ReadUint32() (uint32, error) { - if b.readCheck(4) { - return 0, io.EOF - } - - n := binary.BigEndian.Uint32(b.b[b.i:]) - b.i += 4 - return n, nil -} - -func (b *Buffer) ReadUint64() (uint64, error) { - if b.readCheck(8) { - return 0, io.EOF - } - - n := binary.BigEndian.Uint64(b.b[b.i : b.i+8]) - b.i += 8 - return n, nil -} - -func (b *Buffer) ReadFromOnce(r io.Reader) error { - const minRead = 512 - - l := len(b.b) - if cap(b.b)-l < minRead { - total := l * 2 - if total == 0 { - total = minRead - } - new := make([]byte, l, total) - copy(new, b.b) - b.b = new - } - - n, err := r.Read(b.b[l:cap(b.b)]) - b.b = b.b[:l+n] - return err -} - -func (b *Buffer) Append(p []byte) { - b.b = append(b.b, p...) -} - -func (b *Buffer) AppendByte(bb byte) { - b.b = append(b.b, bb) -} - -func (b *Buffer) AppendString(s string) { - b.b = append(b.b, s...) -} - -func (b *Buffer) Len() int { - return len(b.b) - b.i -} - -func (b *Buffer) Size() int { - return b.i -} - -func (b *Buffer) Bytes() []byte { - return b.b[b.i:] -} - -func (b *Buffer) Detach() []byte { - temp := b.b - b.b = nil - b.i = 0 - return temp -} - -func (b *Buffer) AppendUint16(n uint16) { - b.b = append(b.b, - byte(n>>8), - byte(n), - ) -} - -func (b *Buffer) AppendUint32(n uint32) { - b.b = append(b.b, - byte(n>>24), - byte(n>>16), - byte(n>>8), - byte(n), - ) -} - -func (b *Buffer) AppendUint64(n uint64) { - b.b = append(b.b, - byte(n>>56), - byte(n>>48), - byte(n>>40), - byte(n>>32), - byte(n>>24), - byte(n>>16), - byte(n>>8), - byte(n), - ) -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/internal/debug/debug.go b/sdk/messaging/azeventhubs/internal/go-amqp/internal/debug/debug.go deleted file mode 100644 index 3e6821e1f723..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/internal/debug/debug.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -//go:build !debug -// +build !debug - -package debug - -// dummy functions used when debugging is not enabled - -// Log writes the formatted string to stderr. -// Level indicates the verbosity of the messages to log. -// The greater the value, the more verbose messages will be logged. -func Log(_ int, _ string, _ ...any) {} - -// Assert panics if the specified condition is false. -func Assert(bool) {} - -// Assert panics with the provided message if the specified condition is false. -func Assertf(bool, string, ...any) {} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/internal/debug/debug_debug.go b/sdk/messaging/azeventhubs/internal/go-amqp/internal/debug/debug_debug.go deleted file mode 100644 index 96d53768a5c9..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/internal/debug/debug_debug.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -//go:build debug -// +build debug - -package debug - -import ( - "fmt" - "log" - "os" - "strconv" -) - -var ( - debugLevel = 1 - logger = log.New(os.Stderr, "", log.Lmicroseconds) -) - -func init() { - level, err := strconv.Atoi(os.Getenv("DEBUG_LEVEL")) - if err != nil { - return - } - - debugLevel = level -} - -// Log writes the formatted string to stderr. -// Level indicates the verbosity of the messages to log. -// The greater the value, the more verbose messages will be logged. -func Log(level int, format string, v ...any) { - if level <= debugLevel { - logger.Printf(format, v...) - } -} - -// Assert panics if the specified condition is false. -func Assert(condition bool) { - if !condition { - panic("assertion failed!") - } -} - -// Assert panics with the provided message if the specified condition is false. -func Assertf(condition bool, msg string, v ...any) { - if !condition { - panic(fmt.Sprintf(msg, v...)) - } -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding/decode.go b/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding/decode.go deleted file mode 100644 index 1de2be5f70a9..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding/decode.go +++ /dev/null @@ -1,1150 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package encoding - -import ( - "encoding/binary" - "errors" - "fmt" - "math" - "reflect" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer" -) - -// unmarshaler is fulfilled by types that can unmarshal -// themselves from AMQP data. -type unmarshaler interface { - Unmarshal(r *buffer.Buffer) error -} - -// unmarshal decodes AMQP encoded data into i. -// -// The decoding method is based on the type of i. -// -// If i implements unmarshaler, i.Unmarshal() will be called. -// -// Pointers to primitive types will be decoded via the appropriate read[Type] function. -// -// If i is a pointer to a pointer (**Type), it will be dereferenced and a new instance -// of (*Type) is allocated via reflection. -// -// Common map types (map[string]string, map[Symbol]any, and -// map[any]any), will be decoded via conversion to the mapStringAny, -// mapSymbolAny, and mapAnyAny types. -func Unmarshal(r *buffer.Buffer, i any) error { - if tryReadNull(r) { - return nil - } - - switch t := i.(type) { - case *int: - val, err := readInt(r) - if err != nil { - return err - } - *t = val - case *int8: - val, err := readSbyte(r) - if err != nil { - return err - } - *t = val - case *int16: - val, err := readShort(r) - if err != nil { - return err - } - *t = val - case *int32: - val, err := readInt32(r) - if err != nil { - return err - } - *t = val - case *int64: - val, err := readLong(r) - if err != nil { - return err - } - *t = val - case *uint64: - val, err := readUlong(r) - if err != nil { - return err - } - *t = val - case *uint32: - val, err := readUint32(r) - if err != nil { - return err - } - *t = val - case **uint32: // fastpath for uint32 pointer fields - val, err := readUint32(r) - if err != nil { - return err - } - *t = &val - case *uint16: - val, err := readUshort(r) - if err != nil { - return err - } - *t = val - case *uint8: - val, err := ReadUbyte(r) - if err != nil { - return err - } - *t = val - case *float32: - val, err := readFloat(r) - if err != nil { - return err - } - *t = val - case *float64: - val, err := readDouble(r) - if err != nil { - return err - } - *t = val - case *string: - val, err := ReadString(r) - if err != nil { - return err - } - *t = val - case *Symbol: - s, err := ReadString(r) - if err != nil { - return err - } - *t = Symbol(s) - case *[]byte: - val, err := readBinary(r) - if err != nil { - return err - } - *t = val - case *bool: - b, err := readBool(r) - if err != nil { - return err - } - *t = b - case *time.Time: - ts, err := readTimestamp(r) - if err != nil { - return err - } - *t = ts - case *[]int8: - return (*arrayInt8)(t).Unmarshal(r) - case *[]uint16: - return (*arrayUint16)(t).Unmarshal(r) - case *[]int16: - return (*arrayInt16)(t).Unmarshal(r) - case *[]uint32: - return (*arrayUint32)(t).Unmarshal(r) - case *[]int32: - return (*arrayInt32)(t).Unmarshal(r) - case *[]uint64: - return (*arrayUint64)(t).Unmarshal(r) - case *[]int64: - return (*arrayInt64)(t).Unmarshal(r) - case *[]float32: - return (*arrayFloat)(t).Unmarshal(r) - case *[]float64: - return (*arrayDouble)(t).Unmarshal(r) - case *[]bool: - return (*arrayBool)(t).Unmarshal(r) - case *[]string: - return (*arrayString)(t).Unmarshal(r) - case *[]Symbol: - return (*arraySymbol)(t).Unmarshal(r) - case *[][]byte: - return (*arrayBinary)(t).Unmarshal(r) - case *[]time.Time: - return (*arrayTimestamp)(t).Unmarshal(r) - case *[]UUID: - return (*arrayUUID)(t).Unmarshal(r) - case *[]any: - return (*list)(t).Unmarshal(r) - case *map[any]any: - return (*mapAnyAny)(t).Unmarshal(r) - case *map[string]any: - return (*mapStringAny)(t).Unmarshal(r) - case *map[Symbol]any: - return (*mapSymbolAny)(t).Unmarshal(r) - case *DeliveryState: - type_, _, err := PeekMessageType(r.Bytes()) - if err != nil { - return err - } - - switch AMQPType(type_) { - case TypeCodeStateAccepted: - *t = new(StateAccepted) - case TypeCodeStateModified: - *t = new(StateModified) - case TypeCodeStateReceived: - *t = new(StateReceived) - case TypeCodeStateRejected: - *t = new(StateRejected) - case TypeCodeStateReleased: - *t = new(StateReleased) - default: - return fmt.Errorf("unexpected type %d for deliveryState", type_) - } - return Unmarshal(r, *t) - - case *any: - v, err := ReadAny(r) - if err != nil { - return err - } - *t = v - - case unmarshaler: - return t.Unmarshal(r) - default: - // handle **T - v := reflect.Indirect(reflect.ValueOf(i)) - - // can't unmarshal into a non-pointer - if v.Kind() != reflect.Ptr { - return fmt.Errorf("unable to unmarshal %T", i) - } - - // if nil pointer, allocate a new value to - // unmarshal into - if v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - - return Unmarshal(r, v.Interface()) - } - return nil -} - -// unmarshalComposite is a helper for use in a composite's unmarshal() function. -// -// The composite from r will be unmarshaled into zero or more fields. An error -// will be returned if typ does not match the decoded type. -func UnmarshalComposite(r *buffer.Buffer, type_ AMQPType, fields ...UnmarshalField) error { - cType, numFields, err := readCompositeHeader(r) - if err != nil { - return err - } - - // check type matches expectation - if cType != type_ { - return fmt.Errorf("invalid header %#0x for %#0x", cType, type_) - } - - // Validate the field count is less than or equal to the number of fields - // provided. Fields may be omitted by the sender if they are not set. - if numFields > int64(len(fields)) { - return fmt.Errorf("invalid field count %d for %#0x", numFields, type_) - } - - for i, field := range fields[:numFields] { - // If the field is null and handleNull is set, call it. - if tryReadNull(r) { - if field.HandleNull != nil { - err = field.HandleNull() - if err != nil { - return err - } - } - continue - } - - // Unmarshal each of the received fields. - err = Unmarshal(r, field.Field) - if err != nil { - return fmt.Errorf("unmarshaling field %d: %v", i, err) - } - } - - // check and call handleNull for the remaining fields - for _, field := range fields[numFields:] { - if field.HandleNull != nil { - err = field.HandleNull() - if err != nil { - return err - } - } - } - - return nil -} - -// unmarshalField is a struct that contains a field to be unmarshaled into. -// -// An optional nullHandler can be set. If the composite field being unmarshaled -// is null and handleNull is not nil, nullHandler will be called. -type UnmarshalField struct { - Field any - HandleNull NullHandler -} - -// nullHandler is a function to be called when a composite's field -// is null. -type NullHandler func() error - -func readType(r *buffer.Buffer) (AMQPType, error) { - n, err := r.ReadByte() - return AMQPType(n), err -} - -func peekType(r *buffer.Buffer) (AMQPType, error) { - n, err := r.PeekByte() - return AMQPType(n), err -} - -// readCompositeHeader reads and consumes the composite header from r. -func readCompositeHeader(r *buffer.Buffer) (_ AMQPType, fields int64, _ error) { - type_, err := readType(r) - if err != nil { - return 0, 0, err - } - - // compsites always start with 0x0 - if type_ != 0 { - return 0, 0, fmt.Errorf("invalid composite header %#02x", type_) - } - - // next, the composite type is encoded as an AMQP uint8 - v, err := readUlong(r) - if err != nil { - return 0, 0, err - } - - // fields are represented as a list - fields, err = readListHeader(r) - - return AMQPType(v), fields, err -} - -func readListHeader(r *buffer.Buffer) (length int64, _ error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - listLength := r.Len() - - switch type_ { - case TypeCodeList0: - return 0, nil - case TypeCodeList8: - buf, ok := r.Next(2) - if !ok { - return 0, errors.New("invalid length") - } - _ = buf[1] - - size := int(buf[0]) - if size > listLength-1 { - return 0, errors.New("invalid length") - } - length = int64(buf[1]) - case TypeCodeList32: - buf, ok := r.Next(8) - if !ok { - return 0, errors.New("invalid length") - } - _ = buf[7] - - size := int(binary.BigEndian.Uint32(buf[:4])) - if size > listLength-4 { - return 0, errors.New("invalid length") - } - length = int64(binary.BigEndian.Uint32(buf[4:8])) - default: - return 0, fmt.Errorf("type code %#02x is not a recognized list type", type_) - } - - return length, nil -} - -func readArrayHeader(r *buffer.Buffer) (length int64, _ error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - arrayLength := r.Len() - - switch type_ { - case TypeCodeArray8: - buf, ok := r.Next(2) - if !ok { - return 0, errors.New("invalid length") - } - _ = buf[1] - - size := int(buf[0]) - if size > arrayLength-1 { - return 0, errors.New("invalid length") - } - length = int64(buf[1]) - case TypeCodeArray32: - buf, ok := r.Next(8) - if !ok { - return 0, errors.New("invalid length") - } - _ = buf[7] - - size := binary.BigEndian.Uint32(buf[:4]) - if int(size) > arrayLength-4 { - return 0, fmt.Errorf("invalid length for type %02x", type_) - } - length = int64(binary.BigEndian.Uint32(buf[4:8])) - default: - return 0, fmt.Errorf("type code %#02x is not a recognized array type", type_) - } - return length, nil -} - -func ReadString(r *buffer.Buffer) (string, error) { - type_, err := readType(r) - if err != nil { - return "", err - } - - var length int64 - switch type_ { - case TypeCodeStr8, TypeCodeSym8: - n, err := r.ReadByte() - if err != nil { - return "", err - } - length = int64(n) - case TypeCodeStr32, TypeCodeSym32: - buf, ok := r.Next(4) - if !ok { - return "", fmt.Errorf("invalid length for type %#02x", type_) - } - length = int64(binary.BigEndian.Uint32(buf)) - default: - return "", fmt.Errorf("type code %#02x is not a recognized string type", type_) - } - - buf, ok := r.Next(length) - if !ok { - return "", errors.New("invalid length") - } - return string(buf), nil -} - -func readBinary(r *buffer.Buffer) ([]byte, error) { - type_, err := readType(r) - if err != nil { - return nil, err - } - - var length int64 - switch type_ { - case TypeCodeVbin8: - n, err := r.ReadByte() - if err != nil { - return nil, err - } - length = int64(n) - case TypeCodeVbin32: - buf, ok := r.Next(4) - if !ok { - return nil, fmt.Errorf("invalid length for type %#02x", type_) - } - length = int64(binary.BigEndian.Uint32(buf)) - default: - return nil, fmt.Errorf("type code %#02x is not a recognized binary type", type_) - } - - if length == 0 { - // An empty value and a nil value are distinct, - // ensure that the returned value is not nil in this case. - return make([]byte, 0), nil - } - - buf, ok := r.Next(length) - if !ok { - return nil, errors.New("invalid length") - } - return append([]byte(nil), buf...), nil -} - -func ReadAny(r *buffer.Buffer) (any, error) { - if tryReadNull(r) { - return nil, nil - } - - type_, err := peekType(r) - if err != nil { - return nil, errors.New("invalid length") - } - - switch type_ { - // composite - case 0x0: - return readComposite(r) - - // bool - case TypeCodeBool, TypeCodeBoolTrue, TypeCodeBoolFalse: - return readBool(r) - - // uint - case TypeCodeUbyte: - return ReadUbyte(r) - case TypeCodeUshort: - return readUshort(r) - case TypeCodeUint, - TypeCodeSmallUint, - TypeCodeUint0: - return readUint32(r) - case TypeCodeUlong, - TypeCodeSmallUlong, - TypeCodeUlong0: - return readUlong(r) - - // int - case TypeCodeByte: - return readSbyte(r) - case TypeCodeShort: - return readShort(r) - case TypeCodeInt, - TypeCodeSmallint: - return readInt32(r) - case TypeCodeLong, - TypeCodeSmalllong: - return readLong(r) - - // floating point - case TypeCodeFloat: - return readFloat(r) - case TypeCodeDouble: - return readDouble(r) - - // binary - case TypeCodeVbin8, TypeCodeVbin32: - return readBinary(r) - - // strings - case TypeCodeStr8, TypeCodeStr32: - return ReadString(r) - case TypeCodeSym8, TypeCodeSym32: - // symbols currently decoded as string to avoid - // exposing symbol type in message, this may need - // to change if users need to distinguish strings - // from symbols - return ReadString(r) - - // timestamp - case TypeCodeTimestamp: - return readTimestamp(r) - - // UUID - case TypeCodeUUID: - return readUUID(r) - - // arrays - case TypeCodeArray8, TypeCodeArray32: - return readAnyArray(r) - - // lists - case TypeCodeList0, TypeCodeList8, TypeCodeList32: - return readAnyList(r) - - // maps - case TypeCodeMap8: - return readAnyMap(r) - case TypeCodeMap32: - return readAnyMap(r) - - // TODO: implement - case TypeCodeDecimal32: - return nil, errors.New("decimal32 not implemented") - case TypeCodeDecimal64: - return nil, errors.New("decimal64 not implemented") - case TypeCodeDecimal128: - return nil, errors.New("decimal128 not implemented") - case TypeCodeChar: - return nil, errors.New("char not implemented") - default: - return nil, fmt.Errorf("unknown type %#02x", type_) - } -} - -func readAnyMap(r *buffer.Buffer) (any, error) { - var m map[any]any - err := (*mapAnyAny)(&m).Unmarshal(r) - if err != nil { - return nil, err - } - - if len(m) == 0 { - return m, nil - } - - stringKeys := true -Loop: - for key := range m { - switch key.(type) { - case string: - case Symbol: - default: - stringKeys = false - break Loop - } - } - - if stringKeys { - mm := make(map[string]any, len(m)) - for key, value := range m { - switch key := key.(type) { - case string: - mm[key] = value - case Symbol: - mm[string(key)] = value - } - } - return mm, nil - } - - return m, nil -} - -func readAnyList(r *buffer.Buffer) (any, error) { - var a []any - err := (*list)(&a).Unmarshal(r) - return a, err -} - -func readAnyArray(r *buffer.Buffer) (any, error) { - // get the array type - buf := r.Bytes() - if len(buf) < 1 { - return nil, errors.New("invalid length") - } - - var typeIdx int - switch AMQPType(buf[0]) { - case TypeCodeArray8: - typeIdx = 3 - case TypeCodeArray32: - typeIdx = 9 - default: - return nil, fmt.Errorf("invalid array type %02x", buf[0]) - } - if len(buf) < typeIdx+1 { - return nil, errors.New("invalid length") - } - - switch AMQPType(buf[typeIdx]) { - case TypeCodeByte: - var a []int8 - err := (*arrayInt8)(&a).Unmarshal(r) - return a, err - case TypeCodeUbyte: - var a ArrayUByte - err := a.Unmarshal(r) - return a, err - case TypeCodeUshort: - var a []uint16 - err := (*arrayUint16)(&a).Unmarshal(r) - return a, err - case TypeCodeShort: - var a []int16 - err := (*arrayInt16)(&a).Unmarshal(r) - return a, err - case TypeCodeUint0, TypeCodeSmallUint, TypeCodeUint: - var a []uint32 - err := (*arrayUint32)(&a).Unmarshal(r) - return a, err - case TypeCodeSmallint, TypeCodeInt: - var a []int32 - err := (*arrayInt32)(&a).Unmarshal(r) - return a, err - case TypeCodeUlong0, TypeCodeSmallUlong, TypeCodeUlong: - var a []uint64 - err := (*arrayUint64)(&a).Unmarshal(r) - return a, err - case TypeCodeSmalllong, TypeCodeLong: - var a []int64 - err := (*arrayInt64)(&a).Unmarshal(r) - return a, err - case TypeCodeFloat: - var a []float32 - err := (*arrayFloat)(&a).Unmarshal(r) - return a, err - case TypeCodeDouble: - var a []float64 - err := (*arrayDouble)(&a).Unmarshal(r) - return a, err - case TypeCodeBool, TypeCodeBoolTrue, TypeCodeBoolFalse: - var a []bool - err := (*arrayBool)(&a).Unmarshal(r) - return a, err - case TypeCodeStr8, TypeCodeStr32: - var a []string - err := (*arrayString)(&a).Unmarshal(r) - return a, err - case TypeCodeSym8, TypeCodeSym32: - var a []Symbol - err := (*arraySymbol)(&a).Unmarshal(r) - return a, err - case TypeCodeVbin8, TypeCodeVbin32: - var a [][]byte - err := (*arrayBinary)(&a).Unmarshal(r) - return a, err - case TypeCodeTimestamp: - var a []time.Time - err := (*arrayTimestamp)(&a).Unmarshal(r) - return a, err - case TypeCodeUUID: - var a []UUID - err := (*arrayUUID)(&a).Unmarshal(r) - return a, err - default: - return nil, fmt.Errorf("array decoding not implemented for %#02x", buf[typeIdx]) - } -} - -func readComposite(r *buffer.Buffer) (any, error) { - buf := r.Bytes() - - if len(buf) < 2 { - return nil, errors.New("invalid length for composite") - } - - // compsites start with 0x0 - if AMQPType(buf[0]) != 0x0 { - return nil, fmt.Errorf("invalid composite header %#02x", buf[0]) - } - - var compositeType uint64 - switch AMQPType(buf[1]) { - case TypeCodeSmallUlong: - if len(buf) < 3 { - return nil, errors.New("invalid length for smallulong") - } - compositeType = uint64(buf[2]) - case TypeCodeUlong: - if len(buf) < 10 { - return nil, errors.New("invalid length for ulong") - } - compositeType = binary.BigEndian.Uint64(buf[2:]) - } - - if compositeType > math.MaxUint8 { - // try as described type - var dt DescribedType - err := dt.Unmarshal(r) - return dt, err - } - - switch AMQPType(compositeType) { - // Error - case TypeCodeError: - t := new(Error) - err := t.Unmarshal(r) - return t, err - - // Lifetime Policies - case TypeCodeDeleteOnClose: - t := DeleteOnClose - err := t.Unmarshal(r) - return t, err - case TypeCodeDeleteOnNoMessages: - t := DeleteOnNoMessages - err := t.Unmarshal(r) - return t, err - case TypeCodeDeleteOnNoLinks: - t := DeleteOnNoLinks - err := t.Unmarshal(r) - return t, err - case TypeCodeDeleteOnNoLinksOrMessages: - t := DeleteOnNoLinksOrMessages - err := t.Unmarshal(r) - return t, err - - // Delivery States - case TypeCodeStateAccepted: - t := new(StateAccepted) - err := t.Unmarshal(r) - return t, err - case TypeCodeStateModified: - t := new(StateModified) - err := t.Unmarshal(r) - return t, err - case TypeCodeStateReceived: - t := new(StateReceived) - err := t.Unmarshal(r) - return t, err - case TypeCodeStateRejected: - t := new(StateRejected) - err := t.Unmarshal(r) - return t, err - case TypeCodeStateReleased: - t := new(StateReleased) - err := t.Unmarshal(r) - return t, err - - case TypeCodeOpen, - TypeCodeBegin, - TypeCodeAttach, - TypeCodeFlow, - TypeCodeTransfer, - TypeCodeDisposition, - TypeCodeDetach, - TypeCodeEnd, - TypeCodeClose, - TypeCodeSource, - TypeCodeTarget, - TypeCodeMessageHeader, - TypeCodeDeliveryAnnotations, - TypeCodeMessageAnnotations, - TypeCodeMessageProperties, - TypeCodeApplicationProperties, - TypeCodeApplicationData, - TypeCodeAMQPSequence, - TypeCodeAMQPValue, - TypeCodeFooter, - TypeCodeSASLMechanism, - TypeCodeSASLInit, - TypeCodeSASLChallenge, - TypeCodeSASLResponse, - TypeCodeSASLOutcome: - return nil, fmt.Errorf("readComposite unmarshal not implemented for %#02x", compositeType) - - default: - // try as described type - var dt DescribedType - err := dt.Unmarshal(r) - return dt, err - } -} - -func readTimestamp(r *buffer.Buffer) (time.Time, error) { - type_, err := readType(r) - if err != nil { - return time.Time{}, err - } - - if type_ != TypeCodeTimestamp { - return time.Time{}, fmt.Errorf("invalid type for timestamp %02x", type_) - } - - n, err := r.ReadUint64() - ms := int64(n) - return time.Unix(ms/1000, (ms%1000)*1000000).UTC(), err -} - -func readInt(r *buffer.Buffer) (int, error) { - type_, err := peekType(r) - if err != nil { - return 0, err - } - - switch type_ { - // Unsigned - case TypeCodeUbyte: - n, err := ReadUbyte(r) - return int(n), err - case TypeCodeUshort: - n, err := readUshort(r) - return int(n), err - case TypeCodeUint0, TypeCodeSmallUint, TypeCodeUint: - n, err := readUint32(r) - return int(n), err - case TypeCodeUlong0, TypeCodeSmallUlong, TypeCodeUlong: - n, err := readUlong(r) - return int(n), err - - // Signed - case TypeCodeByte: - n, err := readSbyte(r) - return int(n), err - case TypeCodeShort: - n, err := readShort(r) - return int(n), err - case TypeCodeSmallint, TypeCodeInt: - n, err := readInt32(r) - return int(n), err - case TypeCodeSmalllong, TypeCodeLong: - n, err := readLong(r) - return int(n), err - default: - return 0, fmt.Errorf("type code %#02x is not a recognized number type", type_) - } -} - -func readLong(r *buffer.Buffer) (int64, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - switch type_ { - case TypeCodeSmalllong: - n, err := r.ReadByte() - return int64(int8(n)), err - case TypeCodeLong: - n, err := r.ReadUint64() - return int64(n), err - default: - return 0, fmt.Errorf("invalid type for uint32 %02x", type_) - } -} - -func readInt32(r *buffer.Buffer) (int32, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - switch type_ { - case TypeCodeSmallint: - n, err := r.ReadByte() - return int32(int8(n)), err - case TypeCodeInt: - n, err := r.ReadUint32() - return int32(n), err - default: - return 0, fmt.Errorf("invalid type for int32 %02x", type_) - } -} - -func readShort(r *buffer.Buffer) (int16, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - if type_ != TypeCodeShort { - return 0, fmt.Errorf("invalid type for short %02x", type_) - } - - n, err := r.ReadUint16() - return int16(n), err -} - -func readSbyte(r *buffer.Buffer) (int8, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - if type_ != TypeCodeByte { - return 0, fmt.Errorf("invalid type for int8 %02x", type_) - } - - n, err := r.ReadByte() - return int8(n), err -} - -func ReadUbyte(r *buffer.Buffer) (uint8, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - if type_ != TypeCodeUbyte { - return 0, fmt.Errorf("invalid type for ubyte %02x", type_) - } - - return r.ReadByte() -} - -func readUshort(r *buffer.Buffer) (uint16, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - if type_ != TypeCodeUshort { - return 0, fmt.Errorf("invalid type for ushort %02x", type_) - } - - return r.ReadUint16() -} - -func readUint32(r *buffer.Buffer) (uint32, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - switch type_ { - case TypeCodeUint0: - return 0, nil - case TypeCodeSmallUint: - n, err := r.ReadByte() - return uint32(n), err - case TypeCodeUint: - return r.ReadUint32() - default: - return 0, fmt.Errorf("invalid type for uint32 %02x", type_) - } -} - -func readUlong(r *buffer.Buffer) (uint64, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - switch type_ { - case TypeCodeUlong0: - return 0, nil - case TypeCodeSmallUlong: - n, err := r.ReadByte() - return uint64(n), err - case TypeCodeUlong: - return r.ReadUint64() - default: - return 0, fmt.Errorf("invalid type for uint32 %02x", type_) - } -} - -func readFloat(r *buffer.Buffer) (float32, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - if type_ != TypeCodeFloat { - return 0, fmt.Errorf("invalid type for float32 %02x", type_) - } - - bits, err := r.ReadUint32() - return math.Float32frombits(bits), err -} - -func readDouble(r *buffer.Buffer) (float64, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - if type_ != TypeCodeDouble { - return 0, fmt.Errorf("invalid type for float64 %02x", type_) - } - - bits, err := r.ReadUint64() - return math.Float64frombits(bits), err -} - -func readBool(r *buffer.Buffer) (bool, error) { - type_, err := readType(r) - if err != nil { - return false, err - } - - switch type_ { - case TypeCodeBool: - b, err := r.ReadByte() - return b != 0, err - case TypeCodeBoolTrue: - return true, nil - case TypeCodeBoolFalse: - return false, nil - default: - return false, fmt.Errorf("type code %#02x is not a recognized bool type", type_) - } -} - -func readUint(r *buffer.Buffer) (value uint64, _ error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - switch type_ { - case TypeCodeUint0, TypeCodeUlong0: - return 0, nil - case TypeCodeUbyte, TypeCodeSmallUint, TypeCodeSmallUlong: - n, err := r.ReadByte() - return uint64(n), err - case TypeCodeUshort: - n, err := r.ReadUint16() - return uint64(n), err - case TypeCodeUint: - n, err := r.ReadUint32() - return uint64(n), err - case TypeCodeUlong: - return r.ReadUint64() - default: - return 0, fmt.Errorf("type code %#02x is not a recognized number type", type_) - } -} - -func readUUID(r *buffer.Buffer) (UUID, error) { - var uuid UUID - - type_, err := readType(r) - if err != nil { - return uuid, err - } - - if type_ != TypeCodeUUID { - return uuid, fmt.Errorf("type code %#00x is not a UUID", type_) - } - - buf, ok := r.Next(16) - if !ok { - return uuid, errors.New("invalid length") - } - copy(uuid[:], buf) - - return uuid, nil -} - -func readMapHeader(r *buffer.Buffer) (count uint32, _ error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - length := r.Len() - - switch type_ { - case TypeCodeMap8: - buf, ok := r.Next(2) - if !ok { - return 0, errors.New("invalid length") - } - _ = buf[1] - - size := int(buf[0]) - if size > length-1 { - return 0, errors.New("invalid length") - } - count = uint32(buf[1]) - case TypeCodeMap32: - buf, ok := r.Next(8) - if !ok { - return 0, errors.New("invalid length") - } - _ = buf[7] - - size := int(binary.BigEndian.Uint32(buf[:4])) - if size > length-4 { - return 0, errors.New("invalid length") - } - count = binary.BigEndian.Uint32(buf[4:8]) - default: - return 0, fmt.Errorf("invalid map type %#02x", type_) - } - - if int(count) > r.Len() { - return 0, errors.New("invalid length") - } - return count, nil -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding/encode.go b/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding/encode.go deleted file mode 100644 index 1103c84f2b26..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding/encode.go +++ /dev/null @@ -1,573 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package encoding - -import ( - "encoding/binary" - "errors" - "fmt" - "math" - "time" - "unicode/utf8" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer" -) - -type marshaler interface { - Marshal(*buffer.Buffer) error -} - -func Marshal(wr *buffer.Buffer, i any) error { - switch t := i.(type) { - case nil: - wr.AppendByte(byte(TypeCodeNull)) - case bool: - if t { - wr.AppendByte(byte(TypeCodeBoolTrue)) - } else { - wr.AppendByte(byte(TypeCodeBoolFalse)) - } - case *bool: - if *t { - wr.AppendByte(byte(TypeCodeBoolTrue)) - } else { - wr.AppendByte(byte(TypeCodeBoolFalse)) - } - case uint: - writeUint64(wr, uint64(t)) - case *uint: - writeUint64(wr, uint64(*t)) - case uint64: - writeUint64(wr, t) - case *uint64: - writeUint64(wr, *t) - case uint32: - writeUint32(wr, t) - case *uint32: - writeUint32(wr, *t) - case uint16: - wr.AppendByte(byte(TypeCodeUshort)) - wr.AppendUint16(t) - case *uint16: - wr.AppendByte(byte(TypeCodeUshort)) - wr.AppendUint16(*t) - case uint8: - wr.Append([]byte{ - byte(TypeCodeUbyte), - t, - }) - case *uint8: - wr.Append([]byte{ - byte(TypeCodeUbyte), - *t, - }) - case int: - writeInt64(wr, int64(t)) - case *int: - writeInt64(wr, int64(*t)) - case int8: - wr.Append([]byte{ - byte(TypeCodeByte), - uint8(t), - }) - case *int8: - wr.Append([]byte{ - byte(TypeCodeByte), - uint8(*t), - }) - case int16: - wr.AppendByte(byte(TypeCodeShort)) - wr.AppendUint16(uint16(t)) - case *int16: - wr.AppendByte(byte(TypeCodeShort)) - wr.AppendUint16(uint16(*t)) - case int32: - writeInt32(wr, t) - case *int32: - writeInt32(wr, *t) - case int64: - writeInt64(wr, t) - case *int64: - writeInt64(wr, *t) - case float32: - writeFloat(wr, t) - case *float32: - writeFloat(wr, *t) - case float64: - writeDouble(wr, t) - case *float64: - writeDouble(wr, *t) - case string: - return writeString(wr, t) - case *string: - return writeString(wr, *t) - case []byte: - return WriteBinary(wr, t) - case *[]byte: - return WriteBinary(wr, *t) - case map[any]any: - return writeMap(wr, t) - case *map[any]any: - return writeMap(wr, *t) - case map[string]any: - return writeMap(wr, t) - case *map[string]any: - return writeMap(wr, *t) - case map[Symbol]any: - return writeMap(wr, t) - case *map[Symbol]any: - return writeMap(wr, *t) - case Unsettled: - return writeMap(wr, t) - case *Unsettled: - return writeMap(wr, *t) - case time.Time: - writeTimestamp(wr, t) - case *time.Time: - writeTimestamp(wr, *t) - case []int8: - return arrayInt8(t).Marshal(wr) - case *[]int8: - return arrayInt8(*t).Marshal(wr) - case []uint16: - return arrayUint16(t).Marshal(wr) - case *[]uint16: - return arrayUint16(*t).Marshal(wr) - case []int16: - return arrayInt16(t).Marshal(wr) - case *[]int16: - return arrayInt16(*t).Marshal(wr) - case []uint32: - return arrayUint32(t).Marshal(wr) - case *[]uint32: - return arrayUint32(*t).Marshal(wr) - case []int32: - return arrayInt32(t).Marshal(wr) - case *[]int32: - return arrayInt32(*t).Marshal(wr) - case []uint64: - return arrayUint64(t).Marshal(wr) - case *[]uint64: - return arrayUint64(*t).Marshal(wr) - case []int64: - return arrayInt64(t).Marshal(wr) - case *[]int64: - return arrayInt64(*t).Marshal(wr) - case []float32: - return arrayFloat(t).Marshal(wr) - case *[]float32: - return arrayFloat(*t).Marshal(wr) - case []float64: - return arrayDouble(t).Marshal(wr) - case *[]float64: - return arrayDouble(*t).Marshal(wr) - case []bool: - return arrayBool(t).Marshal(wr) - case *[]bool: - return arrayBool(*t).Marshal(wr) - case []string: - return arrayString(t).Marshal(wr) - case *[]string: - return arrayString(*t).Marshal(wr) - case []Symbol: - return arraySymbol(t).Marshal(wr) - case *[]Symbol: - return arraySymbol(*t).Marshal(wr) - case [][]byte: - return arrayBinary(t).Marshal(wr) - case *[][]byte: - return arrayBinary(*t).Marshal(wr) - case []time.Time: - return arrayTimestamp(t).Marshal(wr) - case *[]time.Time: - return arrayTimestamp(*t).Marshal(wr) - case []UUID: - return arrayUUID(t).Marshal(wr) - case *[]UUID: - return arrayUUID(*t).Marshal(wr) - case []any: - return list(t).Marshal(wr) - case *[]any: - return list(*t).Marshal(wr) - case marshaler: - return t.Marshal(wr) - default: - return fmt.Errorf("marshal not implemented for %T", i) - } - return nil -} - -func writeInt32(wr *buffer.Buffer, n int32) { - if n < 128 && n >= -128 { - wr.Append([]byte{ - byte(TypeCodeSmallint), - byte(n), - }) - return - } - - wr.AppendByte(byte(TypeCodeInt)) - wr.AppendUint32(uint32(n)) -} - -func writeInt64(wr *buffer.Buffer, n int64) { - if n < 128 && n >= -128 { - wr.Append([]byte{ - byte(TypeCodeSmalllong), - byte(n), - }) - return - } - - wr.AppendByte(byte(TypeCodeLong)) - wr.AppendUint64(uint64(n)) -} - -func writeUint32(wr *buffer.Buffer, n uint32) { - if n == 0 { - wr.AppendByte(byte(TypeCodeUint0)) - return - } - - if n < 256 { - wr.Append([]byte{ - byte(TypeCodeSmallUint), - byte(n), - }) - return - } - - wr.AppendByte(byte(TypeCodeUint)) - wr.AppendUint32(n) -} - -func writeUint64(wr *buffer.Buffer, n uint64) { - if n == 0 { - wr.AppendByte(byte(TypeCodeUlong0)) - return - } - - if n < 256 { - wr.Append([]byte{ - byte(TypeCodeSmallUlong), - byte(n), - }) - return - } - - wr.AppendByte(byte(TypeCodeUlong)) - wr.AppendUint64(n) -} - -func writeFloat(wr *buffer.Buffer, f float32) { - wr.AppendByte(byte(TypeCodeFloat)) - wr.AppendUint32(math.Float32bits(f)) -} - -func writeDouble(wr *buffer.Buffer, f float64) { - wr.AppendByte(byte(TypeCodeDouble)) - wr.AppendUint64(math.Float64bits(f)) -} - -func writeTimestamp(wr *buffer.Buffer, t time.Time) { - wr.AppendByte(byte(TypeCodeTimestamp)) - ms := t.UnixNano() / int64(time.Millisecond) - wr.AppendUint64(uint64(ms)) -} - -// marshalField is a field to be marshaled -type MarshalField struct { - Value any // value to be marshaled, use pointers to avoid interface conversion overhead - Omit bool // indicates that this field should be omitted (set to null) -} - -// marshalComposite is a helper for us in a composite's marshal() function. -// -// The returned bytes include the composite header and fields. Fields with -// omit set to true will be encoded as null or omitted altogether if there are -// no non-null fields after them. -func MarshalComposite(wr *buffer.Buffer, code AMQPType, fields []MarshalField) error { - // lastSetIdx is the last index to have a non-omitted field. - // start at -1 as it's possible to have no fields in a composite - lastSetIdx := -1 - - // marshal each field into it's index in rawFields, - // null fields are skipped, leaving the index nil. - for i, f := range fields { - if f.Omit { - continue - } - lastSetIdx = i - } - - // write header only - if lastSetIdx == -1 { - wr.Append([]byte{ - 0x0, - byte(TypeCodeSmallUlong), - byte(code), - byte(TypeCodeList0), - }) - return nil - } - - // write header - WriteDescriptor(wr, code) - - // write fields - wr.AppendByte(byte(TypeCodeList32)) - - // write temp size, replace later - sizeIdx := wr.Len() - wr.Append([]byte{0, 0, 0, 0}) - preFieldLen := wr.Len() - - // field count - wr.AppendUint32(uint32(lastSetIdx + 1)) - - // write null to each index up to lastSetIdx - for _, f := range fields[:lastSetIdx+1] { - if f.Omit { - wr.AppendByte(byte(TypeCodeNull)) - continue - } - err := Marshal(wr, f.Value) - if err != nil { - return err - } - } - - // fix size - size := uint32(wr.Len() - preFieldLen) - buf := wr.Bytes() - binary.BigEndian.PutUint32(buf[sizeIdx:], size) - - return nil -} - -func WriteDescriptor(wr *buffer.Buffer, code AMQPType) { - wr.Append([]byte{ - 0x0, - byte(TypeCodeSmallUlong), - byte(code), - }) -} - -func writeString(wr *buffer.Buffer, str string) error { - if !utf8.ValidString(str) { - return errors.New("not a valid UTF-8 string") - } - l := len(str) - - switch { - // Str8 - case l < 256: - wr.Append([]byte{ - byte(TypeCodeStr8), - byte(l), - }) - wr.AppendString(str) - return nil - - // Str32 - case uint(l) < math.MaxUint32: - wr.AppendByte(byte(TypeCodeStr32)) - wr.AppendUint32(uint32(l)) - wr.AppendString(str) - return nil - - default: - return errors.New("too long") - } -} - -func WriteBinary(wr *buffer.Buffer, bin []byte) error { - l := len(bin) - - switch { - // List8 - case l < 256: - wr.Append([]byte{ - byte(TypeCodeVbin8), - byte(l), - }) - wr.Append(bin) - return nil - - // List32 - case uint(l) < math.MaxUint32: - wr.AppendByte(byte(TypeCodeVbin32)) - wr.AppendUint32(uint32(l)) - wr.Append(bin) - return nil - - default: - return errors.New("too long") - } -} - -func writeMap(wr *buffer.Buffer, m any) error { - startIdx := wr.Len() - wr.Append([]byte{ - byte(TypeCodeMap32), // type - 0, 0, 0, 0, // size placeholder - 0, 0, 0, 0, // length placeholder - }) - - var pairs int - switch m := m.(type) { - case map[any]any: - pairs = len(m) * 2 - for key, val := range m { - err := Marshal(wr, key) - if err != nil { - return err - } - err = Marshal(wr, val) - if err != nil { - return err - } - } - case map[string]any: - pairs = len(m) * 2 - for key, val := range m { - err := writeString(wr, key) - if err != nil { - return err - } - err = Marshal(wr, val) - if err != nil { - return err - } - } - case map[Symbol]any: - pairs = len(m) * 2 - for key, val := range m { - err := key.Marshal(wr) - if err != nil { - return err - } - err = Marshal(wr, val) - if err != nil { - return err - } - } - case Unsettled: - pairs = len(m) * 2 - for key, val := range m { - err := writeString(wr, key) - if err != nil { - return err - } - err = Marshal(wr, val) - if err != nil { - return err - } - } - case Filter: - pairs = len(m) * 2 - for key, val := range m { - err := key.Marshal(wr) - if err != nil { - return err - } - err = val.Marshal(wr) - if err != nil { - return err - } - } - case Annotations: - pairs = len(m) * 2 - for key, val := range m { - switch key := key.(type) { - case string: - err := Symbol(key).Marshal(wr) - if err != nil { - return err - } - case Symbol: - err := key.Marshal(wr) - if err != nil { - return err - } - case int64: - writeInt64(wr, key) - case int: - writeInt64(wr, int64(key)) - default: - return fmt.Errorf("unsupported Annotations key type %T", key) - } - - err := Marshal(wr, val) - if err != nil { - return err - } - } - default: - return fmt.Errorf("unsupported map type %T", m) - } - - if uint(pairs) > math.MaxUint32-4 { - return errors.New("map contains too many elements") - } - - // overwrite placeholder size and length - bytes := wr.Bytes()[startIdx+1 : startIdx+9] - _ = bytes[7] // bounds check hint - - length := wr.Len() - startIdx - 1 - 4 // -1 for type, -4 for length - binary.BigEndian.PutUint32(bytes[:4], uint32(length)) - binary.BigEndian.PutUint32(bytes[4:8], uint32(pairs)) - - return nil -} - -// type length sizes -const ( - array8TLSize = 2 - array32TLSize = 5 -) - -func writeArrayHeader(wr *buffer.Buffer, length, typeSize int, type_ AMQPType) { - size := length * typeSize - - // array type - if size+array8TLSize <= math.MaxUint8 { - wr.Append([]byte{ - byte(TypeCodeArray8), // type - byte(size + array8TLSize), // size - byte(length), // length - byte(type_), // element type - }) - } else { - wr.AppendByte(byte(TypeCodeArray32)) //type - wr.AppendUint32(uint32(size + array32TLSize)) // size - wr.AppendUint32(uint32(length)) // length - wr.AppendByte(byte(type_)) // element type - } -} - -func writeVariableArrayHeader(wr *buffer.Buffer, length, elementsSizeTotal int, type_ AMQPType) { - // 0xA_ == 1, 0xB_ == 4 - // http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-types-v1.0-os.html#doc-idp82960 - elementTypeSize := 1 - if type_&0xf0 == 0xb0 { - elementTypeSize = 4 - } - - size := elementsSizeTotal + (length * elementTypeSize) // size excluding array length - if size+array8TLSize <= math.MaxUint8 { - wr.Append([]byte{ - byte(TypeCodeArray8), // type - byte(size + array8TLSize), // size - byte(length), // length - byte(type_), // element type - }) - } else { - wr.AppendByte(byte(TypeCodeArray32)) // type - wr.AppendUint32(uint32(size + array32TLSize)) // size - wr.AppendUint32(uint32(length)) // length - wr.AppendByte(byte(type_)) // element type - } -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding/types.go b/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding/types.go deleted file mode 100644 index 5196d49b4d4c..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding/types.go +++ /dev/null @@ -1,2155 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package encoding - -import ( - "encoding/binary" - "encoding/hex" - "errors" - "fmt" - "math" - "reflect" - "time" - "unicode/utf8" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer" -) - -type AMQPType uint8 - -// Type codes -const ( - TypeCodeNull AMQPType = 0x40 - - // Bool - TypeCodeBool AMQPType = 0x56 // boolean with the octet 0x00 being false and octet 0x01 being true - TypeCodeBoolTrue AMQPType = 0x41 - TypeCodeBoolFalse AMQPType = 0x42 - - // Unsigned - TypeCodeUbyte AMQPType = 0x50 // 8-bit unsigned integer (1) - TypeCodeUshort AMQPType = 0x60 // 16-bit unsigned integer in network byte order (2) - TypeCodeUint AMQPType = 0x70 // 32-bit unsigned integer in network byte order (4) - TypeCodeSmallUint AMQPType = 0x52 // unsigned integer value in the range 0 to 255 inclusive (1) - TypeCodeUint0 AMQPType = 0x43 // the uint value 0 (0) - TypeCodeUlong AMQPType = 0x80 // 64-bit unsigned integer in network byte order (8) - TypeCodeSmallUlong AMQPType = 0x53 // unsigned long value in the range 0 to 255 inclusive (1) - TypeCodeUlong0 AMQPType = 0x44 // the ulong value 0 (0) - - // Signed - TypeCodeByte AMQPType = 0x51 // 8-bit two's-complement integer (1) - TypeCodeShort AMQPType = 0x61 // 16-bit two's-complement integer in network byte order (2) - TypeCodeInt AMQPType = 0x71 // 32-bit two's-complement integer in network byte order (4) - TypeCodeSmallint AMQPType = 0x54 // 8-bit two's-complement integer (1) - TypeCodeLong AMQPType = 0x81 // 64-bit two's-complement integer in network byte order (8) - TypeCodeSmalllong AMQPType = 0x55 // 8-bit two's-complement integer - - // Decimal - TypeCodeFloat AMQPType = 0x72 // IEEE 754-2008 binary32 (4) - TypeCodeDouble AMQPType = 0x82 // IEEE 754-2008 binary64 (8) - TypeCodeDecimal32 AMQPType = 0x74 // IEEE 754-2008 decimal32 using the Binary Integer Decimal encoding (4) - TypeCodeDecimal64 AMQPType = 0x84 // IEEE 754-2008 decimal64 using the Binary Integer Decimal encoding (8) - TypeCodeDecimal128 AMQPType = 0x94 // IEEE 754-2008 decimal128 using the Binary Integer Decimal encoding (16) - - // Other - TypeCodeChar AMQPType = 0x73 // a UTF-32BE encoded Unicode character (4) - TypeCodeTimestamp AMQPType = 0x83 // 64-bit two's-complement integer representing milliseconds since the unix epoch - TypeCodeUUID AMQPType = 0x98 // UUID as defined in section 4.1.2 of RFC-4122 - - // Variable Length - TypeCodeVbin8 AMQPType = 0xa0 // up to 2^8 - 1 octets of binary data (1 + variable) - TypeCodeVbin32 AMQPType = 0xb0 // up to 2^32 - 1 octets of binary data (4 + variable) - TypeCodeStr8 AMQPType = 0xa1 // up to 2^8 - 1 octets worth of UTF-8 Unicode (with no byte order mark) (1 + variable) - TypeCodeStr32 AMQPType = 0xb1 // up to 2^32 - 1 octets worth of UTF-8 Unicode (with no byte order mark) (4 +variable) - TypeCodeSym8 AMQPType = 0xa3 // up to 2^8 - 1 seven bit ASCII characters representing a symbolic value (1 + variable) - TypeCodeSym32 AMQPType = 0xb3 // up to 2^32 - 1 seven bit ASCII characters representing a symbolic value (4 + variable) - - // Compound - TypeCodeList0 AMQPType = 0x45 // the empty list (i.e. the list with no elements) (0) - TypeCodeList8 AMQPType = 0xc0 // up to 2^8 - 1 list elements with total size less than 2^8 octets (1 + compound) - TypeCodeList32 AMQPType = 0xd0 // up to 2^32 - 1 list elements with total size less than 2^32 octets (4 + compound) - TypeCodeMap8 AMQPType = 0xc1 // up to 2^8 - 1 octets of encoded map data (1 + compound) - TypeCodeMap32 AMQPType = 0xd1 // up to 2^32 - 1 octets of encoded map data (4 + compound) - TypeCodeArray8 AMQPType = 0xe0 // up to 2^8 - 1 array elements with total size less than 2^8 octets (1 + array) - TypeCodeArray32 AMQPType = 0xf0 // up to 2^32 - 1 array elements with total size less than 2^32 octets (4 + array) - - // Composites - TypeCodeOpen AMQPType = 0x10 - TypeCodeBegin AMQPType = 0x11 - TypeCodeAttach AMQPType = 0x12 - TypeCodeFlow AMQPType = 0x13 - TypeCodeTransfer AMQPType = 0x14 - TypeCodeDisposition AMQPType = 0x15 - TypeCodeDetach AMQPType = 0x16 - TypeCodeEnd AMQPType = 0x17 - TypeCodeClose AMQPType = 0x18 - - TypeCodeSource AMQPType = 0x28 - TypeCodeTarget AMQPType = 0x29 - TypeCodeError AMQPType = 0x1d - - TypeCodeMessageHeader AMQPType = 0x70 - TypeCodeDeliveryAnnotations AMQPType = 0x71 - TypeCodeMessageAnnotations AMQPType = 0x72 - TypeCodeMessageProperties AMQPType = 0x73 - TypeCodeApplicationProperties AMQPType = 0x74 - TypeCodeApplicationData AMQPType = 0x75 - TypeCodeAMQPSequence AMQPType = 0x76 - TypeCodeAMQPValue AMQPType = 0x77 - TypeCodeFooter AMQPType = 0x78 - - TypeCodeStateReceived AMQPType = 0x23 - TypeCodeStateAccepted AMQPType = 0x24 - TypeCodeStateRejected AMQPType = 0x25 - TypeCodeStateReleased AMQPType = 0x26 - TypeCodeStateModified AMQPType = 0x27 - - TypeCodeSASLMechanism AMQPType = 0x40 - TypeCodeSASLInit AMQPType = 0x41 - TypeCodeSASLChallenge AMQPType = 0x42 - TypeCodeSASLResponse AMQPType = 0x43 - TypeCodeSASLOutcome AMQPType = 0x44 - - TypeCodeDeleteOnClose AMQPType = 0x2b - TypeCodeDeleteOnNoLinks AMQPType = 0x2c - TypeCodeDeleteOnNoMessages AMQPType = 0x2d - TypeCodeDeleteOnNoLinksOrMessages AMQPType = 0x2e -) - -// Durability Policies -const ( - // No terminus state is retained durably. - DurabilityNone Durability = 0 - - // Only the existence and configuration of the terminus is - // retained durably. - DurabilityConfiguration Durability = 1 - - // In addition to the existence and configuration of the - // terminus, the unsettled state for durable messages is - // retained durably. - DurabilityUnsettledState Durability = 2 -) - -// Durability specifies the durability of a link. -type Durability uint32 - -func (d *Durability) String() string { - if d == nil { - return "" - } - - switch *d { - case DurabilityNone: - return "none" - case DurabilityConfiguration: - return "configuration" - case DurabilityUnsettledState: - return "unsettled-state" - default: - return fmt.Sprintf("unknown durability %d", *d) - } -} - -func (d Durability) Marshal(wr *buffer.Buffer) error { - return Marshal(wr, uint32(d)) -} - -func (d *Durability) Unmarshal(r *buffer.Buffer) error { - return Unmarshal(r, (*uint32)(d)) -} - -// Expiry Policies -const ( - // The expiry timer starts when terminus is detached. - ExpiryLinkDetach ExpiryPolicy = "link-detach" - - // The expiry timer starts when the most recently - // associated session is ended. - ExpirySessionEnd ExpiryPolicy = "session-end" - - // The expiry timer starts when most recently associated - // connection is closed. - ExpiryConnectionClose ExpiryPolicy = "connection-close" - - // The terminus never expires. - ExpiryNever ExpiryPolicy = "never" -) - -// ExpiryPolicy specifies when the expiry timer of a terminus -// starts counting down from the timeout value. -// -// If the link is subsequently re-attached before the terminus is expired, -// then the count down is aborted. If the conditions for the -// terminus-expiry-policy are subsequently re-met, the expiry timer restarts -// from its originally configured timeout value. -type ExpiryPolicy Symbol - -func ValidateExpiryPolicy(e ExpiryPolicy) error { - switch e { - case ExpiryLinkDetach, - ExpirySessionEnd, - ExpiryConnectionClose, - ExpiryNever: - return nil - default: - return fmt.Errorf("unknown expiry-policy %q", e) - } -} - -func (e ExpiryPolicy) Marshal(wr *buffer.Buffer) error { - return Symbol(e).Marshal(wr) -} - -func (e *ExpiryPolicy) Unmarshal(r *buffer.Buffer) error { - err := Unmarshal(r, (*Symbol)(e)) - if err != nil { - return err - } - return ValidateExpiryPolicy(*e) -} - -func (e *ExpiryPolicy) String() string { - if e == nil { - return "" - } - return string(*e) -} - -// Sender Settlement Modes -const ( - // Sender will send all deliveries initially unsettled to the receiver. - SenderSettleModeUnsettled SenderSettleMode = 0 - - // Sender will send all deliveries settled to the receiver. - SenderSettleModeSettled SenderSettleMode = 1 - - // Sender MAY send a mixture of settled and unsettled deliveries to the receiver. - SenderSettleModeMixed SenderSettleMode = 2 -) - -// SenderSettleMode specifies how the sender will settle messages. -type SenderSettleMode uint8 - -func (m SenderSettleMode) Ptr() *SenderSettleMode { - return &m -} - -func (m *SenderSettleMode) String() string { - if m == nil { - return "" - } - - switch *m { - case SenderSettleModeUnsettled: - return "unsettled" - - case SenderSettleModeSettled: - return "settled" - - case SenderSettleModeMixed: - return "mixed" - - default: - return fmt.Sprintf("unknown sender mode %d", uint8(*m)) - } -} - -func (m SenderSettleMode) Marshal(wr *buffer.Buffer) error { - return Marshal(wr, uint8(m)) -} - -func (m *SenderSettleMode) Unmarshal(r *buffer.Buffer) error { - n, err := ReadUbyte(r) - *m = SenderSettleMode(n) - return err -} - -// Receiver Settlement Modes -const ( - // Receiver will spontaneously settle all incoming transfers. - ReceiverSettleModeFirst ReceiverSettleMode = 0 - - // Receiver will only settle after sending the disposition to the - // sender and receiving a disposition indicating settlement of - // the delivery from the sender. - ReceiverSettleModeSecond ReceiverSettleMode = 1 -) - -// ReceiverSettleMode specifies how the receiver will settle messages. -type ReceiverSettleMode uint8 - -func (m ReceiverSettleMode) Ptr() *ReceiverSettleMode { - return &m -} - -func (m *ReceiverSettleMode) String() string { - if m == nil { - return "" - } - - switch *m { - case ReceiverSettleModeFirst: - return "first" - - case ReceiverSettleModeSecond: - return "second" - - default: - return fmt.Sprintf("unknown receiver mode %d", uint8(*m)) - } -} - -func (m ReceiverSettleMode) Marshal(wr *buffer.Buffer) error { - return Marshal(wr, uint8(m)) -} - -func (m *ReceiverSettleMode) Unmarshal(r *buffer.Buffer) error { - n, err := ReadUbyte(r) - *m = ReceiverSettleMode(n) - return err -} - -type Role bool - -const ( - RoleSender Role = false - RoleReceiver Role = true -) - -func (rl Role) String() string { - if rl { - return "Receiver" - } - return "Sender" -} - -func (rl *Role) Unmarshal(r *buffer.Buffer) error { - b, err := readBool(r) - *rl = Role(b) - return err -} - -func (rl Role) Marshal(wr *buffer.Buffer) error { - return Marshal(wr, (bool)(rl)) -} - -type SASLCode uint8 - -// SASL Codes -const ( - CodeSASLOK SASLCode = iota // Connection authentication succeeded. - CodeSASLAuth // Connection authentication failed due to an unspecified problem with the supplied credentials. - CodeSASLSysPerm // Connection authentication failed due to a system error that is unlikely to be corrected without intervention. -) - -func (s SASLCode) Marshal(wr *buffer.Buffer) error { - return Marshal(wr, uint8(s)) -} - -func (s *SASLCode) Unmarshal(r *buffer.Buffer) error { - n, err := ReadUbyte(r) - *s = SASLCode(n) - return err -} - -// DeliveryState encapsulates the various concrete delivery states. -// http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#section-delivery-state -// TODO: http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transactions-v1.0-os.html#type-declared -type DeliveryState interface { - deliveryState() // marker method -} - -type Unsettled map[string]DeliveryState - -func (u Unsettled) Marshal(wr *buffer.Buffer) error { - return writeMap(wr, u) -} - -func (u *Unsettled) Unmarshal(r *buffer.Buffer) error { - count, err := readMapHeader(r) - if err != nil { - return err - } - - m := make(Unsettled, count/2) - for i := uint32(0); i < count; i += 2 { - key, err := ReadString(r) - if err != nil { - return err - } - var value DeliveryState - err = Unmarshal(r, &value) - if err != nil { - return err - } - m[key] = value - } - *u = m - return nil -} - -type Filter map[Symbol]*DescribedType - -func (f Filter) Marshal(wr *buffer.Buffer) error { - return writeMap(wr, f) -} - -func (f *Filter) Unmarshal(r *buffer.Buffer) error { - count, err := readMapHeader(r) - if err != nil { - return err - } - - m := make(Filter, count/2) - for i := uint32(0); i < count; i += 2 { - key, err := ReadString(r) - if err != nil { - return err - } - var value DescribedType - err = Unmarshal(r, &value) - if err != nil { - return err - } - m[Symbol(key)] = &value - } - *f = m - return nil -} - -// peekMessageType reads the message type without -// modifying any data. -func PeekMessageType(buf []byte) (uint8, uint8, error) { - if len(buf) < 3 { - return 0, 0, errors.New("invalid message") - } - - if buf[0] != 0 { - return 0, 0, fmt.Errorf("invalid composite header %02x", buf[0]) - } - - // copied from readUlong to avoid allocations - t := AMQPType(buf[1]) - if t == TypeCodeUlong0 { - return 0, 2, nil - } - - if t == TypeCodeSmallUlong { - if len(buf[2:]) == 0 { - return 0, 0, errors.New("invalid ulong") - } - return buf[2], 3, nil - } - - if t != TypeCodeUlong { - return 0, 0, fmt.Errorf("invalid type for uint32 %02x", t) - } - - if len(buf[2:]) < 8 { - return 0, 0, errors.New("invalid ulong") - } - v := binary.BigEndian.Uint64(buf[2:10]) - - return uint8(v), 10, nil -} - -func tryReadNull(r *buffer.Buffer) bool { - if r.Len() > 0 && AMQPType(r.Bytes()[0]) == TypeCodeNull { - r.Skip(1) - return true - } - return false -} - -// Annotations keys must be of type string, int, or int64. -// -// String keys are encoded as AMQP Symbols. -type Annotations map[any]any - -func (a Annotations) Marshal(wr *buffer.Buffer) error { - return writeMap(wr, a) -} - -func (a *Annotations) Unmarshal(r *buffer.Buffer) error { - count, err := readMapHeader(r) - if err != nil { - return err - } - - m := make(Annotations, count/2) - for i := uint32(0); i < count; i += 2 { - key, err := ReadAny(r) - if err != nil { - return err - } - value, err := ReadAny(r) - if err != nil { - return err - } - m[key] = value - } - *a = m - return nil -} - -// ErrCond is one of the error conditions defined in the AMQP spec. -type ErrCond string - -func (ec ErrCond) Marshal(wr *buffer.Buffer) error { - return (Symbol)(ec).Marshal(wr) -} - -func (ec *ErrCond) Unmarshal(r *buffer.Buffer) error { - s, err := ReadString(r) - *ec = ErrCond(s) - return err -} - -/* - - - - - - -*/ - -// Error is an AMQP error. -type Error struct { - // A symbolic value indicating the error condition. - Condition ErrCond - - // descriptive text about the error condition - // - // This text supplies any supplementary details not indicated by the condition field. - // This text can be logged as an aid to resolving issues. - Description string - - // map carrying information about the error condition - Info map[string]any -} - -func (e *Error) Marshal(wr *buffer.Buffer) error { - return MarshalComposite(wr, TypeCodeError, []MarshalField{ - {Value: &e.Condition, Omit: false}, - {Value: &e.Description, Omit: e.Description == ""}, - {Value: e.Info, Omit: len(e.Info) == 0}, - }) -} - -func (e *Error) Unmarshal(r *buffer.Buffer) error { - return UnmarshalComposite(r, TypeCodeError, []UnmarshalField{ - {Field: &e.Condition, HandleNull: func() error { return errors.New("Error.Condition is required") }}, - {Field: &e.Description}, - {Field: &e.Info}, - }...) -} - -func (e *Error) String() string { - if e == nil { - return "*Error(nil)" - } - return fmt.Sprintf("*Error{Condition: %s, Description: %s, Info: %v}", - e.Condition, - e.Description, - e.Info, - ) -} - -func (e *Error) Error() string { - return e.String() -} - -/* - - - - - -*/ - -type StateReceived struct { - // When sent by the sender this indicates the first section of the message - // (with section-number 0 being the first section) for which data can be resent. - // Data from sections prior to the given section cannot be retransmitted for - // this delivery. - // - // When sent by the receiver this indicates the first section of the message - // for which all data might not yet have been received. - SectionNumber uint32 - - // When sent by the sender this indicates the first byte of the encoded section - // data of the section given by section-number for which data can be resent - // (with section-offset 0 being the first byte). Bytes from the same section - // prior to the given offset section cannot be retransmitted for this delivery. - // - // When sent by the receiver this indicates the first byte of the given section - // which has not yet been received. Note that if a receiver has received all of - // section number X (which contains N bytes of data), but none of section number - // X + 1, then it can indicate this by sending either Received(section-number=X, - // section-offset=N) or Received(section-number=X+1, section-offset=0). The state - // Received(section-number=0, section-offset=0) indicates that no message data - // at all has been transferred. - SectionOffset uint64 -} - -func (sr *StateReceived) deliveryState() {} - -func (sr *StateReceived) Marshal(wr *buffer.Buffer) error { - return MarshalComposite(wr, TypeCodeStateReceived, []MarshalField{ - {Value: &sr.SectionNumber, Omit: false}, - {Value: &sr.SectionOffset, Omit: false}, - }) -} - -func (sr *StateReceived) Unmarshal(r *buffer.Buffer) error { - return UnmarshalComposite(r, TypeCodeStateReceived, []UnmarshalField{ - {Field: &sr.SectionNumber, HandleNull: func() error { return errors.New("StateReceiver.SectionNumber is required") }}, - {Field: &sr.SectionOffset, HandleNull: func() error { return errors.New("StateReceiver.SectionOffset is required") }}, - }...) -} - -/* - - - -*/ - -type StateAccepted struct{} - -func (sr *StateAccepted) deliveryState() {} - -func (sa *StateAccepted) Marshal(wr *buffer.Buffer) error { - return MarshalComposite(wr, TypeCodeStateAccepted, nil) -} - -func (sa *StateAccepted) Unmarshal(r *buffer.Buffer) error { - return UnmarshalComposite(r, TypeCodeStateAccepted) -} - -func (sa *StateAccepted) String() string { - return "Accepted" -} - -/* - - - - -*/ - -type StateRejected struct { - Error *Error -} - -func (sr *StateRejected) deliveryState() {} - -func (sr *StateRejected) Marshal(wr *buffer.Buffer) error { - return MarshalComposite(wr, TypeCodeStateRejected, []MarshalField{ - {Value: sr.Error, Omit: sr.Error == nil}, - }) -} - -func (sr *StateRejected) Unmarshal(r *buffer.Buffer) error { - return UnmarshalComposite(r, TypeCodeStateRejected, - UnmarshalField{Field: &sr.Error}, - ) -} - -func (sr *StateRejected) String() string { - return fmt.Sprintf("Rejected{Error: %v}", sr.Error) -} - -/* - - - -*/ - -type StateReleased struct{} - -func (sr *StateReleased) deliveryState() {} - -func (sr *StateReleased) Marshal(wr *buffer.Buffer) error { - return MarshalComposite(wr, TypeCodeStateReleased, nil) -} - -func (sr *StateReleased) Unmarshal(r *buffer.Buffer) error { - return UnmarshalComposite(r, TypeCodeStateReleased) -} - -func (sr *StateReleased) String() string { - return "Released" -} - -/* - - - - - - -*/ - -type StateModified struct { - // count the transfer as an unsuccessful delivery attempt - // - // If the delivery-failed flag is set, any messages modified - // MUST have their delivery-count incremented. - DeliveryFailed bool - - // prevent redelivery - // - // If the undeliverable-here is set, then any messages released MUST NOT - // be redelivered to the modifying link endpoint. - UndeliverableHere bool - - // message attributes - // Map containing attributes to combine with the existing message-annotations - // held in the message's header section. Where the existing message-annotations - // of the message contain an entry with the same key as an entry in this field, - // the value in this field associated with that key replaces the one in the - // existing headers; where the existing message-annotations has no such value, - // the value in this map is added. - MessageAnnotations Annotations -} - -func (sr *StateModified) deliveryState() {} - -func (sm *StateModified) Marshal(wr *buffer.Buffer) error { - return MarshalComposite(wr, TypeCodeStateModified, []MarshalField{ - {Value: &sm.DeliveryFailed, Omit: !sm.DeliveryFailed}, - {Value: &sm.UndeliverableHere, Omit: !sm.UndeliverableHere}, - {Value: sm.MessageAnnotations, Omit: sm.MessageAnnotations == nil}, - }) -} - -func (sm *StateModified) Unmarshal(r *buffer.Buffer) error { - return UnmarshalComposite(r, TypeCodeStateModified, []UnmarshalField{ - {Field: &sm.DeliveryFailed}, - {Field: &sm.UndeliverableHere}, - {Field: &sm.MessageAnnotations}, - }...) -} - -func (sm *StateModified) String() string { - return fmt.Sprintf("Modified{DeliveryFailed: %t, UndeliverableHere: %t, MessageAnnotations: %v}", sm.DeliveryFailed, sm.UndeliverableHere, sm.MessageAnnotations) -} - -// symbol is an AMQP symbolic string. -type Symbol string - -func (s Symbol) Marshal(wr *buffer.Buffer) error { - l := len(s) - switch { - // Sym8 - case l < 256: - wr.Append([]byte{ - byte(TypeCodeSym8), - byte(l), - }) - wr.AppendString(string(s)) - - // Sym32 - case uint(l) < math.MaxUint32: - wr.AppendByte(uint8(TypeCodeSym32)) - wr.AppendUint32(uint32(l)) - wr.AppendString(string(s)) - default: - return errors.New("too long") - } - return nil -} - -type Milliseconds time.Duration - -func (m Milliseconds) Marshal(wr *buffer.Buffer) error { - writeUint32(wr, uint32(m/Milliseconds(time.Millisecond))) - return nil -} - -func (m *Milliseconds) Unmarshal(r *buffer.Buffer) error { - n, err := readUint(r) - *m = Milliseconds(time.Duration(n) * time.Millisecond) - return err -} - -// mapAnyAny is used to decode AMQP maps who's keys are undefined or -// inconsistently typed. -type mapAnyAny map[any]any - -func (m mapAnyAny) Marshal(wr *buffer.Buffer) error { - return writeMap(wr, map[any]any(m)) -} - -func (m *mapAnyAny) Unmarshal(r *buffer.Buffer) error { - count, err := readMapHeader(r) - if err != nil { - return err - } - - mm := make(mapAnyAny, count/2) - for i := uint32(0); i < count; i += 2 { - key, err := ReadAny(r) - if err != nil { - return err - } - value, err := ReadAny(r) - if err != nil { - return err - } - - // https://golang.org/ref/spec#Map_types: - // The comparison operators == and != must be fully defined - // for operands of the key type; thus the key type must not - // be a function, map, or slice. - switch reflect.ValueOf(key).Kind() { - case reflect.Slice, reflect.Func, reflect.Map: - return errors.New("invalid map key") - } - - mm[key] = value - } - *m = mm - return nil -} - -// mapStringAny is used to decode AMQP maps that have string keys -type mapStringAny map[string]any - -func (m mapStringAny) Marshal(wr *buffer.Buffer) error { - return writeMap(wr, map[string]any(m)) -} - -func (m *mapStringAny) Unmarshal(r *buffer.Buffer) error { - count, err := readMapHeader(r) - if err != nil { - return err - } - - mm := make(mapStringAny, count/2) - for i := uint32(0); i < count; i += 2 { - key, err := ReadString(r) - if err != nil { - return err - } - value, err := ReadAny(r) - if err != nil { - return err - } - mm[key] = value - } - *m = mm - - return nil -} - -// mapStringAny is used to decode AMQP maps that have Symbol keys -type mapSymbolAny map[Symbol]any - -func (m mapSymbolAny) Marshal(wr *buffer.Buffer) error { - return writeMap(wr, map[Symbol]any(m)) -} - -func (m *mapSymbolAny) Unmarshal(r *buffer.Buffer) error { - count, err := readMapHeader(r) - if err != nil { - return err - } - - mm := make(mapSymbolAny, count/2) - for i := uint32(0); i < count; i += 2 { - key, err := ReadString(r) - if err != nil { - return err - } - value, err := ReadAny(r) - if err != nil { - return err - } - mm[Symbol(key)] = value - } - *m = mm - return nil -} - -// UUID is a 128 bit identifier as defined in RFC 4122. -type UUID [16]byte - -// String returns the hex encoded representation described in RFC 4122, Section 3. -func (u UUID) String() string { - var buf [36]byte - hex.Encode(buf[:8], u[:4]) - buf[8] = '-' - hex.Encode(buf[9:13], u[4:6]) - buf[13] = '-' - hex.Encode(buf[14:18], u[6:8]) - buf[18] = '-' - hex.Encode(buf[19:23], u[8:10]) - buf[23] = '-' - hex.Encode(buf[24:], u[10:]) - return string(buf[:]) -} - -func (u UUID) Marshal(wr *buffer.Buffer) error { - wr.AppendByte(byte(TypeCodeUUID)) - wr.Append(u[:]) - return nil -} - -func (u *UUID) Unmarshal(r *buffer.Buffer) error { - un, err := readUUID(r) - *u = un - return err -} - -type LifetimePolicy uint8 - -const ( - DeleteOnClose = LifetimePolicy(TypeCodeDeleteOnClose) - DeleteOnNoLinks = LifetimePolicy(TypeCodeDeleteOnNoLinks) - DeleteOnNoMessages = LifetimePolicy(TypeCodeDeleteOnNoMessages) - DeleteOnNoLinksOrMessages = LifetimePolicy(TypeCodeDeleteOnNoLinksOrMessages) -) - -func (p LifetimePolicy) Marshal(wr *buffer.Buffer) error { - wr.Append([]byte{ - 0x0, - byte(TypeCodeSmallUlong), - byte(p), - byte(TypeCodeList0), - }) - return nil -} - -func (p *LifetimePolicy) Unmarshal(r *buffer.Buffer) error { - typ, fields, err := readCompositeHeader(r) - if err != nil { - return err - } - if fields != 0 { - return fmt.Errorf("invalid size %d for lifetime-policy", fields) - } - *p = LifetimePolicy(typ) - return nil -} - -type DescribedType struct { - Descriptor any - Value any -} - -func (t DescribedType) Marshal(wr *buffer.Buffer) error { - wr.AppendByte(0x0) // descriptor constructor - err := Marshal(wr, t.Descriptor) - if err != nil { - return err - } - return Marshal(wr, t.Value) -} - -func (t *DescribedType) Unmarshal(r *buffer.Buffer) error { - b, err := r.ReadByte() - if err != nil { - return err - } - - if b != 0x0 { - return fmt.Errorf("invalid described type header %02x", b) - } - - err = Unmarshal(r, &t.Descriptor) - if err != nil { - return err - } - return Unmarshal(r, &t.Value) -} - -func (t DescribedType) String() string { - return fmt.Sprintf("DescribedType{descriptor: %v, value: %v}", - t.Descriptor, - t.Value, - ) -} - -// SLICES - -// ArrayUByte allows encoding []uint8/[]byte as an array -// rather than binary data. -type ArrayUByte []uint8 - -func (a ArrayUByte) Marshal(wr *buffer.Buffer) error { - const typeSize = 1 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeUbyte) - wr.Append(a) - - return nil -} - -func (a *ArrayUByte) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeUbyte { - return fmt.Errorf("invalid type for []uint16 %02x", type_) - } - - buf, ok := r.Next(length) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - *a = append([]byte(nil), buf...) - - return nil -} - -type arrayInt8 []int8 - -func (a arrayInt8) Marshal(wr *buffer.Buffer) error { - const typeSize = 1 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeByte) - - for _, value := range a { - wr.AppendByte(uint8(value)) - } - - return nil -} - -func (a *arrayInt8) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeByte { - return fmt.Errorf("invalid type for []uint16 %02x", type_) - } - - buf, ok := r.Next(length) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]int8, length) - } else { - aa = aa[:length] - } - - for i, value := range buf { - aa[i] = int8(value) - } - - *a = aa - return nil -} - -type arrayUint16 []uint16 - -func (a arrayUint16) Marshal(wr *buffer.Buffer) error { - const typeSize = 2 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeUshort) - - for _, element := range a { - wr.AppendUint16(element) - } - - return nil -} - -func (a *arrayUint16) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeUshort { - return fmt.Errorf("invalid type for []uint16 %02x", type_) - } - - const typeSize = 2 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]uint16, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - aa[i] = binary.BigEndian.Uint16(buf[bufIdx:]) - bufIdx += 2 - } - - *a = aa - return nil -} - -type arrayInt16 []int16 - -func (a arrayInt16) Marshal(wr *buffer.Buffer) error { - const typeSize = 2 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeShort) - - for _, element := range a { - wr.AppendUint16(uint16(element)) - } - - return nil -} - -func (a *arrayInt16) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeShort { - return fmt.Errorf("invalid type for []uint16 %02x", type_) - } - - const typeSize = 2 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]int16, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - aa[i] = int16(binary.BigEndian.Uint16(buf[bufIdx : bufIdx+2])) - bufIdx += 2 - } - - *a = aa - return nil -} - -type arrayUint32 []uint32 - -func (a arrayUint32) Marshal(wr *buffer.Buffer) error { - var ( - typeSize = 1 - TypeCode = TypeCodeSmallUint - ) - for _, n := range a { - if n > math.MaxUint8 { - typeSize = 4 - TypeCode = TypeCodeUint - break - } - } - - writeArrayHeader(wr, len(a), typeSize, TypeCode) - - if TypeCode == TypeCodeUint { - for _, element := range a { - wr.AppendUint32(element) - } - } else { - for _, element := range a { - wr.AppendByte(byte(element)) - } - } - - return nil -} - -func (a *arrayUint32) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - aa := (*a)[:0] - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeUint0: - if int64(cap(aa)) < length { - aa = make([]uint32, length) - } else { - aa = aa[:length] - for i := range aa { - aa[i] = 0 - } - } - case TypeCodeSmallUint: - buf, ok := r.Next(length) - if !ok { - return errors.New("invalid length") - } - - if int64(cap(aa)) < length { - aa = make([]uint32, length) - } else { - aa = aa[:length] - } - - for i, n := range buf { - aa[i] = uint32(n) - } - case TypeCodeUint: - const typeSize = 4 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - if int64(cap(aa)) < length { - aa = make([]uint32, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - aa[i] = binary.BigEndian.Uint32(buf[bufIdx : bufIdx+4]) - bufIdx += 4 - } - default: - return fmt.Errorf("invalid type for []uint32 %02x", type_) - } - - *a = aa - return nil -} - -type arrayInt32 []int32 - -func (a arrayInt32) Marshal(wr *buffer.Buffer) error { - var ( - typeSize = 1 - TypeCode = TypeCodeSmallint - ) - for _, n := range a { - if n > math.MaxInt8 { - typeSize = 4 - TypeCode = TypeCodeInt - break - } - } - - writeArrayHeader(wr, len(a), typeSize, TypeCode) - - if TypeCode == TypeCodeInt { - for _, element := range a { - wr.AppendUint32(uint32(element)) - } - } else { - for _, element := range a { - wr.AppendByte(byte(element)) - } - } - - return nil -} - -func (a *arrayInt32) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - aa := (*a)[:0] - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeSmallint: - buf, ok := r.Next(length) - if !ok { - return errors.New("invalid length") - } - - if int64(cap(aa)) < length { - aa = make([]int32, length) - } else { - aa = aa[:length] - } - - for i, n := range buf { - aa[i] = int32(int8(n)) - } - case TypeCodeInt: - const typeSize = 4 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - if int64(cap(aa)) < length { - aa = make([]int32, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - aa[i] = int32(binary.BigEndian.Uint32(buf[bufIdx:])) - bufIdx += 4 - } - default: - return fmt.Errorf("invalid type for []int32 %02x", type_) - } - - *a = aa - return nil -} - -type arrayUint64 []uint64 - -func (a arrayUint64) Marshal(wr *buffer.Buffer) error { - var ( - typeSize = 1 - TypeCode = TypeCodeSmallUlong - ) - for _, n := range a { - if n > math.MaxUint8 { - typeSize = 8 - TypeCode = TypeCodeUlong - break - } - } - - writeArrayHeader(wr, len(a), typeSize, TypeCode) - - if TypeCode == TypeCodeUlong { - for _, element := range a { - wr.AppendUint64(element) - } - } else { - for _, element := range a { - wr.AppendByte(byte(element)) - } - } - - return nil -} - -func (a *arrayUint64) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - aa := (*a)[:0] - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeUlong0: - if int64(cap(aa)) < length { - aa = make([]uint64, length) - } else { - aa = aa[:length] - for i := range aa { - aa[i] = 0 - } - } - case TypeCodeSmallUlong: - buf, ok := r.Next(length) - if !ok { - return errors.New("invalid length") - } - - if int64(cap(aa)) < length { - aa = make([]uint64, length) - } else { - aa = aa[:length] - } - - for i, n := range buf { - aa[i] = uint64(n) - } - case TypeCodeUlong: - const typeSize = 8 - buf, ok := r.Next(length * typeSize) - if !ok { - return errors.New("invalid length") - } - - if int64(cap(aa)) < length { - aa = make([]uint64, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - aa[i] = binary.BigEndian.Uint64(buf[bufIdx : bufIdx+8]) - bufIdx += 8 - } - default: - return fmt.Errorf("invalid type for []uint64 %02x", type_) - } - - *a = aa - return nil -} - -type arrayInt64 []int64 - -func (a arrayInt64) Marshal(wr *buffer.Buffer) error { - var ( - typeSize = 1 - TypeCode = TypeCodeSmalllong - ) - for _, n := range a { - if n > math.MaxInt8 { - typeSize = 8 - TypeCode = TypeCodeLong - break - } - } - - writeArrayHeader(wr, len(a), typeSize, TypeCode) - - if TypeCode == TypeCodeLong { - for _, element := range a { - wr.AppendUint64(uint64(element)) - } - } else { - for _, element := range a { - wr.AppendByte(byte(element)) - } - } - - return nil -} - -func (a *arrayInt64) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - aa := (*a)[:0] - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeSmalllong: - buf, ok := r.Next(length) - if !ok { - return errors.New("invalid length") - } - - if int64(cap(aa)) < length { - aa = make([]int64, length) - } else { - aa = aa[:length] - } - - for i, n := range buf { - aa[i] = int64(int8(n)) - } - case TypeCodeLong: - const typeSize = 8 - buf, ok := r.Next(length * typeSize) - if !ok { - return errors.New("invalid length") - } - - if int64(cap(aa)) < length { - aa = make([]int64, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - aa[i] = int64(binary.BigEndian.Uint64(buf[bufIdx:])) - bufIdx += 8 - } - default: - return fmt.Errorf("invalid type for []uint64 %02x", type_) - } - - *a = aa - return nil -} - -type arrayFloat []float32 - -func (a arrayFloat) Marshal(wr *buffer.Buffer) error { - const typeSize = 4 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeFloat) - - for _, element := range a { - wr.AppendUint32(math.Float32bits(element)) - } - - return nil -} - -func (a *arrayFloat) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeFloat { - return fmt.Errorf("invalid type for []float32 %02x", type_) - } - - const typeSize = 4 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]float32, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - bits := binary.BigEndian.Uint32(buf[bufIdx:]) - aa[i] = math.Float32frombits(bits) - bufIdx += typeSize - } - - *a = aa - return nil -} - -type arrayDouble []float64 - -func (a arrayDouble) Marshal(wr *buffer.Buffer) error { - const typeSize = 8 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeDouble) - - for _, element := range a { - wr.AppendUint64(math.Float64bits(element)) - } - - return nil -} - -func (a *arrayDouble) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeDouble { - return fmt.Errorf("invalid type for []float64 %02x", type_) - } - - const typeSize = 8 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]float64, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - bits := binary.BigEndian.Uint64(buf[bufIdx:]) - aa[i] = math.Float64frombits(bits) - bufIdx += typeSize - } - - *a = aa - return nil -} - -type arrayBool []bool - -func (a arrayBool) Marshal(wr *buffer.Buffer) error { - const typeSize = 1 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeBool) - - for _, element := range a { - value := byte(0) - if element { - value = 1 - } - wr.AppendByte(value) - } - - return nil -} - -func (a *arrayBool) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]bool, length) - } else { - aa = aa[:length] - } - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeBool: - buf, ok := r.Next(length) - if !ok { - return errors.New("invalid length") - } - - for i, value := range buf { - if value == 0 { - aa[i] = false - } else { - aa[i] = true - } - } - - case TypeCodeBoolTrue: - for i := range aa { - aa[i] = true - } - case TypeCodeBoolFalse: - for i := range aa { - aa[i] = false - } - default: - return fmt.Errorf("invalid type for []bool %02x", type_) - } - - *a = aa - return nil -} - -type arrayString []string - -func (a arrayString) Marshal(wr *buffer.Buffer) error { - var ( - elementType = TypeCodeStr8 - elementsSizeTotal int - ) - for _, element := range a { - if !utf8.ValidString(element) { - return errors.New("not a valid UTF-8 string") - } - - elementsSizeTotal += len(element) - - if len(element) > math.MaxUint8 { - elementType = TypeCodeStr32 - } - } - - writeVariableArrayHeader(wr, len(a), elementsSizeTotal, elementType) - - if elementType == TypeCodeStr32 { - for _, element := range a { - wr.AppendUint32(uint32(len(element))) - wr.AppendString(element) - } - } else { - for _, element := range a { - wr.AppendByte(byte(len(element))) - wr.AppendString(element) - } - } - - return nil -} - -func (a *arrayString) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - const typeSize = 2 // assume all strings are at least 2 bytes - if length*typeSize > int64(r.Len()) { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]string, length) - } else { - aa = aa[:length] - } - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeStr8: - for i := range aa { - size, err := r.ReadByte() - if err != nil { - return err - } - - buf, ok := r.Next(int64(size)) - if !ok { - return errors.New("invalid length") - } - - aa[i] = string(buf) - } - case TypeCodeStr32: - for i := range aa { - buf, ok := r.Next(4) - if !ok { - return errors.New("invalid length") - } - size := int64(binary.BigEndian.Uint32(buf)) - - buf, ok = r.Next(size) - if !ok { - return errors.New("invalid length") - } - aa[i] = string(buf) - } - default: - return fmt.Errorf("invalid type for []string %02x", type_) - } - - *a = aa - return nil -} - -type arraySymbol []Symbol - -func (a arraySymbol) Marshal(wr *buffer.Buffer) error { - var ( - elementType = TypeCodeSym8 - elementsSizeTotal int - ) - for _, element := range a { - elementsSizeTotal += len(element) - - if len(element) > math.MaxUint8 { - elementType = TypeCodeSym32 - } - } - - writeVariableArrayHeader(wr, len(a), elementsSizeTotal, elementType) - - if elementType == TypeCodeSym32 { - for _, element := range a { - wr.AppendUint32(uint32(len(element))) - wr.AppendString(string(element)) - } - } else { - for _, element := range a { - wr.AppendByte(byte(len(element))) - wr.AppendString(string(element)) - } - } - - return nil -} - -func (a *arraySymbol) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - const typeSize = 2 // assume all symbols are at least 2 bytes - if length*typeSize > int64(r.Len()) { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]Symbol, length) - } else { - aa = aa[:length] - } - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeSym8: - for i := range aa { - size, err := r.ReadByte() - if err != nil { - return err - } - - buf, ok := r.Next(int64(size)) - if !ok { - return errors.New("invalid length") - } - aa[i] = Symbol(buf) - } - case TypeCodeSym32: - for i := range aa { - buf, ok := r.Next(4) - if !ok { - return errors.New("invalid length") - } - size := int64(binary.BigEndian.Uint32(buf)) - - buf, ok = r.Next(size) - if !ok { - return errors.New("invalid length") - } - aa[i] = Symbol(buf) - } - default: - return fmt.Errorf("invalid type for []Symbol %02x", type_) - } - - *a = aa - return nil -} - -type arrayBinary [][]byte - -func (a arrayBinary) Marshal(wr *buffer.Buffer) error { - var ( - elementType = TypeCodeVbin8 - elementsSizeTotal int - ) - for _, element := range a { - elementsSizeTotal += len(element) - - if len(element) > math.MaxUint8 { - elementType = TypeCodeVbin32 - } - } - - writeVariableArrayHeader(wr, len(a), elementsSizeTotal, elementType) - - if elementType == TypeCodeVbin32 { - for _, element := range a { - wr.AppendUint32(uint32(len(element))) - wr.Append(element) - } - } else { - for _, element := range a { - wr.AppendByte(byte(len(element))) - wr.Append(element) - } - } - - return nil -} - -func (a *arrayBinary) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - const typeSize = 2 // assume all binary is at least 2 bytes - if length*typeSize > int64(r.Len()) { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([][]byte, length) - } else { - aa = aa[:length] - } - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeVbin8: - for i := range aa { - size, err := r.ReadByte() - if err != nil { - return err - } - - buf, ok := r.Next(int64(size)) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - aa[i] = append([]byte(nil), buf...) - } - case TypeCodeVbin32: - for i := range aa { - buf, ok := r.Next(4) - if !ok { - return errors.New("invalid length") - } - size := binary.BigEndian.Uint32(buf) - - buf, ok = r.Next(int64(size)) - if !ok { - return errors.New("invalid length") - } - aa[i] = append([]byte(nil), buf...) - } - default: - return fmt.Errorf("invalid type for [][]byte %02x", type_) - } - - *a = aa - return nil -} - -type arrayTimestamp []time.Time - -func (a arrayTimestamp) Marshal(wr *buffer.Buffer) error { - const typeSize = 8 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeTimestamp) - - for _, element := range a { - ms := element.UnixNano() / int64(time.Millisecond) - wr.AppendUint64(uint64(ms)) - } - - return nil -} - -func (a *arrayTimestamp) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeTimestamp { - return fmt.Errorf("invalid type for []time.Time %02x", type_) - } - - const typeSize = 8 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]time.Time, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - ms := int64(binary.BigEndian.Uint64(buf[bufIdx:])) - bufIdx += typeSize - aa[i] = time.Unix(ms/1000, (ms%1000)*1000000).UTC() - } - - *a = aa - return nil -} - -type arrayUUID []UUID - -func (a arrayUUID) Marshal(wr *buffer.Buffer) error { - const typeSize = 16 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeUUID) - - for _, element := range a { - wr.Append(element[:]) - } - - return nil -} - -func (a *arrayUUID) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeUUID { - return fmt.Errorf("invalid type for []UUID %#02x", type_) - } - - const typeSize = 16 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]UUID, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - copy(aa[i][:], buf[bufIdx:bufIdx+16]) - bufIdx += 16 - } - - *a = aa - return nil -} - -// LIST - -type list []any - -func (l list) Marshal(wr *buffer.Buffer) error { - length := len(l) - - // type - if length == 0 { - wr.AppendByte(byte(TypeCodeList0)) - return nil - } - wr.AppendByte(byte(TypeCodeList32)) - - // size - sizeIdx := wr.Len() - wr.Append([]byte{0, 0, 0, 0}) - - // length - wr.AppendUint32(uint32(length)) - - for _, element := range l { - err := Marshal(wr, element) - if err != nil { - return err - } - } - - // overwrite size - binary.BigEndian.PutUint32(wr.Bytes()[sizeIdx:], uint32(wr.Len()-(sizeIdx+4))) - - return nil -} - -func (l *list) Unmarshal(r *buffer.Buffer) error { - length, err := readListHeader(r) - if err != nil { - return err - } - - // assume that all types are at least 1 byte - if length > int64(r.Len()) { - return fmt.Errorf("invalid length %d", length) - } - - ll := *l - if int64(cap(ll)) < length { - ll = make([]any, length) - } else { - ll = ll[:length] - } - - for i := range ll { - ll[i], err = ReadAny(r) - if err != nil { - return err - } - } - - *l = ll - return nil -} - -// multiSymbol can decode a single symbol or an array. -type MultiSymbol []Symbol - -func (ms MultiSymbol) Marshal(wr *buffer.Buffer) error { - return Marshal(wr, []Symbol(ms)) -} - -func (ms *MultiSymbol) Unmarshal(r *buffer.Buffer) error { - type_, err := peekType(r) - if err != nil { - return err - } - - if type_ == TypeCodeSym8 || type_ == TypeCodeSym32 { - s, err := ReadString(r) - if err != nil { - return err - } - - *ms = []Symbol{Symbol(s)} - return nil - } - - return Unmarshal(r, (*[]Symbol)(ms)) -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/internal/frames/frames.go b/sdk/messaging/azeventhubs/internal/go-amqp/internal/frames/frames.go deleted file mode 100644 index 63491582a85d..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/internal/frames/frames.go +++ /dev/null @@ -1,1543 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package frames - -import ( - "errors" - "fmt" - "strconv" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding" -) - -// Type contains the values for a frame's type. -type Type uint8 - -const ( - TypeAMQP Type = 0x0 - TypeSASL Type = 0x1 -) - -// String implements the fmt.Stringer interface for type Type. -func (t Type) String() string { - if t == 0 { - return "AMQP" - } - return "SASL" -} - -/* - - - - - - - - - - - - - - - - -*/ -type Source struct { - // the address of the source - // - // The address of the source MUST NOT be set when sent on a attach frame sent by - // the receiving link endpoint where the dynamic flag is set to true (that is where - // the receiver is requesting the sender to create an addressable node). - // - // The address of the source MUST be set when sent on a attach frame sent by the - // sending link endpoint where the dynamic flag is set to true (that is where the - // sender has created an addressable node at the request of the receiver and is now - // communicating the address of that created node). The generated name of the address - // SHOULD include the link name and the container-id of the remote container to allow - // for ease of identification. - Address string - - // indicates the durability of the terminus - // - // Indicates what state of the terminus will be retained durably: the state of durable - // messages, only existence and configuration of the terminus, or no state at all. - // - // 0: none - // 1: configuration - // 2: unsettled-state - Durable encoding.Durability - - // the expiry policy of the source - // - // link-detach: The expiry timer starts when terminus is detached. - // session-end: The expiry timer starts when the most recently associated session is - // ended. - // connection-close: The expiry timer starts when most recently associated connection - // is closed. - // never: The terminus never expires. - ExpiryPolicy encoding.ExpiryPolicy - - // duration that an expiring source will be retained - // - // The source starts expiring as indicated by the expiry-policy. - Timeout uint32 // seconds - - // request dynamic creation of a remote node - // - // When set to true by the receiving link endpoint, this field constitutes a request - // for the sending peer to dynamically create a node at the source. In this case the - // address field MUST NOT be set. - // - // When set to true by the sending link endpoint this field indicates creation of a - // dynamically created node. In this case the address field will contain the address - // of the created node. The generated address SHOULD include the link name and other - // available information on the initiator of the request (such as the remote - // container-id) in some recognizable form for ease of traceability. - Dynamic bool - - // properties of the dynamically created node - // - // If the dynamic field is not set to true this field MUST be left unset. - // - // When set by the receiving link endpoint, this field contains the desired - // properties of the node the receiver wishes to be created. When set by the - // sending link endpoint this field contains the actual properties of the - // dynamically created node. See subsection 3.5.9 for standard node properties. - // http://www.amqp.org/specification/1.0/node-properties - // - // lifetime-policy: The lifetime of a dynamically generated node. - // Definitionally, the lifetime will never be less than the lifetime - // of the link which caused its creation, however it is possible to - // extend the lifetime of dynamically created node using a lifetime - // policy. The value of this entry MUST be of a type which provides - // the lifetime-policy archetype. The following standard - // lifetime-policies are defined below: delete-on-close, - // delete-on-no-links, delete-on-no-messages or - // delete-on-no-links-or-messages. - // supported-dist-modes: The distribution modes that the node supports. - // The value of this entry MUST be one or more symbols which are valid - // distribution-modes. That is, the value MUST be of the same type as - // would be valid in a field defined with the following attributes: - // type="symbol" multiple="true" requires="distribution-mode" - DynamicNodeProperties map[encoding.Symbol]any // TODO: implement custom type with validation - - // the distribution mode of the link - // - // This field MUST be set by the sending end of the link if the endpoint supports more - // than one distribution-mode. This field MAY be set by the receiving end of the link - // to indicate a preference when a node supports multiple distribution modes. - DistributionMode encoding.Symbol - - // a set of predicates to filter the messages admitted onto the link - // - // The receiving endpoint sets its desired filter, the sending endpoint sets the filter - // actually in place (including any filters defaulted at the node). The receiving - // endpoint MUST check that the filter in place meets its needs and take responsibility - // for detaching if it does not. - Filter encoding.Filter - - // default outcome for unsettled transfers - // - // Indicates the outcome to be used for transfers that have not reached a terminal - // state at the receiver when the transfer is settled, including when the source - // is destroyed. The value MUST be a valid outcome (e.g., released or rejected). - DefaultOutcome any - - // descriptors for the outcomes that can be chosen on this link - // - // The values in this field are the symbolic descriptors of the outcomes that can - // be chosen on this link. This field MAY be empty, indicating that the default-outcome - // will be assumed for all message transfers (if the default-outcome is not set, and no - // outcomes are provided, then the accepted outcome MUST be supported by the source). - // - // When present, the values MUST be a symbolic descriptor of a valid outcome, - // e.g., "amqp:accepted:list". - Outcomes encoding.MultiSymbol - - // the extension capabilities the sender supports/desires - // - // http://www.amqp.org/specification/1.0/source-capabilities - Capabilities encoding.MultiSymbol -} - -func (s *Source) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeSource, []encoding.MarshalField{ - {Value: &s.Address, Omit: s.Address == ""}, - {Value: &s.Durable, Omit: s.Durable == encoding.DurabilityNone}, - {Value: &s.ExpiryPolicy, Omit: s.ExpiryPolicy == "" || s.ExpiryPolicy == encoding.ExpirySessionEnd}, - {Value: &s.Timeout, Omit: s.Timeout == 0}, - {Value: &s.Dynamic, Omit: !s.Dynamic}, - {Value: s.DynamicNodeProperties, Omit: len(s.DynamicNodeProperties) == 0}, - {Value: &s.DistributionMode, Omit: s.DistributionMode == ""}, - {Value: s.Filter, Omit: len(s.Filter) == 0}, - {Value: &s.DefaultOutcome, Omit: s.DefaultOutcome == nil}, - {Value: &s.Outcomes, Omit: len(s.Outcomes) == 0}, - {Value: &s.Capabilities, Omit: len(s.Capabilities) == 0}, - }) -} - -func (s *Source) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeSource, []encoding.UnmarshalField{ - {Field: &s.Address}, - {Field: &s.Durable}, - {Field: &s.ExpiryPolicy, HandleNull: func() error { s.ExpiryPolicy = encoding.ExpirySessionEnd; return nil }}, - {Field: &s.Timeout}, - {Field: &s.Dynamic}, - {Field: &s.DynamicNodeProperties}, - {Field: &s.DistributionMode}, - {Field: &s.Filter}, - {Field: &s.DefaultOutcome}, - {Field: &s.Outcomes}, - {Field: &s.Capabilities}, - }...) -} - -func (s Source) String() string { - return fmt.Sprintf("source{Address: %s, Durable: %d, ExpiryPolicy: %s, Timeout: %d, "+ - "Dynamic: %t, DynamicNodeProperties: %v, DistributionMode: %s, Filter: %v, DefaultOutcome: %v "+ - "Outcomes: %v, Capabilities: %v}", - s.Address, - s.Durable, - s.ExpiryPolicy, - s.Timeout, - s.Dynamic, - s.DynamicNodeProperties, - s.DistributionMode, - s.Filter, - s.DefaultOutcome, - s.Outcomes, - s.Capabilities, - ) -} - -/* - - - - - - - - - - - - -*/ -type Target struct { - // the address of the target - // - // The address of the target MUST NOT be set when sent on a attach frame sent by - // the sending link endpoint where the dynamic flag is set to true (that is where - // the sender is requesting the receiver to create an addressable node). - // - // The address of the source MUST be set when sent on a attach frame sent by the - // receiving link endpoint where the dynamic flag is set to true (that is where - // the receiver has created an addressable node at the request of the sender and - // is now communicating the address of that created node). The generated name of - // the address SHOULD include the link name and the container-id of the remote - // container to allow for ease of identification. - Address string - - // indicates the durability of the terminus - // - // Indicates what state of the terminus will be retained durably: the state of durable - // messages, only existence and configuration of the terminus, or no state at all. - // - // 0: none - // 1: configuration - // 2: unsettled-state - Durable encoding.Durability - - // the expiry policy of the target - // - // link-detach: The expiry timer starts when terminus is detached. - // session-end: The expiry timer starts when the most recently associated session is - // ended. - // connection-close: The expiry timer starts when most recently associated connection - // is closed. - // never: The terminus never expires. - ExpiryPolicy encoding.ExpiryPolicy - - // duration that an expiring target will be retained - // - // The target starts expiring as indicated by the expiry-policy. - Timeout uint32 // seconds - - // request dynamic creation of a remote node - // - // When set to true by the sending link endpoint, this field constitutes a request - // for the receiving peer to dynamically create a node at the target. In this case - // the address field MUST NOT be set. - // - // When set to true by the receiving link endpoint this field indicates creation of - // a dynamically created node. In this case the address field will contain the - // address of the created node. The generated address SHOULD include the link name - // and other available information on the initiator of the request (such as the - // remote container-id) in some recognizable form for ease of traceability. - Dynamic bool - - // properties of the dynamically created node - // - // If the dynamic field is not set to true this field MUST be left unset. - // - // When set by the sending link endpoint, this field contains the desired - // properties of the node the sender wishes to be created. When set by the - // receiving link endpoint this field contains the actual properties of the - // dynamically created node. See subsection 3.5.9 for standard node properties. - // http://www.amqp.org/specification/1.0/node-properties - // - // lifetime-policy: The lifetime of a dynamically generated node. - // Definitionally, the lifetime will never be less than the lifetime - // of the link which caused its creation, however it is possible to - // extend the lifetime of dynamically created node using a lifetime - // policy. The value of this entry MUST be of a type which provides - // the lifetime-policy archetype. The following standard - // lifetime-policies are defined below: delete-on-close, - // delete-on-no-links, delete-on-no-messages or - // delete-on-no-links-or-messages. - // supported-dist-modes: The distribution modes that the node supports. - // The value of this entry MUST be one or more symbols which are valid - // distribution-modes. That is, the value MUST be of the same type as - // would be valid in a field defined with the following attributes: - // type="symbol" multiple="true" requires="distribution-mode" - DynamicNodeProperties map[encoding.Symbol]any // TODO: implement custom type with validation - - // the extension capabilities the sender supports/desires - // - // http://www.amqp.org/specification/1.0/target-capabilities - Capabilities encoding.MultiSymbol -} - -func (t *Target) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeTarget, []encoding.MarshalField{ - {Value: &t.Address, Omit: t.Address == ""}, - {Value: &t.Durable, Omit: t.Durable == encoding.DurabilityNone}, - {Value: &t.ExpiryPolicy, Omit: t.ExpiryPolicy == "" || t.ExpiryPolicy == encoding.ExpirySessionEnd}, - {Value: &t.Timeout, Omit: t.Timeout == 0}, - {Value: &t.Dynamic, Omit: !t.Dynamic}, - {Value: t.DynamicNodeProperties, Omit: len(t.DynamicNodeProperties) == 0}, - {Value: &t.Capabilities, Omit: len(t.Capabilities) == 0}, - }) -} - -func (t *Target) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeTarget, []encoding.UnmarshalField{ - {Field: &t.Address}, - {Field: &t.Durable}, - {Field: &t.ExpiryPolicy, HandleNull: func() error { t.ExpiryPolicy = encoding.ExpirySessionEnd; return nil }}, - {Field: &t.Timeout}, - {Field: &t.Dynamic}, - {Field: &t.DynamicNodeProperties}, - {Field: &t.Capabilities}, - }...) -} - -func (t Target) String() string { - return fmt.Sprintf("source{Address: %s, Durable: %d, ExpiryPolicy: %s, Timeout: %d, "+ - "Dynamic: %t, DynamicNodeProperties: %v, Capabilities: %v}", - t.Address, - t.Durable, - t.ExpiryPolicy, - t.Timeout, - t.Dynamic, - t.DynamicNodeProperties, - t.Capabilities, - ) -} - -// frame is the decoded representation of a frame -type Frame struct { - Type Type // AMQP/SASL - Channel uint16 // channel this frame is for - Body FrameBody // body of the frame -} - -// String implements the fmt.Stringer interface for type Frame. -func (f Frame) String() string { - return fmt.Sprintf("Frame{Type: %s, Channel: %d, Body: %s}", f.Type, f.Channel, f.Body) -} - -// frameBody adds some type safety to frame encoding -type FrameBody interface { - frameBody() -} - -/* - - - - - - - - - - - - - -*/ - -type PerformOpen struct { - ContainerID string // required - Hostname string - MaxFrameSize uint32 // default: 4294967295 - ChannelMax uint16 // default: 65535 - IdleTimeout time.Duration // from milliseconds - OutgoingLocales encoding.MultiSymbol - IncomingLocales encoding.MultiSymbol - OfferedCapabilities encoding.MultiSymbol - DesiredCapabilities encoding.MultiSymbol - Properties map[encoding.Symbol]any -} - -func (o *PerformOpen) frameBody() {} - -func (o *PerformOpen) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeOpen, []encoding.MarshalField{ - {Value: &o.ContainerID, Omit: false}, - {Value: &o.Hostname, Omit: o.Hostname == ""}, - {Value: &o.MaxFrameSize, Omit: o.MaxFrameSize == 4294967295}, - {Value: &o.ChannelMax, Omit: o.ChannelMax == 65535}, - {Value: (*encoding.Milliseconds)(&o.IdleTimeout), Omit: o.IdleTimeout == 0}, - {Value: &o.OutgoingLocales, Omit: len(o.OutgoingLocales) == 0}, - {Value: &o.IncomingLocales, Omit: len(o.IncomingLocales) == 0}, - {Value: &o.OfferedCapabilities, Omit: len(o.OfferedCapabilities) == 0}, - {Value: &o.DesiredCapabilities, Omit: len(o.DesiredCapabilities) == 0}, - {Value: o.Properties, Omit: len(o.Properties) == 0}, - }) -} - -func (o *PerformOpen) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeOpen, []encoding.UnmarshalField{ - {Field: &o.ContainerID, HandleNull: func() error { return errors.New("Open.ContainerID is required") }}, - {Field: &o.Hostname}, - {Field: &o.MaxFrameSize, HandleNull: func() error { o.MaxFrameSize = 4294967295; return nil }}, - {Field: &o.ChannelMax, HandleNull: func() error { o.ChannelMax = 65535; return nil }}, - {Field: (*encoding.Milliseconds)(&o.IdleTimeout)}, - {Field: &o.OutgoingLocales}, - {Field: &o.IncomingLocales}, - {Field: &o.OfferedCapabilities}, - {Field: &o.DesiredCapabilities}, - {Field: &o.Properties}, - }...) -} - -func (o *PerformOpen) String() string { - return fmt.Sprintf("Open{ContainerID : %s, Hostname: %s, MaxFrameSize: %d, "+ - "ChannelMax: %d, IdleTimeout: %v, "+ - "OutgoingLocales: %v, IncomingLocales: %v, "+ - "OfferedCapabilities: %v, DesiredCapabilities: %v, "+ - "Properties: %v}", - o.ContainerID, - o.Hostname, - o.MaxFrameSize, - o.ChannelMax, - o.IdleTimeout, - o.OutgoingLocales, - o.IncomingLocales, - o.OfferedCapabilities, - o.DesiredCapabilities, - o.Properties, - ) -} - -/* - - - - - - - - - - - - - -*/ -type PerformBegin struct { - // the remote channel for this session - // If a session is locally initiated, the remote-channel MUST NOT be set. - // When an endpoint responds to a remotely initiated session, the remote-channel - // MUST be set to the channel on which the remote session sent the begin. - RemoteChannel *uint16 - - // the transfer-id of the first transfer id the sender will send - NextOutgoingID uint32 // required, sequence number http://www.ietf.org/rfc/rfc1982.txt - - // the initial incoming-window of the sender - IncomingWindow uint32 // required - - // the initial outgoing-window of the sender - OutgoingWindow uint32 // required - - // the maximum handle value that can be used on the session - // The handle-max value is the highest handle value that can be - // used on the session. A peer MUST NOT attempt to attach a link - // using a handle value outside the range that its partner can handle. - // A peer that receives a handle outside the supported range MUST - // close the connection with the framing-error error-code. - HandleMax uint32 // default 4294967295 - - // the extension capabilities the sender supports - // http://www.amqp.org/specification/1.0/session-capabilities - OfferedCapabilities encoding.MultiSymbol - - // the extension capabilities the sender can use if the receiver supports them - // The sender MUST NOT attempt to use any capability other than those it - // has declared in desired-capabilities field. - DesiredCapabilities encoding.MultiSymbol - - // session properties - // http://www.amqp.org/specification/1.0/session-properties - Properties map[encoding.Symbol]any -} - -func (b *PerformBegin) frameBody() {} - -func (b *PerformBegin) String() string { - return fmt.Sprintf("Begin{RemoteChannel: %v, NextOutgoingID: %d, IncomingWindow: %d, "+ - "OutgoingWindow: %d, HandleMax: %d, OfferedCapabilities: %v, DesiredCapabilities: %v, "+ - "Properties: %v}", - formatUint16Ptr(b.RemoteChannel), - b.NextOutgoingID, - b.IncomingWindow, - b.OutgoingWindow, - b.HandleMax, - b.OfferedCapabilities, - b.DesiredCapabilities, - b.Properties, - ) -} - -func formatUint16Ptr(p *uint16) string { - if p == nil { - return "" - } - return strconv.FormatUint(uint64(*p), 10) -} - -func (b *PerformBegin) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeBegin, []encoding.MarshalField{ - {Value: b.RemoteChannel, Omit: b.RemoteChannel == nil}, - {Value: &b.NextOutgoingID, Omit: false}, - {Value: &b.IncomingWindow, Omit: false}, - {Value: &b.OutgoingWindow, Omit: false}, - {Value: &b.HandleMax, Omit: b.HandleMax == 4294967295}, - {Value: &b.OfferedCapabilities, Omit: len(b.OfferedCapabilities) == 0}, - {Value: &b.DesiredCapabilities, Omit: len(b.DesiredCapabilities) == 0}, - {Value: b.Properties, Omit: b.Properties == nil}, - }) -} - -func (b *PerformBegin) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeBegin, []encoding.UnmarshalField{ - {Field: &b.RemoteChannel}, - {Field: &b.NextOutgoingID, HandleNull: func() error { return errors.New("Begin.NextOutgoingID is required") }}, - {Field: &b.IncomingWindow, HandleNull: func() error { return errors.New("Begin.IncomingWindow is required") }}, - {Field: &b.OutgoingWindow, HandleNull: func() error { return errors.New("Begin.OutgoingWindow is required") }}, - {Field: &b.HandleMax, HandleNull: func() error { b.HandleMax = 4294967295; return nil }}, - {Field: &b.OfferedCapabilities}, - {Field: &b.DesiredCapabilities}, - {Field: &b.Properties}, - }...) -} - -/* - - - - - - - - - - - - - - - - - - - -*/ -type PerformAttach struct { - // the name of the link - // - // This name uniquely identifies the link from the container of the source - // to the container of the target node, e.g., if the container of the source - // node is A, and the container of the target node is B, the link MAY be - // globally identified by the (ordered) tuple (A,B,). - Name string // required - - // the handle for the link while attached - // - // The numeric handle assigned by the the peer as a shorthand to refer to the - // link in all performatives that reference the link until the it is detached. - // - // The handle MUST NOT be used for other open links. An attempt to attach using - // a handle which is already associated with a link MUST be responded to with - // an immediate close carrying a handle-in-use session-error. - // - // To make it easier to monitor AMQP link attach frames, it is RECOMMENDED that - // implementations always assign the lowest available handle to this field. - // - // The two endpoints MAY potentially use different handles to refer to the same link. - // Link handles MAY be reused once a link is closed for both send and receive. - Handle uint32 // required - - // role of the link endpoint - // - // The role being played by the peer, i.e., whether the peer is the sender or the - // receiver of messages on the link. - Role encoding.Role - - // settlement policy for the sender - // - // The delivery settlement policy for the sender. When set at the receiver this - // indicates the desired value for the settlement mode at the sender. When set - // at the sender this indicates the actual settlement mode in use. The sender - // SHOULD respect the receiver's desired settlement mode if the receiver initiates - // the attach exchange and the sender supports the desired mode. - // - // 0: unsettled - The sender will send all deliveries initially unsettled to the receiver. - // 1: settled - The sender will send all deliveries settled to the receiver. - // 2: mixed - The sender MAY send a mixture of settled and unsettled deliveries to the receiver. - SenderSettleMode *encoding.SenderSettleMode - - // the settlement policy of the receiver - // - // The delivery settlement policy for the receiver. When set at the sender this - // indicates the desired value for the settlement mode at the receiver. - // When set at the receiver this indicates the actual settlement mode in use. - // The receiver SHOULD respect the sender's desired settlement mode if the sender - // initiates the attach exchange and the receiver supports the desired mode. - // - // 0: first - The receiver will spontaneously settle all incoming transfers. - // 1: second - The receiver will only settle after sending the disposition to - // the sender and receiving a disposition indicating settlement of - // the delivery from the sender. - ReceiverSettleMode *encoding.ReceiverSettleMode - - // the source for messages - // - // If no source is specified on an outgoing link, then there is no source currently - // attached to the link. A link with no source will never produce outgoing messages. - Source *Source - - // the target for messages - // - // If no target is specified on an incoming link, then there is no target currently - // attached to the link. A link with no target will never permit incoming messages. - Target *Target - - // unsettled delivery state - // - // This is used to indicate any unsettled delivery states when a suspended link is - // resumed. The map is keyed by delivery-tag with values indicating the delivery state. - // The local and remote delivery states for a given delivery-tag MUST be compared to - // resolve any in-doubt deliveries. If necessary, deliveries MAY be resent, or resumed - // based on the outcome of this comparison. See subsection 2.6.13. - // - // If the local unsettled map is too large to be encoded within a frame of the agreed - // maximum frame size then the session MAY be ended with the frame-size-too-small error. - // The endpoint SHOULD make use of the ability to send an incomplete unsettled map - // (see below) to avoid sending an error. - // - // The unsettled map MUST NOT contain null valued keys. - // - // When reattaching (as opposed to resuming), the unsettled map MUST be null. - Unsettled encoding.Unsettled - - // If set to true this field indicates that the unsettled map provided is not complete. - // When the map is incomplete the recipient of the map cannot take the absence of a - // delivery tag from the map as evidence of settlement. On receipt of an incomplete - // unsettled map a sending endpoint MUST NOT send any new deliveries (i.e. deliveries - // where resume is not set to true) to its partner (and a receiving endpoint which sent - // an incomplete unsettled map MUST detach with an error on receiving a transfer which - // does not have the resume flag set to true). - // - // Note that if this flag is set to true then the endpoints MUST detach and reattach at - // least once in order to send new deliveries. This flag can be useful when there are - // too many entries in the unsettled map to fit within a single frame. An endpoint can - // attach, resume, settle, and detach until enough unsettled state has been cleared for - // an attach where this flag is set to false. - IncompleteUnsettled bool // default: false - - // the sender's initial value for delivery-count - // - // This MUST NOT be null if role is sender, and it is ignored if the role is receiver. - InitialDeliveryCount uint32 // sequence number - - // the maximum message size supported by the link endpoint - // - // This field indicates the maximum message size supported by the link endpoint. - // Any attempt to deliver a message larger than this results in a message-size-exceeded - // link-error. If this field is zero or unset, there is no maximum size imposed by the - // link endpoint. - MaxMessageSize uint64 - - // the extension capabilities the sender supports - // http://www.amqp.org/specification/1.0/link-capabilities - OfferedCapabilities encoding.MultiSymbol - - // the extension capabilities the sender can use if the receiver supports them - // - // The sender MUST NOT attempt to use any capability other than those it - // has declared in desired-capabilities field. - DesiredCapabilities encoding.MultiSymbol - - // link properties - // http://www.amqp.org/specification/1.0/link-properties - Properties map[encoding.Symbol]any -} - -func (a *PerformAttach) frameBody() {} - -func (a PerformAttach) String() string { - return fmt.Sprintf("Attach{Name: %s, Handle: %d, Role: %s, SenderSettleMode: %s, ReceiverSettleMode: %s, "+ - "Source: %v, Target: %v, Unsettled: %v, IncompleteUnsettled: %t, InitialDeliveryCount: %d, MaxMessageSize: %d, "+ - "OfferedCapabilities: %v, DesiredCapabilities: %v, Properties: %v}", - a.Name, - a.Handle, - a.Role, - a.SenderSettleMode, - a.ReceiverSettleMode, - a.Source, - a.Target, - a.Unsettled, - a.IncompleteUnsettled, - a.InitialDeliveryCount, - a.MaxMessageSize, - a.OfferedCapabilities, - a.DesiredCapabilities, - a.Properties, - ) -} - -func (a *PerformAttach) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeAttach, []encoding.MarshalField{ - {Value: &a.Name, Omit: false}, - {Value: &a.Handle, Omit: false}, - {Value: &a.Role, Omit: false}, - {Value: a.SenderSettleMode, Omit: a.SenderSettleMode == nil}, - {Value: a.ReceiverSettleMode, Omit: a.ReceiverSettleMode == nil}, - {Value: a.Source, Omit: a.Source == nil}, - {Value: a.Target, Omit: a.Target == nil}, - {Value: a.Unsettled, Omit: len(a.Unsettled) == 0}, - {Value: &a.IncompleteUnsettled, Omit: !a.IncompleteUnsettled}, - {Value: &a.InitialDeliveryCount, Omit: a.Role == encoding.RoleReceiver}, - {Value: &a.MaxMessageSize, Omit: a.MaxMessageSize == 0}, - {Value: &a.OfferedCapabilities, Omit: len(a.OfferedCapabilities) == 0}, - {Value: &a.DesiredCapabilities, Omit: len(a.DesiredCapabilities) == 0}, - {Value: a.Properties, Omit: len(a.Properties) == 0}, - }) -} - -func (a *PerformAttach) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeAttach, []encoding.UnmarshalField{ - {Field: &a.Name, HandleNull: func() error { return errors.New("Attach.Name is required") }}, - {Field: &a.Handle, HandleNull: func() error { return errors.New("Attach.Handle is required") }}, - {Field: &a.Role, HandleNull: func() error { return errors.New("Attach.Role is required") }}, - {Field: &a.SenderSettleMode}, - {Field: &a.ReceiverSettleMode}, - {Field: &a.Source}, - {Field: &a.Target}, - {Field: &a.Unsettled}, - {Field: &a.IncompleteUnsettled}, - {Field: &a.InitialDeliveryCount}, - {Field: &a.MaxMessageSize}, - {Field: &a.OfferedCapabilities}, - {Field: &a.DesiredCapabilities}, - {Field: &a.Properties}, - }...) -} - -/* - - - - - - - - - - - - - - - - -*/ -type PerformFlow struct { - // Identifies the expected transfer-id of the next incoming transfer frame. - // This value MUST be set if the peer has received the begin frame for the - // session, and MUST NOT be set if it has not. See subsection 2.5.6 for more details. - NextIncomingID *uint32 // sequence number - - // Defines the maximum number of incoming transfer frames that the endpoint - // can currently receive. See subsection 2.5.6 for more details. - IncomingWindow uint32 // required - - // The transfer-id that will be assigned to the next outgoing transfer frame. - // See subsection 2.5.6 for more details. - NextOutgoingID uint32 // sequence number - - // Defines the maximum number of outgoing transfer frames that the endpoint - // could potentially currently send, if it was not constrained by restrictions - // imposed by its peer's incoming-window. See subsection 2.5.6 for more details. - OutgoingWindow uint32 - - // If set, indicates that the flow frame carries flow state information for the local - // link endpoint associated with the given handle. If not set, the flow frame is - // carrying only information pertaining to the session endpoint. - // - // If set to a handle that is not currently associated with an attached link, - // the recipient MUST respond by ending the session with an unattached-handle - // session error. - Handle *uint32 - - // The delivery-count is initialized by the sender when a link endpoint is created, - // and is incremented whenever a message is sent. Only the sender MAY independently - // modify this field. The receiver's value is calculated based on the last known - // value from the sender and any subsequent messages received on the link. Note that, - // despite its name, the delivery-count is not a count but a sequence number - // initialized at an arbitrary point by the sender. - // - // When the handle field is not set, this field MUST NOT be set. - // - // When the handle identifies that the flow state is being sent from the sender link - // endpoint to receiver link endpoint this field MUST be set to the current - // delivery-count of the link endpoint. - // - // When the flow state is being sent from the receiver endpoint to the sender endpoint - // this field MUST be set to the last known value of the corresponding sending endpoint. - // In the event that the receiving link endpoint has not yet seen the initial attach - // frame from the sender this field MUST NOT be set. - DeliveryCount *uint32 // sequence number - - // the current maximum number of messages that can be received - // - // The current maximum number of messages that can be handled at the receiver endpoint - // of the link. Only the receiver endpoint can independently set this value. The sender - // endpoint sets this to the last known value seen from the receiver. - // See subsection 2.6.7 for more details. - // - // When the handle field is not set, this field MUST NOT be set. - LinkCredit *uint32 - - // the number of available messages - // - // The number of messages awaiting credit at the link sender endpoint. Only the sender - // can independently set this value. The receiver sets this to the last known value seen - // from the sender. See subsection 2.6.7 for more details. - // - // When the handle field is not set, this field MUST NOT be set. - Available *uint32 - - // indicates drain mode - // - // When flow state is sent from the sender to the receiver, this field contains the - // actual drain mode of the sender. When flow state is sent from the receiver to the - // sender, this field contains the desired drain mode of the receiver. - // See subsection 2.6.7 for more details. - // - // When the handle field is not set, this field MUST NOT be set. - Drain bool - - // request state from partner - // - // If set to true then the receiver SHOULD send its state at the earliest convenient - // opportunity. - // - // If set to true, and the handle field is not set, then the sender only requires - // session endpoint state to be echoed, however, the receiver MAY fulfil this requirement - // by sending a flow performative carrying link-specific state (since any such flow also - // carries session state). - // - // If a sender makes multiple requests for the same state before the receiver can reply, - // the receiver MAY send only one flow in return. - // - // Note that if a peer responds to echo requests with flows which themselves have the - // echo field set to true, an infinite loop could result if its partner adopts the same - // policy (therefore such a policy SHOULD be avoided). - Echo bool - - // link state properties - // http://www.amqp.org/specification/1.0/link-state-properties - Properties map[encoding.Symbol]any -} - -func (f *PerformFlow) frameBody() {} - -func (f *PerformFlow) String() string { - return fmt.Sprintf("Flow{NextIncomingID: %s, IncomingWindow: %d, NextOutgoingID: %d, OutgoingWindow: %d, "+ - "Handle: %s, DeliveryCount: %s, LinkCredit: %s, Available: %s, Drain: %t, Echo: %t, Properties: %+v}", - formatUint32Ptr(f.NextIncomingID), - f.IncomingWindow, - f.NextOutgoingID, - f.OutgoingWindow, - formatUint32Ptr(f.Handle), - formatUint32Ptr(f.DeliveryCount), - formatUint32Ptr(f.LinkCredit), - formatUint32Ptr(f.Available), - f.Drain, - f.Echo, - f.Properties, - ) -} - -func formatUint32Ptr(p *uint32) string { - if p == nil { - return "" - } - return strconv.FormatUint(uint64(*p), 10) -} - -func (f *PerformFlow) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeFlow, []encoding.MarshalField{ - {Value: f.NextIncomingID, Omit: f.NextIncomingID == nil}, - {Value: &f.IncomingWindow, Omit: false}, - {Value: &f.NextOutgoingID, Omit: false}, - {Value: &f.OutgoingWindow, Omit: false}, - {Value: f.Handle, Omit: f.Handle == nil}, - {Value: f.DeliveryCount, Omit: f.DeliveryCount == nil}, - {Value: f.LinkCredit, Omit: f.LinkCredit == nil}, - {Value: f.Available, Omit: f.Available == nil}, - {Value: &f.Drain, Omit: !f.Drain}, - {Value: &f.Echo, Omit: !f.Echo}, - {Value: f.Properties, Omit: len(f.Properties) == 0}, - }) -} - -func (f *PerformFlow) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeFlow, []encoding.UnmarshalField{ - {Field: &f.NextIncomingID}, - {Field: &f.IncomingWindow, HandleNull: func() error { return errors.New("Flow.IncomingWindow is required") }}, - {Field: &f.NextOutgoingID, HandleNull: func() error { return errors.New("Flow.NextOutgoingID is required") }}, - {Field: &f.OutgoingWindow, HandleNull: func() error { return errors.New("Flow.OutgoingWindow is required") }}, - {Field: &f.Handle}, - {Field: &f.DeliveryCount}, - {Field: &f.LinkCredit}, - {Field: &f.Available}, - {Field: &f.Drain}, - {Field: &f.Echo}, - {Field: &f.Properties}, - }...) -} - -/* - - - - - - - - - - - - - - - - -*/ -type PerformTransfer struct { - // Specifies the link on which the message is transferred. - Handle uint32 // required - - // The delivery-id MUST be supplied on the first transfer of a multi-transfer - // delivery. On continuation transfers the delivery-id MAY be omitted. It is - // an error if the delivery-id on a continuation transfer differs from the - // delivery-id on the first transfer of a delivery. - DeliveryID *uint32 // sequence number - - // Uniquely identifies the delivery attempt for a given message on this link. - // This field MUST be specified for the first transfer of a multi-transfer - // message and can only be omitted for continuation transfers. It is an error - // if the delivery-tag on a continuation transfer differs from the delivery-tag - // on the first transfer of a delivery. - DeliveryTag []byte // up to 32 bytes - - // This field MUST be specified for the first transfer of a multi-transfer message - // and can only be omitted for continuation transfers. It is an error if the - // message-format on a continuation transfer differs from the message-format on - // the first transfer of a delivery. - // - // The upper three octets of a message format code identify a particular message - // format. The lowest octet indicates the version of said message format. Any given - // version of a format is forwards compatible with all higher versions. - MessageFormat *uint32 - - // If not set on the first (or only) transfer for a (multi-transfer) delivery, - // then the settled flag MUST be interpreted as being false. For subsequent - // transfers in a multi-transfer delivery if the settled flag is left unset then - // it MUST be interpreted as true if and only if the value of the settled flag on - // any of the preceding transfers was true; if no preceding transfer was sent with - // settled being true then the value when unset MUST be taken as false. - // - // If the negotiated value for snd-settle-mode at attachment is settled, then this - // field MUST be true on at least one transfer frame for a delivery (i.e., the - // delivery MUST be settled at the sender at the point the delivery has been - // completely transferred). - // - // If the negotiated value for snd-settle-mode at attachment is unsettled, then this - // field MUST be false (or unset) on every transfer frame for a delivery (unless the - // delivery is aborted). - Settled bool - - // indicates that the message has more content - // - // Note that if both the more and aborted fields are set to true, the aborted flag - // takes precedence. That is, a receiver SHOULD ignore the value of the more field - // if the transfer is marked as aborted. A sender SHOULD NOT set the more flag to - // true if it also sets the aborted flag to true. - More bool - - // If first, this indicates that the receiver MUST settle the delivery once it has - // arrived without waiting for the sender to settle first. - // - // If second, this indicates that the receiver MUST NOT settle until sending its - // disposition to the sender and receiving a settled disposition from the sender. - // - // If not set, this value is defaulted to the value negotiated on link attach. - // - // If the negotiated link value is first, then it is illegal to set this field - // to second. - // - // If the message is being sent settled by the sender, the value of this field - // is ignored. - // - // The (implicit or explicit) value of this field does not form part of the - // transfer state, and is not retained if a link is suspended and subsequently resumed. - // - // 0: first - The receiver will spontaneously settle all incoming transfers. - // 1: second - The receiver will only settle after sending the disposition to - // the sender and receiving a disposition indicating settlement of - // the delivery from the sender. - ReceiverSettleMode *encoding.ReceiverSettleMode - - // the state of the delivery at the sender - // - // When set this informs the receiver of the state of the delivery at the sender. - // This is particularly useful when transfers of unsettled deliveries are resumed - // after resuming a link. Setting the state on the transfer can be thought of as - // being equivalent to sending a disposition immediately before the transfer - // performative, i.e., it is the state of the delivery (not the transfer) that - // existed at the point the frame was sent. - // - // Note that if the transfer performative (or an earlier disposition performative - // referring to the delivery) indicates that the delivery has attained a terminal - // state, then no future transfer or disposition sent by the sender can alter that - // terminal state. - State encoding.DeliveryState - - // indicates a resumed delivery - // - // If true, the resume flag indicates that the transfer is being used to reassociate - // an unsettled delivery from a dissociated link endpoint. See subsection 2.6.13 - // for more details. - // - // The receiver MUST ignore resumed deliveries that are not in its local unsettled map. - // The sender MUST NOT send resumed transfers for deliveries not in its local - // unsettled map. - // - // If a resumed delivery spans more than one transfer performative, then the resume - // flag MUST be set to true on the first transfer of the resumed delivery. For - // subsequent transfers for the same delivery the resume flag MAY be set to true, - // or MAY be omitted. - // - // In the case where the exchange of unsettled maps makes clear that all message - // data has been successfully transferred to the receiver, and that only the final - // state (and potentially settlement) at the sender needs to be conveyed, then a - // resumed delivery MAY carry no payload and instead act solely as a vehicle for - // carrying the terminal state of the delivery at the sender. - Resume bool - - // indicates that the message is aborted - // - // Aborted messages SHOULD be discarded by the recipient (any payload within the - // frame carrying the performative MUST be ignored). An aborted message is - // implicitly settled. - Aborted bool - - // batchable hint - // - // If true, then the issuer is hinting that there is no need for the peer to urgently - // communicate updated delivery state. This hint MAY be used to artificially increase - // the amount of batching an implementation uses when communicating delivery states, - // and thereby save bandwidth. - // - // If the message being delivered is too large to fit within a single frame, then the - // setting of batchable to true on any of the transfer performatives for the delivery - // is equivalent to setting batchable to true for all the transfer performatives for - // the delivery. - // - // The batchable value does not form part of the transfer state, and is not retained - // if a link is suspended and subsequently resumed. - Batchable bool - - Payload []byte - - // optional channel to indicate to sender that transfer has completed - // - // Settled=true: closed when the transferred on network. - // Settled=false: closed when the receiver has confirmed settlement. - Done chan encoding.DeliveryState -} - -func (t *PerformTransfer) frameBody() {} - -func (t PerformTransfer) String() string { - deliveryTag := "" - if t.DeliveryTag != nil { - deliveryTag = fmt.Sprintf("%X", t.DeliveryTag) - } - - return fmt.Sprintf("Transfer{Handle: %d, DeliveryID: %s, DeliveryTag: %s, MessageFormat: %s, "+ - "Settled: %t, More: %t, ReceiverSettleMode: %s, State: %v, Resume: %t, Aborted: %t, "+ - "Batchable: %t, Payload [size]: %d}", - t.Handle, - formatUint32Ptr(t.DeliveryID), - deliveryTag, - formatUint32Ptr(t.MessageFormat), - t.Settled, - t.More, - t.ReceiverSettleMode, - t.State, - t.Resume, - t.Aborted, - t.Batchable, - len(t.Payload), - ) -} - -func (t *PerformTransfer) Marshal(wr *buffer.Buffer) error { - err := encoding.MarshalComposite(wr, encoding.TypeCodeTransfer, []encoding.MarshalField{ - {Value: &t.Handle}, - {Value: t.DeliveryID, Omit: t.DeliveryID == nil}, - {Value: &t.DeliveryTag, Omit: len(t.DeliveryTag) == 0}, - {Value: t.MessageFormat, Omit: t.MessageFormat == nil}, - {Value: &t.Settled, Omit: !t.Settled}, - {Value: &t.More, Omit: !t.More}, - {Value: t.ReceiverSettleMode, Omit: t.ReceiverSettleMode == nil}, - {Value: t.State, Omit: t.State == nil}, - {Value: &t.Resume, Omit: !t.Resume}, - {Value: &t.Aborted, Omit: !t.Aborted}, - {Value: &t.Batchable, Omit: !t.Batchable}, - }) - if err != nil { - return err - } - - wr.Append(t.Payload) - return nil -} - -func (t *PerformTransfer) Unmarshal(r *buffer.Buffer) error { - err := encoding.UnmarshalComposite(r, encoding.TypeCodeTransfer, []encoding.UnmarshalField{ - {Field: &t.Handle, HandleNull: func() error { return errors.New("Transfer.Handle is required") }}, - {Field: &t.DeliveryID}, - {Field: &t.DeliveryTag}, - {Field: &t.MessageFormat}, - {Field: &t.Settled}, - {Field: &t.More}, - {Field: &t.ReceiverSettleMode}, - {Field: &t.State}, - {Field: &t.Resume}, - {Field: &t.Aborted}, - {Field: &t.Batchable}, - }...) - if err != nil { - return err - } - - t.Payload = append([]byte(nil), r.Bytes()...) - - return err -} - -/* - - - - - - - - - - - -*/ -type PerformDisposition struct { - // directionality of disposition - // - // The role identifies whether the disposition frame contains information about - // sending link endpoints or receiving link endpoints. - Role encoding.Role - - // lower bound of deliveries - // - // Identifies the lower bound of delivery-ids for the deliveries in this set. - First uint32 // required, sequence number - - // upper bound of deliveries - // - // Identifies the upper bound of delivery-ids for the deliveries in this set. - // If not set, this is taken to be the same as first. - Last *uint32 // sequence number - - // indicates deliveries are settled - // - // If true, indicates that the referenced deliveries are considered settled by - // the issuing endpoint. - Settled bool - - // indicates state of deliveries - // - // Communicates the state of all the deliveries referenced by this disposition. - State encoding.DeliveryState - - // batchable hint - // - // If true, then the issuer is hinting that there is no need for the peer to - // urgently communicate the impact of the updated delivery states. This hint - // MAY be used to artificially increase the amount of batching an implementation - // uses when communicating delivery states, and thereby save bandwidth. - Batchable bool -} - -func (d *PerformDisposition) frameBody() {} - -func (d PerformDisposition) String() string { - return fmt.Sprintf("Disposition{Role: %s, First: %d, Last: %s, Settled: %t, State: %v, Batchable: %t}", - d.Role, - d.First, - formatUint32Ptr(d.Last), - d.Settled, - d.State, - d.Batchable, - ) -} - -func (d *PerformDisposition) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeDisposition, []encoding.MarshalField{ - {Value: &d.Role, Omit: false}, - {Value: &d.First, Omit: false}, - {Value: d.Last, Omit: d.Last == nil}, - {Value: &d.Settled, Omit: !d.Settled}, - {Value: d.State, Omit: d.State == nil}, - {Value: &d.Batchable, Omit: !d.Batchable}, - }) -} - -func (d *PerformDisposition) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeDisposition, []encoding.UnmarshalField{ - {Field: &d.Role, HandleNull: func() error { return errors.New("Disposition.Role is required") }}, - {Field: &d.First, HandleNull: func() error { return errors.New("Disposition.Handle is required") }}, - {Field: &d.Last}, - {Field: &d.Settled}, - {Field: &d.State}, - {Field: &d.Batchable}, - }...) -} - -/* - - - - - - - - -*/ -type PerformDetach struct { - // the local handle of the link to be detached - Handle uint32 //required - - // if true then the sender has closed the link - Closed bool - - // error causing the detach - // - // If set, this field indicates that the link is being detached due to an error - // condition. The value of the field SHOULD contain details on the cause of the error. - Error *encoding.Error -} - -func (d *PerformDetach) frameBody() {} - -func (d PerformDetach) String() string { - return fmt.Sprintf("Detach{Handle: %d, Closed: %t, Error: %v}", - d.Handle, - d.Closed, - d.Error, - ) -} - -func (d *PerformDetach) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeDetach, []encoding.MarshalField{ - {Value: &d.Handle, Omit: false}, - {Value: &d.Closed, Omit: !d.Closed}, - {Value: d.Error, Omit: d.Error == nil}, - }) -} - -func (d *PerformDetach) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeDetach, []encoding.UnmarshalField{ - {Field: &d.Handle, HandleNull: func() error { return errors.New("Detach.Handle is required") }}, - {Field: &d.Closed}, - {Field: &d.Error}, - }...) -} - -/* - - - - - - -*/ -type PerformEnd struct { - // error causing the end - // - // If set, this field indicates that the session is being ended due to an error - // condition. The value of the field SHOULD contain details on the cause of the error. - Error *encoding.Error -} - -func (e *PerformEnd) frameBody() {} - -func (d PerformEnd) String() string { - return fmt.Sprintf("End{Error: %v}", d.Error) -} - -func (e *PerformEnd) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeEnd, []encoding.MarshalField{ - {Value: e.Error, Omit: e.Error == nil}, - }) -} - -func (e *PerformEnd) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeEnd, - encoding.UnmarshalField{Field: &e.Error}, - ) -} - -/* - - - - - - -*/ -type PerformClose struct { - // error causing the close - // - // If set, this field indicates that the session is being closed due to an error - // condition. The value of the field SHOULD contain details on the cause of the error. - Error *encoding.Error -} - -func (c *PerformClose) frameBody() {} - -func (c *PerformClose) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeClose, []encoding.MarshalField{ - {Value: c.Error, Omit: c.Error == nil}, - }) -} - -func (c *PerformClose) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeClose, - encoding.UnmarshalField{Field: &c.Error}, - ) -} - -func (c *PerformClose) String() string { - return fmt.Sprintf("Close{Error: %s}", c.Error) -} - -/* - - - - - - -*/ - -type SASLInit struct { - Mechanism encoding.Symbol - InitialResponse []byte - Hostname string -} - -func (si *SASLInit) frameBody() {} - -func (si *SASLInit) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeSASLInit, []encoding.MarshalField{ - {Value: &si.Mechanism, Omit: false}, - {Value: &si.InitialResponse, Omit: false}, - {Value: &si.Hostname, Omit: len(si.Hostname) == 0}, - }) -} - -func (si *SASLInit) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeSASLInit, []encoding.UnmarshalField{ - {Field: &si.Mechanism, HandleNull: func() error { return errors.New("saslInit.Mechanism is required") }}, - {Field: &si.InitialResponse}, - {Field: &si.Hostname}, - }...) -} - -func (si *SASLInit) String() string { - // Elide the InitialResponse as it may contain a plain text secret. - return fmt.Sprintf("SaslInit{Mechanism : %s, InitialResponse: ********, Hostname: %s}", - si.Mechanism, - si.Hostname, - ) -} - -/* - - - - -*/ - -type SASLMechanisms struct { - Mechanisms encoding.MultiSymbol -} - -func (sm *SASLMechanisms) frameBody() {} - -func (sm *SASLMechanisms) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeSASLMechanism, []encoding.MarshalField{ - {Value: &sm.Mechanisms, Omit: false}, - }) -} - -func (sm *SASLMechanisms) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeSASLMechanism, - encoding.UnmarshalField{Field: &sm.Mechanisms, HandleNull: func() error { return errors.New("saslMechanisms.Mechanisms is required") }}, - ) -} - -func (sm *SASLMechanisms) String() string { - return fmt.Sprintf("SaslMechanisms{Mechanisms : %v}", - sm.Mechanisms, - ) -} - -/* - - - - -*/ - -type SASLChallenge struct { - Challenge []byte -} - -func (sc *SASLChallenge) String() string { - return "Challenge{Challenge: ********}" -} - -func (sc *SASLChallenge) frameBody() {} - -func (sc *SASLChallenge) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeSASLChallenge, []encoding.MarshalField{ - {Value: &sc.Challenge, Omit: false}, - }) -} - -func (sc *SASLChallenge) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeSASLChallenge, []encoding.UnmarshalField{ - {Field: &sc.Challenge, HandleNull: func() error { return errors.New("saslChallenge.Challenge is required") }}, - }...) -} - -/* - - - - -*/ - -type SASLResponse struct { - Response []byte -} - -func (sr *SASLResponse) String() string { - return "Response{Response: ********}" -} - -func (sr *SASLResponse) frameBody() {} - -func (sr *SASLResponse) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeSASLResponse, []encoding.MarshalField{ - {Value: &sr.Response, Omit: false}, - }) -} - -func (sr *SASLResponse) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeSASLResponse, []encoding.UnmarshalField{ - {Field: &sr.Response, HandleNull: func() error { return errors.New("saslResponse.Response is required") }}, - }...) -} - -/* - - - - - -*/ - -type SASLOutcome struct { - Code encoding.SASLCode - AdditionalData []byte -} - -func (so *SASLOutcome) frameBody() {} - -func (so *SASLOutcome) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeSASLOutcome, []encoding.MarshalField{ - {Value: &so.Code, Omit: false}, - {Value: &so.AdditionalData, Omit: len(so.AdditionalData) == 0}, - }) -} - -func (so *SASLOutcome) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeSASLOutcome, []encoding.UnmarshalField{ - {Field: &so.Code, HandleNull: func() error { return errors.New("saslOutcome.AdditionalData is required") }}, - {Field: &so.AdditionalData}, - }...) -} - -func (so *SASLOutcome) String() string { - return fmt.Sprintf("SaslOutcome{Code : %v, AdditionalData: %v}", - so.Code, - so.AdditionalData, - ) -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/internal/frames/parsing.go b/sdk/messaging/azeventhubs/internal/go-amqp/internal/frames/parsing.go deleted file mode 100644 index 0e03a52a23e3..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/internal/frames/parsing.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package frames - -import ( - "encoding/binary" - "errors" - "fmt" - "math" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding" -) - -const HeaderSize = 8 - -// Frame structure: -// -// header (8 bytes) -// 0-3: SIZE (total size, at least 8 bytes for header, uint32) -// 4: DOFF (data offset,at least 2, count of 4 bytes words, uint8) -// 5: TYPE (frame type) -// 0x0: AMQP -// 0x1: SASL -// 6-7: type dependent (channel for AMQP) -// extended header (opt) -// body (opt) - -// Header in a structure appropriate for use with binary.Read() -type Header struct { - // size: an unsigned 32-bit integer that MUST contain the total frame size of the frame header, - // extended header, and frame body. The frame is malformed if the size is less than the size of - // the frame header (8 bytes). - Size uint32 - // doff: gives the position of the body within the frame. The value of the data offset is an - // unsigned, 8-bit integer specifying a count of 4-byte words. Due to the mandatory 8-byte - // frame header, the frame is malformed if the value is less than 2. - DataOffset uint8 - FrameType uint8 - Channel uint16 -} - -// ParseHeader reads the header from r and returns the result. -// -// No validation is done. -func ParseHeader(r *buffer.Buffer) (Header, error) { - buf, ok := r.Next(8) - if !ok { - return Header{}, errors.New("invalid frameHeader") - } - _ = buf[7] - - fh := Header{ - Size: binary.BigEndian.Uint32(buf[0:4]), - DataOffset: buf[4], - FrameType: buf[5], - Channel: binary.BigEndian.Uint16(buf[6:8]), - } - - if fh.Size < HeaderSize { - return fh, fmt.Errorf("received frame header with invalid size %d", fh.Size) - } - - if fh.DataOffset < 2 { - return fh, fmt.Errorf("received frame header with invalid data offset %d", fh.DataOffset) - } - - return fh, nil -} - -// ParseBody reads and unmarshals an AMQP frame. -func ParseBody(r *buffer.Buffer) (FrameBody, error) { - payload := r.Bytes() - - if r.Len() < 3 || payload[0] != 0 || encoding.AMQPType(payload[1]) != encoding.TypeCodeSmallUlong { - return nil, errors.New("invalid frame body header") - } - - switch pType := encoding.AMQPType(payload[2]); pType { - case encoding.TypeCodeOpen: - t := new(PerformOpen) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeBegin: - t := new(PerformBegin) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeAttach: - t := new(PerformAttach) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeFlow: - t := new(PerformFlow) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeTransfer: - t := new(PerformTransfer) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeDisposition: - t := new(PerformDisposition) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeDetach: - t := new(PerformDetach) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeEnd: - t := new(PerformEnd) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeClose: - t := new(PerformClose) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeSASLMechanism: - t := new(SASLMechanisms) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeSASLChallenge: - t := new(SASLChallenge) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeSASLOutcome: - t := new(SASLOutcome) - err := t.Unmarshal(r) - return t, err - default: - return nil, fmt.Errorf("unknown performative type %02x", pType) - } -} - -// Write encodes fr into buf. -// split out from conn.WriteFrame for testing purposes. -func Write(buf *buffer.Buffer, fr Frame) error { - // write header - buf.Append([]byte{ - 0, 0, 0, 0, // size, overwrite later - 2, // doff, see frameHeader.DataOffset comment - uint8(fr.Type), // frame type - }) - buf.AppendUint16(fr.Channel) // channel - - // write AMQP frame body - err := encoding.Marshal(buf, fr.Body) - if err != nil { - return err - } - - // validate size - if uint(buf.Len()) > math.MaxUint32 { - return errors.New("frame too large") - } - - // retrieve raw bytes - bufBytes := buf.Bytes() - - // write correct size - binary.BigEndian.PutUint32(bufBytes, uint32(len(bufBytes))) - return nil -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/internal/queue/queue.go b/sdk/messaging/azeventhubs/internal/go-amqp/internal/queue/queue.go deleted file mode 100644 index 45d6f5af9daf..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/internal/queue/queue.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Microsoft Corporation - -package queue - -import ( - "container/ring" -) - -// Holder provides synchronized access to a *Queue[T]. -type Holder[T any] struct { - // these channels work in tandem to provide exclusive access to the underlying *Queue[T]. - // each channel is created with a buffer size of one. - // empty behaves like a mutex when there's one or more messages in the queue. - // populated is like a semaphore when the queue is empty. - // the *Queue[T] is only ever in one channel. which channel depends on if it contains any items. - // the initial state is for empty to contain an empty queue. - empty chan *Queue[T] - populated chan *Queue[T] -} - -// NewHolder creates a new Holder[T] that contains the provided *Queue[T]. -func NewHolder[T any](q *Queue[T]) *Holder[T] { - h := &Holder[T]{ - empty: make(chan *Queue[T], 1), - populated: make(chan *Queue[T], 1), - } - h.Release(q) - return h -} - -// Acquire attempts to acquire the *Queue[T]. If the *Queue[T] has already been acquired the call blocks. -// When the *Queue[T] is no longer required, you MUST call Release() to relinquish acquisition. -func (h *Holder[T]) Acquire() *Queue[T] { - // the queue will be in only one of the channels, it doesn't matter which one - var q *Queue[T] - select { - case q = <-h.empty: - // empty queue - case q = <-h.populated: - // populated queue - } - return q -} - -// Wait returns a channel that's signaled when the *Queue[T] contains at least one item. -// When the *Queue[T] is no longer required, you MUST call Release() to relinquish acquisition. -func (h *Holder[T]) Wait() <-chan *Queue[T] { - return h.populated -} - -// Release returns the *Queue[T] back to the Holder[T]. -// Once the *Queue[T] has been released, it is no longer safe to call its methods. -func (h *Holder[T]) Release(q *Queue[T]) { - if q.Len() == 0 { - h.empty <- q - } else { - h.populated <- q - } -} - -// Len returns the length of the *Queue[T]. -func (h *Holder[T]) Len() int { - msgLen := 0 - select { - case q := <-h.empty: - h.empty <- q - case q := <-h.populated: - msgLen = q.Len() - h.populated <- q - } - return msgLen -} - -// Queue[T] is a segmented FIFO queue of Ts. -type Queue[T any] struct { - head *ring.Ring - tail *ring.Ring - size int -} - -// New creates a new instance of Queue[T]. -// - size is the size of each Queue segment -func New[T any](size int) *Queue[T] { - r := &ring.Ring{ - Value: &segment[T]{ - items: make([]*T, size), - }, - } - return &Queue[T]{ - head: r, - tail: r, - } -} - -// Enqueue adds the specified item to the end of the queue. -// If the current segment is full, a new segment is created. -func (q *Queue[T]) Enqueue(item T) { - for { - r := q.tail - seg := r.Value.(*segment[T]) - - if seg.tail < len(seg.items) { - seg.items[seg.tail] = &item - seg.tail++ - q.size++ - return - } - - // segment is full, can we advance? - if next := r.Next(); next != q.head { - q.tail = next - continue - } - - // no, add a new ring - r.Link(&ring.Ring{ - Value: &segment[T]{ - items: make([]*T, len(seg.items)), - }, - }) - - q.tail = r.Next() - } -} - -// Dequeue removes and returns the item from the front of the queue. -func (q *Queue[T]) Dequeue() *T { - r := q.head - seg := r.Value.(*segment[T]) - - if seg.tail == 0 { - // queue is empty - return nil - } - - // remove first item - item := seg.items[seg.head] - seg.items[seg.head] = nil - seg.head++ - q.size-- - - if seg.head == seg.tail { - // segment is now empty, reset indices - seg.head, seg.tail = 0, 0 - - // if we're not at the last ring, advance head to the next one - if q.head != q.tail { - q.head = r.Next() - } - } - - return item -} - -// Len returns the total count of enqueued items. -func (q *Queue[T]) Len() int { - return q.size -} - -type segment[T any] struct { - items []*T - head int - tail int -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/internal/shared/shared.go b/sdk/messaging/azeventhubs/internal/go-amqp/internal/shared/shared.go deleted file mode 100644 index 867c1e932bf5..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/internal/shared/shared.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation - -package shared - -import ( - "encoding/base64" - "math/rand" - "sync" - "time" -) - -// lockedRand provides a rand source that is safe for concurrent use. -type lockedRand struct { - mu sync.Mutex - src *rand.Rand -} - -func (r *lockedRand) Read(p []byte) (int, error) { - r.mu.Lock() - defer r.mu.Unlock() - return r.src.Read(p) -} - -// package scoped rand source to avoid any issues with seeding -// of the global source. -var pkgRand = &lockedRand{ - src: rand.New(rand.NewSource(time.Now().UnixNano())), -} - -// RandString returns a base64 encoded string of n bytes. -func RandString(n int) string { - b := make([]byte, n) - // from math/rand, cannot fail - _, _ = pkgRand.Read(b) - return base64.RawURLEncoding.EncodeToString(b) -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/link.go b/sdk/messaging/azeventhubs/internal/go-amqp/link.go deleted file mode 100644 index 27a257c7ecb3..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/link.go +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "context" - "errors" - "fmt" - "sync" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/debug" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/frames" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/queue" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/shared" -) - -// linkKey uniquely identifies a link on a connection by name and direction. -// -// A link can be identified uniquely by the ordered tuple -// -// (source-container-id, target-container-id, name) -// -// On a single connection the container ID pairs can be abbreviated -// to a boolean flag indicating the direction of the link. -type linkKey struct { - name string - role encoding.Role // Local role: sender/receiver -} - -// link contains the common state and methods for sending and receiving links -type link struct { - key linkKey // Name and direction - handle uint32 // our handle - remoteHandle uint32 // remote's handle - dynamicAddr bool // request a dynamic link address from the server - - // frames destined for this link are added to this queue by Session.muxFrameToLink - rxQ *queue.Holder[frames.FrameBody] - - // used for gracefully closing link - close chan struct{} // signals a link's mux to shut down; DO NOT use this to check if a link has terminated, use done instead - closeOnce *sync.Once // closeOnce protects close from being closed multiple times - - done chan struct{} // closed when the link has terminated (mux exited); DO NOT wait on this from within a link's mux() as it will never trigger! - doneErr error // contains the mux error state; ONLY written to by the mux and MUST only be read from after done is closed! - closeErr error // contains the error state returned from closeLink(); ONLY closeLink() reads/writes this! - - session *Session // parent session - source *frames.Source // used for Receiver links - target *frames.Target // used for Sender links - properties map[encoding.Symbol]any // additional properties sent upon link attach - - // "The delivery-count is initialized by the sender when a link endpoint is created, - // and is incremented whenever a message is sent. Only the sender MAY independently - // modify this field. The receiver's value is calculated based on the last known - // value from the sender and any subsequent messages received on the link. Note that, - // despite its name, the delivery-count is not a count but a sequence number - // initialized at an arbitrary point by the sender." - deliveryCount uint32 - - // The current maximum number of messages that can be handled at the receiver endpoint of the link. Only the receiver endpoint - // can independently set this value. The sender endpoint sets this to the last known value seen from the receiver. - linkCredit uint32 - - senderSettleMode *SenderSettleMode - receiverSettleMode *ReceiverSettleMode - maxMessageSize uint64 - - closeInProgress bool // indicates that the detach performative has been sent -} - -func newLink(s *Session, r encoding.Role) link { - l := link{ - key: linkKey{shared.RandString(40), r}, - session: s, - close: make(chan struct{}), - closeOnce: &sync.Once{}, - done: make(chan struct{}), - } - - // set the segment size relative to respective window - var segmentSize int - if r == encoding.RoleReceiver { - segmentSize = int(s.incomingWindow) - } else { - segmentSize = int(s.outgoingWindow) - } - - l.rxQ = queue.NewHolder(queue.New[frames.FrameBody](segmentSize)) - return l -} - -// waitForFrame waits for an incoming frame to be queued. -// it returns the next frame from the queue, or an error. -// the error is either from the context or session.doneErr. -// not meant for consumption outside of link.go. -func (l *link) waitForFrame(ctx context.Context) (frames.FrameBody, error) { - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-l.session.done: - // session has terminated, no need to deallocate in this case - return nil, l.session.doneErr - case q := <-l.rxQ.Wait(): - // frame received - fr := q.Dequeue() - l.rxQ.Release(q) - return *fr, nil - } -} - -// attach sends the Attach performative to establish the link with its parent session. -// this is automatically called by the new*Link constructors. -func (l *link) attach(ctx context.Context, beforeAttach func(*frames.PerformAttach), afterAttach func(*frames.PerformAttach)) error { - if err := l.session.freeAbandonedLinks(ctx); err != nil { - return err - } - - // once the abandoned links have been cleaned up we can create our link - if err := l.session.allocateHandle(ctx, l); err != nil { - return err - } - - attach := &frames.PerformAttach{ - Name: l.key.name, - Handle: l.handle, - ReceiverSettleMode: l.receiverSettleMode, - SenderSettleMode: l.senderSettleMode, - MaxMessageSize: l.maxMessageSize, - Source: l.source, - Target: l.target, - Properties: l.properties, - } - - // link-specific configuration of the attach frame - beforeAttach(attach) - - if err := l.txFrameAndWait(ctx, attach); err != nil { - return err - } - - // wait for response - fr, err := l.waitForFrame(ctx) - if err != nil { - l.session.abandonLink(l) - return err - } - - resp, ok := fr.(*frames.PerformAttach) - if !ok { - debug.Log(1, "RX (link %p): unexpected attach response frame %T", l, fr) - if err := l.session.conn.Close(); err != nil { - return err - } - return &ConnError{inner: fmt.Errorf("unexpected attach response: %#v", fr)} - } - - // If the remote encounters an error during the attach it returns an Attach - // with no Source or Target. The remote then sends a Detach with an error. - // - // Note that if the application chooses not to create a terminus, the session - // endpoint will still create a link endpoint and issue an attach indicating - // that the link endpoint has no associated local terminus. In this case, the - // session endpoint MUST immediately detach the newly created link endpoint. - // - // http://docs.oasis-open.org/amqp/core/v1.0/csprd01/amqp-core-transport-v1.0-csprd01.html#doc-idp386144 - if resp.Source == nil && resp.Target == nil { - // wait for detach - fr, err := l.waitForFrame(ctx) - if err != nil { - // we timed out waiting for the peer to close the link, this really isn't an abandoned link. - // however, we still need to send the detach performative to ack the peer. - l.session.abandonLink(l) - return err - } - - detach, ok := fr.(*frames.PerformDetach) - if !ok { - if err := l.session.conn.Close(); err != nil { - return err - } - return &ConnError{inner: fmt.Errorf("unexpected frame while waiting for detach: %#v", fr)} - } - - // send return detach - fr = &frames.PerformDetach{ - Handle: l.handle, - Closed: true, - } - if err := l.txFrameAndWait(ctx, fr); err != nil { - return err - } - - if detach.Error == nil { - return fmt.Errorf("received detach with no error specified") - } - return detach.Error - } - - if l.maxMessageSize == 0 || resp.MaxMessageSize < l.maxMessageSize { - l.maxMessageSize = resp.MaxMessageSize - } - - // link-specific configuration post attach - afterAttach(resp) - - if err := l.setSettleModes(resp); err != nil { - // close the link as there's a mismatch on requested/supported settlement modes - dr := &frames.PerformDetach{ - Handle: l.handle, - Closed: true, - } - if err := l.txFrameAndWait(ctx, dr); err != nil { - return err - } - return err - } - - return nil -} - -// setSettleModes sets the settlement modes based on the resp frames.PerformAttach. -// -// If a settlement mode has been explicitly set locally and it was not honored by the -// server an error is returned. -func (l *link) setSettleModes(resp *frames.PerformAttach) error { - var ( - localRecvSettle = receiverSettleModeValue(l.receiverSettleMode) - respRecvSettle = receiverSettleModeValue(resp.ReceiverSettleMode) - ) - if l.receiverSettleMode != nil && localRecvSettle != respRecvSettle { - return fmt.Errorf("amqp: receiver settlement mode %q requested, received %q from server", l.receiverSettleMode, &respRecvSettle) - } - l.receiverSettleMode = &respRecvSettle - - var ( - localSendSettle = senderSettleModeValue(l.senderSettleMode) - respSendSettle = senderSettleModeValue(resp.SenderSettleMode) - ) - if l.senderSettleMode != nil && localSendSettle != respSendSettle { - return fmt.Errorf("amqp: sender settlement mode %q requested, received %q from server", l.senderSettleMode, &respSendSettle) - } - l.senderSettleMode = &respSendSettle - - return nil -} - -// muxHandleFrame processes fr based on type. -func (l *link) muxHandleFrame(fr frames.FrameBody) error { - switch fr := fr.(type) { - case *frames.PerformDetach: - if !fr.Closed { - l.closeWithError(ErrCondNotImplemented, fmt.Sprintf("non-closing detach not supported: %+v", fr)) - return nil - } - - // there are two possibilities: - // - this is the ack to a client-side Close() - // - the peer is closing the link so we must ack - - if l.closeInProgress { - // if the client-side close was initiated due to an error (l.closeWithError) - // then l.doneErr will already be set. in this case, return that error instead - // of an empty LinkError which indicates a clean client-side close. - if l.doneErr != nil { - return l.doneErr - } - return &LinkError{} - } - - dr := &frames.PerformDetach{ - Handle: l.handle, - Closed: true, - } - l.txFrame(context.Background(), dr, nil) - return &LinkError{RemoteErr: fr.Error} - - default: - debug.Log(1, "RX (link %p): unexpected frame: %s", l, fr) - l.closeWithError(ErrCondInternalError, fmt.Sprintf("link received unexpected frame %T", fr)) - return nil - } -} - -// Close closes the Sender and AMQP link. -func (l *link) closeLink(ctx context.Context) error { - var ctxErr error - l.closeOnce.Do(func() { - close(l.close) - - // once the mux has received the ack'ing detach performative, the mux will - // exit which deletes the link and closes l.done. - select { - case <-l.done: - l.closeErr = l.doneErr - case <-ctx.Done(): - // notify the caller that the close timed out/was cancelled. - // the mux will remain running and once the ack is received it will terminate. - ctxErr = ctx.Err() - - // record that the close timed out/was cancelled. - // subsequent calls to closeLink() will return this - debug.Log(1, "TX (link %p) closing %s: %v", l, l.key.name, ctxErr) - l.closeErr = &LinkError{inner: ctxErr} - } - }) - - if ctxErr != nil { - return ctxErr - } - - var linkErr *LinkError - if errors.As(l.closeErr, &linkErr) && linkErr.RemoteErr == nil && linkErr.inner == nil { - // an empty LinkError means the link was cleanly closed by the caller - return nil - } - return l.closeErr -} - -// closeWithError initiates closing the link with the specified AMQP error. -// the mux must continue to run until the ack'ing detach is received. -// l.doneErr is populated with a &LinkError{} containing an inner error constructed from the specified values -// - cnd is the AMQP error condition -// - desc is the error description -func (l *link) closeWithError(cnd ErrCond, desc string) { - amqpErr := &Error{Condition: cnd, Description: desc} - if l.closeInProgress { - debug.Log(3, "TX (link %p) close error already pending, discarding %v", l, amqpErr) - return - } - - dr := &frames.PerformDetach{ - Handle: l.handle, - Closed: true, - Error: amqpErr, - } - l.closeInProgress = true - l.doneErr = &LinkError{inner: fmt.Errorf("%s: %s", cnd, desc)} - l.txFrame(context.Background(), dr, nil) -} - -// txFrame sends the specified frame via the link's session. -// you MUST call this instead of session.txFrame() to ensure -// that frames are not sent during session shutdown. -func (l *link) txFrame(ctx context.Context, fr frames.FrameBody, sent chan error) { - // NOTE: there is no need to select on l.done as this is either - // called from a link's mux or before the mux has even started. - select { - case <-l.session.done: - if sent != nil { - sent <- l.session.doneErr - } - case <-l.session.endSent: - // we swallow this to prevent the link's mux from terminating. - // l.session.done will soon close so this is temporary. - return - case l.session.tx <- frameBodyEnvelope{Ctx: ctx, FrameBody: fr, Sent: sent}: - debug.Log(2, "TX (link %p): mux frame to Session (%p): %s", l, l.session, fr) - } -} - -// txFrame sends the specified frame via the link's session. -// you MUST call this instead of session.txFrame() to ensure -// that frames are not sent during session shutdown. -func (l *link) txFrameAndWait(ctx context.Context, fr frames.FrameBody) error { - // NOTE: there is no need to select on l.done as this is either - // called from a link's mux or before the mux has even started. - sent := make(chan error, 1) - select { - case <-l.session.done: - return l.session.doneErr - case <-l.session.endSent: - // we swallow this to prevent the link's mux from terminating. - // l.session.done will soon close so this is temporary. - return nil - case l.session.tx <- frameBodyEnvelope{Ctx: ctx, FrameBody: fr, Sent: sent}: - debug.Log(2, "TX (link %p): mux frame to Session (%p): %s", l, l.session, fr) - } - - select { - case err := <-sent: - return err - case <-l.done: - return l.doneErr - case <-l.session.done: - return l.session.doneErr - } -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/link_options.go b/sdk/messaging/azeventhubs/internal/go-amqp/link_options.go deleted file mode 100644 index c4ba797007db..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/link_options.go +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding" -) - -type SenderOptions struct { - // Capabilities is the list of extension capabilities the sender supports. - Capabilities []string - - // Durability indicates what state of the sender will be retained durably. - // - // Default: DurabilityNone. - Durability Durability - - // DynamicAddress indicates a dynamic address is to be used. - // Any specified address will be ignored. - // - // Default: false. - DynamicAddress bool - - // ExpiryPolicy determines when the expiry timer of the sender starts counting - // down from the timeout value. If the link is subsequently re-attached before - // the timeout is reached, the count down is aborted. - // - // Default: ExpirySessionEnd. - ExpiryPolicy ExpiryPolicy - - // ExpiryTimeout is the duration in seconds that the sender will be retained. - // - // Default: 0. - ExpiryTimeout uint32 - - // Name sets the name of the link. - // - // Link names must be unique per-connection and direction. - // - // Default: randomly generated. - Name string - - // Properties sets an entry in the link properties map sent to the server. - Properties map[string]any - - // RequestedReceiverSettleMode sets the requested receiver settlement mode. - // - // If a settlement mode is explicitly set and the server does not - // honor it an error will be returned during link attachment. - // - // Default: Accept the settlement mode set by the server, commonly ModeFirst. - RequestedReceiverSettleMode *ReceiverSettleMode - - // SettlementMode sets the settlement mode in use by this sender. - // - // Default: ModeMixed. - SettlementMode *SenderSettleMode - - // SourceAddress specifies the source address for this sender. - SourceAddress string - - // TargetCapabilities is the list of extension capabilities the sender desires. - TargetCapabilities []string - - // TargetDurability indicates what state of the peer will be retained durably. - // - // Default: DurabilityNone. - TargetDurability Durability - - // TargetExpiryPolicy determines when the expiry timer of the peer starts counting - // down from the timeout value. If the link is subsequently re-attached before - // the timeout is reached, the count down is aborted. - // - // Default: ExpirySessionEnd. - TargetExpiryPolicy ExpiryPolicy - - // TargetExpiryTimeout is the duration in seconds that the peer will be retained. - // - // Default: 0. - TargetExpiryTimeout uint32 -} - -type ReceiverOptions struct { - // Capabilities is the list of extension capabilities the receiver supports. - Capabilities []string - - // Credit specifies the maximum number of unacknowledged messages - // the sender can transmit. Once this limit is reached, no more messages - // will arrive until messages are acknowledged and settled. - // - // As messages are settled, any available credit will automatically be issued. - // - // Setting this to -1 requires manual management of link credit. - // Credits can be added with IssueCredit(), and links can also be - // drained with DrainCredit(). - // This should only be enabled when complete control of the link's - // flow control is required. - // - // Default: 1. - Credit int32 - - // Durability indicates what state of the receiver will be retained durably. - // - // Default: DurabilityNone. - Durability Durability - - // DynamicAddress indicates a dynamic address is to be used. - // Any specified address will be ignored. - // - // Default: false. - DynamicAddress bool - - // ExpiryPolicy determines when the expiry timer of the sender starts counting - // down from the timeout value. If the link is subsequently re-attached before - // the timeout is reached, the count down is aborted. - // - // Default: ExpirySessionEnd. - ExpiryPolicy ExpiryPolicy - - // ExpiryTimeout is the duration in seconds that the sender will be retained. - // - // Default: 0. - ExpiryTimeout uint32 - - // Filters contains the desired filters for this receiver. - // If the peer cannot fulfill the filters the link will be detached. - Filters []LinkFilter - - // MaxMessageSize sets the maximum message size that can - // be received on the link. - // - // A size of zero indicates no limit. - // - // Default: 0. - MaxMessageSize uint64 - - // Name sets the name of the link. - // - // Link names must be unique per-connection and direction. - // - // Default: randomly generated. - Name string - - // Properties sets an entry in the link properties map sent to the server. - Properties map[string]any - - // RequestedSenderSettleMode sets the requested sender settlement mode. - // - // If a settlement mode is explicitly set and the server does not - // honor it an error will be returned during link attachment. - // - // Default: Accept the settlement mode set by the server, commonly ModeMixed. - RequestedSenderSettleMode *SenderSettleMode - - // SettlementMode sets the settlement mode in use by this receiver. - // - // Default: ModeFirst. - SettlementMode *ReceiverSettleMode - - // TargetAddress specifies the target address for this receiver. - TargetAddress string - - // SourceCapabilities is the list of extension capabilities the receiver desires. - SourceCapabilities []string - - // SourceDurability indicates what state of the peer will be retained durably. - // - // Default: DurabilityNone. - SourceDurability Durability - - // SourceExpiryPolicy determines when the expiry timer of the peer starts counting - // down from the timeout value. If the link is subsequently re-attached before - // the timeout is reached, the count down is aborted. - // - // Default: ExpirySessionEnd. - SourceExpiryPolicy ExpiryPolicy - - // SourceExpiryTimeout is the duration in seconds that the peer will be retained. - // - // Default: 0. - SourceExpiryTimeout uint32 -} - -// LinkFilter is an advanced API for setting non-standard source filters. -// Please file an issue or open a PR if a standard filter is missing from this -// library. -// -// The name is the key for the filter map. It will be encoded as an AMQP symbol type. -// -// The code is the descriptor of the described type value. The domain-id and descriptor-id -// should be concatenated together. If 0 is passed as the code, the name will be used as -// the descriptor. -// -// The value is the value of the descriped types. Acceptable types for value are specific -// to the filter. -// -// Example: -// -// The standard selector-filter is defined as: -// -// -// -// In this case the name is "apache.org:selector-filter:string" and the code is -// 0x0000468C00000004. -// -// LinkSourceFilter("apache.org:selector-filter:string", 0x0000468C00000004, exampleValue) -// -// References: -// -// http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-filter-set -// http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-types-v1.0-os.html#section-descriptor-values -type LinkFilter func(encoding.Filter) - -// NewLinkFilter creates a new LinkFilter with the specified values. -// Any preexisting link filter with the same name will be updated with the new code and value. -func NewLinkFilter(name string, code uint64, value any) LinkFilter { - return func(f encoding.Filter) { - var descriptor any - if code != 0 { - descriptor = code - } else { - descriptor = encoding.Symbol(name) - } - f[encoding.Symbol(name)] = &encoding.DescribedType{ - Descriptor: descriptor, - Value: value, - } - } -} - -// NewSelectorFilter creates a new selector filter (apache.org:selector-filter:string) with the specified filter value. -// Any preexisting selector filter will be updated with the new filter value. -func NewSelectorFilter(filter string) LinkFilter { - return NewLinkFilter(selectorFilter, selectorFilterCode, filter) -} - -const ( - selectorFilter = "apache.org:selector-filter:string" - selectorFilterCode = uint64(0x0000468C00000004) -) diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/message.go b/sdk/messaging/azeventhubs/internal/go-amqp/message.go deleted file mode 100644 index 20df597b6dc6..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/message.go +++ /dev/null @@ -1,492 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "fmt" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding" -) - -// Message is an AMQP message. -type Message struct { - // Message format code. - // - // The upper three octets of a message format code identify a particular message - // format. The lowest octet indicates the version of said message format. Any - // given version of a format is forwards compatible with all higher versions. - Format uint32 - - // The DeliveryTag can be up to 32 octets of binary data. - // Note that when mode one is enabled there will be no delivery tag. - DeliveryTag []byte - - // The header section carries standard delivery details about the transfer - // of a message through the AMQP network. - Header *MessageHeader - // If the header section is omitted the receiver MUST assume the appropriate - // default values (or the meaning implied by no value being set) for the - // fields within the header unless other target or node specific defaults - // have otherwise been set. - - // The delivery-annotations section is used for delivery-specific non-standard - // properties at the head of the message. Delivery annotations convey information - // from the sending peer to the receiving peer. - DeliveryAnnotations Annotations - // If the recipient does not understand the annotation it cannot be acted upon - // and its effects (such as any implied propagation) cannot be acted upon. - // Annotations might be specific to one implementation, or common to multiple - // implementations. The capabilities negotiated on link attach and on the source - // and target SHOULD be used to establish which annotations a peer supports. A - // registry of defined annotations and their meanings is maintained [AMQPDELANN]. - // The symbolic key "rejected" is reserved for the use of communicating error - // information regarding rejected messages. Any values associated with the - // "rejected" key MUST be of type error. - // - // If the delivery-annotations section is omitted, it is equivalent to a - // delivery-annotations section containing an empty map of annotations. - - // The message-annotations section is used for properties of the message which - // are aimed at the infrastructure. - Annotations Annotations - // The message-annotations section is used for properties of the message which - // are aimed at the infrastructure and SHOULD be propagated across every - // delivery step. Message annotations convey information about the message. - // Intermediaries MUST propagate the annotations unless the annotations are - // explicitly augmented or modified (e.g., by the use of the modified outcome). - // - // The capabilities negotiated on link attach and on the source and target can - // be used to establish which annotations a peer understands; however, in a - // network of AMQP intermediaries it might not be possible to know if every - // intermediary will understand the annotation. Note that for some annotations - // it might not be necessary for the intermediary to understand their purpose, - // i.e., they could be used purely as an attribute which can be filtered on. - // - // A registry of defined annotations and their meanings is maintained [AMQPMESSANN]. - // - // If the message-annotations section is omitted, it is equivalent to a - // message-annotations section containing an empty map of annotations. - - // The properties section is used for a defined set of standard properties of - // the message. - Properties *MessageProperties - // The properties section is part of the bare message; therefore, - // if retransmitted by an intermediary, it MUST remain unaltered. - - // The application-properties section is a part of the bare message used for - // structured application data. Intermediaries can use the data within this - // structure for the purposes of filtering or routing. - ApplicationProperties map[string]any - // The keys of this map are restricted to be of type string (which excludes - // the possibility of a null key) and the values are restricted to be of - // simple types only, that is, excluding map, list, and array types. - - // Data payloads. - // A data section contains opaque binary data. - Data [][]byte - - // Value payload. - // An amqp-value section contains a single AMQP value. - Value any - - // Sequence will contain AMQP sequence sections from the body of the message. - // An amqp-sequence section contains an AMQP sequence. - Sequence [][]any - - // The footer section is used for details about the message or delivery which - // can only be calculated or evaluated once the whole bare message has been - // constructed or seen (for example message hashes, HMACs, signatures and - // encryption details). - Footer Annotations - - deliveryID uint32 // used when sending disposition - settled bool // whether transfer was settled by sender -} - -// NewMessage returns a *Message with data as the payload. -// -// This constructor is intended as a helper for basic Messages with a -// single data payload. It is valid to construct a Message directly for -// more complex usages. -func NewMessage(data []byte) *Message { - return &Message{ - Data: [][]byte{data}, - } -} - -// GetData returns the first []byte from the Data field -// or nil if Data is empty. -func (m *Message) GetData() []byte { - if len(m.Data) < 1 { - return nil - } - return m.Data[0] -} - -// MarshalBinary encodes the message into binary form. -func (m *Message) MarshalBinary() ([]byte, error) { - buf := &buffer.Buffer{} - err := m.Marshal(buf) - return buf.Detach(), err -} - -func (m *Message) Marshal(wr *buffer.Buffer) error { - if m.Header != nil { - err := m.Header.Marshal(wr) - if err != nil { - return err - } - } - - if m.DeliveryAnnotations != nil { - encoding.WriteDescriptor(wr, encoding.TypeCodeDeliveryAnnotations) - err := encoding.Marshal(wr, m.DeliveryAnnotations) - if err != nil { - return err - } - } - - if m.Annotations != nil { - encoding.WriteDescriptor(wr, encoding.TypeCodeMessageAnnotations) - err := encoding.Marshal(wr, m.Annotations) - if err != nil { - return err - } - } - - if m.Properties != nil { - err := encoding.Marshal(wr, m.Properties) - if err != nil { - return err - } - } - - if m.ApplicationProperties != nil { - encoding.WriteDescriptor(wr, encoding.TypeCodeApplicationProperties) - err := encoding.Marshal(wr, m.ApplicationProperties) - if err != nil { - return err - } - } - - for _, data := range m.Data { - encoding.WriteDescriptor(wr, encoding.TypeCodeApplicationData) - err := encoding.WriteBinary(wr, data) - if err != nil { - return err - } - } - - if m.Value != nil { - encoding.WriteDescriptor(wr, encoding.TypeCodeAMQPValue) - err := encoding.Marshal(wr, m.Value) - if err != nil { - return err - } - } - - if m.Sequence != nil { - // the body can basically be one of three different types (value, data or sequence). - // When it's sequence it's actually _several_ sequence sections, one for each sub-array. - for _, v := range m.Sequence { - encoding.WriteDescriptor(wr, encoding.TypeCodeAMQPSequence) - err := encoding.Marshal(wr, v) - if err != nil { - return err - } - } - } - - if m.Footer != nil { - encoding.WriteDescriptor(wr, encoding.TypeCodeFooter) - err := encoding.Marshal(wr, m.Footer) - if err != nil { - return err - } - } - - return nil -} - -// UnmarshalBinary decodes the message from binary form. -func (m *Message) UnmarshalBinary(data []byte) error { - buf := buffer.New(data) - return m.Unmarshal(buf) -} - -func (m *Message) Unmarshal(r *buffer.Buffer) error { - // loop, decoding sections until bytes have been consumed - for r.Len() > 0 { - // determine type - type_, headerLength, err := encoding.PeekMessageType(r.Bytes()) - if err != nil { - return err - } - - var ( - section any - // section header is read from r before - // unmarshaling section is set to true - discardHeader = true - ) - switch encoding.AMQPType(type_) { - - case encoding.TypeCodeMessageHeader: - discardHeader = false - section = &m.Header - - case encoding.TypeCodeDeliveryAnnotations: - section = &m.DeliveryAnnotations - - case encoding.TypeCodeMessageAnnotations: - section = &m.Annotations - - case encoding.TypeCodeMessageProperties: - discardHeader = false - section = &m.Properties - - case encoding.TypeCodeApplicationProperties: - section = &m.ApplicationProperties - - case encoding.TypeCodeApplicationData: - r.Skip(int(headerLength)) - - var data []byte - err = encoding.Unmarshal(r, &data) - if err != nil { - return err - } - - m.Data = append(m.Data, data) - continue - - case encoding.TypeCodeAMQPSequence: - r.Skip(int(headerLength)) - - var data []any - err = encoding.Unmarshal(r, &data) - if err != nil { - return err - } - - m.Sequence = append(m.Sequence, data) - continue - - case encoding.TypeCodeFooter: - section = &m.Footer - - case encoding.TypeCodeAMQPValue: - section = &m.Value - - default: - return fmt.Errorf("unknown message section %#02x", type_) - } - - if discardHeader { - r.Skip(int(headerLength)) - } - - err = encoding.Unmarshal(r, section) - if err != nil { - return err - } - } - return nil -} - -/* - - - - - - - - -*/ - -// MessageHeader carries standard delivery details about the transfer -// of a message. -type MessageHeader struct { - Durable bool - Priority uint8 - TTL time.Duration // from milliseconds - FirstAcquirer bool - DeliveryCount uint32 -} - -func (h *MessageHeader) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeMessageHeader, []encoding.MarshalField{ - {Value: &h.Durable, Omit: !h.Durable}, - {Value: &h.Priority, Omit: h.Priority == 4}, - {Value: (*encoding.Milliseconds)(&h.TTL), Omit: h.TTL == 0}, - {Value: &h.FirstAcquirer, Omit: !h.FirstAcquirer}, - {Value: &h.DeliveryCount, Omit: h.DeliveryCount == 0}, - }) -} - -func (h *MessageHeader) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeMessageHeader, []encoding.UnmarshalField{ - {Field: &h.Durable}, - {Field: &h.Priority, HandleNull: func() error { h.Priority = 4; return nil }}, - {Field: (*encoding.Milliseconds)(&h.TTL)}, - {Field: &h.FirstAcquirer}, - {Field: &h.DeliveryCount}, - }...) -} - -/* - - - - - - - - - - - - - - - - -*/ - -// MessageProperties is the defined set of properties for AMQP messages. -type MessageProperties struct { - // Message-id, if set, uniquely identifies a message within the message system. - // The message producer is usually responsible for setting the message-id in - // such a way that it is assured to be globally unique. A broker MAY discard a - // message as a duplicate if the value of the message-id matches that of a - // previously received message sent to the same node. - // - // The value is restricted to the following types - // - uint64, UUID, []byte, or string - MessageID any - - // The identity of the user responsible for producing the message. - // The client sets this value, and it MAY be authenticated by intermediaries. - UserID []byte - - // The to field identifies the node that is the intended destination of the message. - // On any given transfer this might not be the node at the receiving end of the link. - To *string - - // A common field for summary information about the message content and purpose. - Subject *string - - // The address of the node to send replies to. - ReplyTo *string - - // This is a client-specific id that can be used to mark or identify messages - // between clients. - // - // The value is restricted to the following types - // - uint64, UUID, []byte, or string - CorrelationID any - - // The RFC-2046 [RFC2046] MIME type for the message's application-data section - // (body). As per RFC-2046 [RFC2046] this can contain a charset parameter defining - // the character encoding used: e.g., 'text/plain; charset="utf-8"'. - // - // For clarity, as per section 7.2.1 of RFC-2616 [RFC2616], where the content type - // is unknown the content-type SHOULD NOT be set. This allows the recipient the - // opportunity to determine the actual type. Where the section is known to be truly - // opaque binary data, the content-type SHOULD be set to application/octet-stream. - // - // When using an application-data section with a section code other than data, - // content-type SHOULD NOT be set. - ContentType *string - - // The content-encoding property is used as a modifier to the content-type. - // When present, its value indicates what additional content encodings have been - // applied to the application-data, and thus what decoding mechanisms need to be - // applied in order to obtain the media-type referenced by the content-type header - // field. - // - // Content-encoding is primarily used to allow a document to be compressed without - // losing the identity of its underlying content type. - // - // Content-encodings are to be interpreted as per section 3.5 of RFC 2616 [RFC2616]. - // Valid content-encodings are registered at IANA [IANAHTTPPARAMS]. - // - // The content-encoding MUST NOT be set when the application-data section is other - // than data. The binary representation of all other application-data section types - // is defined completely in terms of the AMQP type system. - // - // Implementations MUST NOT use the identity encoding. Instead, implementations - // SHOULD NOT set this property. Implementations SHOULD NOT use the compress encoding, - // except as to remain compatible with messages originally sent with other protocols, - // e.g. HTTP or SMTP. - // - // Implementations SHOULD NOT specify multiple content-encoding values except as to - // be compatible with messages originally sent with other protocols, e.g. HTTP or SMTP. - ContentEncoding *string - - // An absolute time when this message is considered to be expired. - AbsoluteExpiryTime *time.Time - - // An absolute time when this message was created. - CreationTime *time.Time - - // Identifies the group the message belongs to. - GroupID *string - - // The relative position of this message within its group. - // - // The value is defined as a RFC-1982 sequence number - GroupSequence *uint32 - - // This is a client-specific id that is used so that client can send replies to this - // message to a specific group. - ReplyToGroupID *string -} - -func (p *MessageProperties) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeMessageProperties, []encoding.MarshalField{ - {Value: p.MessageID, Omit: p.MessageID == nil}, - {Value: &p.UserID, Omit: len(p.UserID) == 0}, - {Value: p.To, Omit: p.To == nil}, - {Value: p.Subject, Omit: p.Subject == nil}, - {Value: p.ReplyTo, Omit: p.ReplyTo == nil}, - {Value: p.CorrelationID, Omit: p.CorrelationID == nil}, - {Value: (*encoding.Symbol)(p.ContentType), Omit: p.ContentType == nil}, - {Value: (*encoding.Symbol)(p.ContentEncoding), Omit: p.ContentEncoding == nil}, - {Value: p.AbsoluteExpiryTime, Omit: p.AbsoluteExpiryTime == nil}, - {Value: p.CreationTime, Omit: p.CreationTime == nil}, - {Value: p.GroupID, Omit: p.GroupID == nil}, - {Value: p.GroupSequence, Omit: p.GroupSequence == nil}, - {Value: p.ReplyToGroupID, Omit: p.ReplyToGroupID == nil}, - }) -} - -func (p *MessageProperties) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeMessageProperties, []encoding.UnmarshalField{ - {Field: &p.MessageID}, - {Field: &p.UserID}, - {Field: &p.To}, - {Field: &p.Subject}, - {Field: &p.ReplyTo}, - {Field: &p.CorrelationID}, - {Field: &p.ContentType}, - {Field: &p.ContentEncoding}, - {Field: &p.AbsoluteExpiryTime}, - {Field: &p.CreationTime}, - {Field: &p.GroupID}, - {Field: &p.GroupSequence}, - {Field: &p.ReplyToGroupID}, - }...) -} - -// Annotations keys must be of type string, int, or int64. -// -// String keys are encoded as AMQP Symbols. -type Annotations = encoding.Annotations - -// UUID is a 128 bit identifier as defined in RFC 4122. -type UUID = encoding.UUID diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/receiver.go b/sdk/messaging/azeventhubs/internal/go-amqp/receiver.go deleted file mode 100644 index 88da28bac3ca..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/receiver.go +++ /dev/null @@ -1,897 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "bytes" - "context" - "errors" - "fmt" - "sync" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/debug" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/frames" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/queue" -) - -// Default link options -const ( - defaultLinkCredit = 1 -) - -// Receiver receives messages on a single AMQP link. -type Receiver struct { - l link - - // message receiving - receiverReady chan struct{} // receiver sends on this when mux is paused to indicate it can handle more messages - messagesQ *queue.Holder[Message] // used to send completed messages to receiver - txDisposition chan frameBodyEnvelope // used to funnel disposition frames through the mux - - unsettledMessages map[string]struct{} // used to keep track of messages being handled downstream - unsettledMessagesLock sync.RWMutex // lock to protect concurrent access to unsettledMessages - msgBuf buffer.Buffer // buffered bytes for current message - more bool // if true, buf contains a partial message - msg Message // current message being decoded - - settlementCount uint32 // the count of settled messages - settlementCountMu sync.Mutex // must be held when accessing settlementCount - - autoSendFlow bool // automatically send flow frames as credit becomes available - inFlight inFlight // used to track message disposition when rcv-settle-mode == second - creditor creditor // manages credits via calls to IssueCredit/DrainCredit -} - -// IssueCredit adds credits to be requested in the next flow request. -// Attempting to issue more credit than the receiver's max credit as -// specified in ReceiverOptions.MaxCredit will result in an error. -func (r *Receiver) IssueCredit(credit uint32) error { - if r.autoSendFlow { - return errors.New("issueCredit can only be used with receiver links using manual credit management") - } - - if err := r.creditor.IssueCredit(credit); err != nil { - return err - } - - // cause mux() to check our flow conditions. - select { - case r.receiverReady <- struct{}{}: - default: - } - - return nil -} - -// Prefetched returns the next message that is stored in the Receiver's -// prefetch cache. It does NOT wait for the remote sender to send messages -// and returns immediately if the prefetch cache is empty. To receive from the -// prefetch and wait for messages from the remote Sender use `Receive`. -// -// Once a message is received, and if the sender is configured in any mode other -// than SenderSettleModeSettled, you *must* take an action on the message by calling -// one of the following: AcceptMessage, RejectMessage, ReleaseMessage, ModifyMessage. -func (r *Receiver) Prefetched() *Message { - select { - case r.receiverReady <- struct{}{}: - default: - } - - // non-blocking receive to ensure buffered messages are - // delivered regardless of whether the link has been closed. - q := r.messagesQ.Acquire() - msg := q.Dequeue() - r.messagesQ.Release(q) - - if msg == nil { - return nil - } - - debug.Log(3, "RX (Receiver %p): prefetched delivery ID %d", r, msg.deliveryID) - - if msg.settled { - r.onSettlement(1) - } - - return msg -} - -// ReceiveOptions contains any optional values for the Receiver.Receive method. -type ReceiveOptions struct { - // for future expansion -} - -// Receive returns the next message from the sender. -// Blocks until a message is received, ctx completes, or an error occurs. -// -// Once a message is received, and if the sender is configured in any mode other -// than SenderSettleModeSettled, you *must* take an action on the message by calling -// one of the following: AcceptMessage, RejectMessage, ReleaseMessage, ModifyMessage. -func (r *Receiver) Receive(ctx context.Context, opts *ReceiveOptions) (*Message, error) { - if msg := r.Prefetched(); msg != nil { - return msg, nil - } - - // wait for the next message - select { - case q := <-r.messagesQ.Wait(): - msg := q.Dequeue() - debug.Assert(msg != nil) - debug.Log(3, "RX (Receiver %p): received delivery ID %d", r, msg.deliveryID) - r.messagesQ.Release(q) - if msg.settled { - r.onSettlement(1) - } - return msg, nil - case <-r.l.done: - // if the link receives messages and is then closed between the above call to r.Prefetched() - // and this select statement, the order of selecting r.messages and r.l.done is undefined. - // however, once r.l.done is closed the link cannot receive any more messages. so be sure to - // drain any that might have trickled in within this window. - if msg := r.Prefetched(); msg != nil { - return msg, nil - } - return nil, r.l.doneErr - case <-ctx.Done(): - return nil, ctx.Err() - } -} - -// Accept notifies the server that the message has been accepted and does not require redelivery. -// - ctx controls waiting for the peer to acknowledge the disposition -// - msg is the message to accept -// -// If the context's deadline expires or is cancelled before the operation -// completes, the message's disposition is in an unknown state. -func (r *Receiver) AcceptMessage(ctx context.Context, msg *Message) error { - return r.messageDisposition(ctx, msg, &encoding.StateAccepted{}) -} - -// Reject notifies the server that the message is invalid. -// - ctx controls waiting for the peer to acknowledge the disposition -// - msg is the message to reject -// - e is an optional rejection error -// -// If the context's deadline expires or is cancelled before the operation -// completes, the message's disposition is in an unknown state. -func (r *Receiver) RejectMessage(ctx context.Context, msg *Message, e *Error) error { - return r.messageDisposition(ctx, msg, &encoding.StateRejected{Error: e}) -} - -// Release releases the message back to the server. The message may be redelivered to this or another consumer. -// - ctx controls waiting for the peer to acknowledge the disposition -// - msg is the message to release -// -// If the context's deadline expires or is cancelled before the operation -// completes, the message's disposition is in an unknown state. -func (r *Receiver) ReleaseMessage(ctx context.Context, msg *Message) error { - return r.messageDisposition(ctx, msg, &encoding.StateReleased{}) -} - -// Modify notifies the server that the message was not acted upon and should be modifed. -// - ctx controls waiting for the peer to acknowledge the disposition -// - msg is the message to modify -// - options contains the optional settings to modify -// -// If the context's deadline expires or is cancelled before the operation -// completes, the message's disposition is in an unknown state. -func (r *Receiver) ModifyMessage(ctx context.Context, msg *Message, options *ModifyMessageOptions) error { - if options == nil { - options = &ModifyMessageOptions{} - } - return r.messageDisposition(ctx, - msg, &encoding.StateModified{ - DeliveryFailed: options.DeliveryFailed, - UndeliverableHere: options.UndeliverableHere, - MessageAnnotations: options.Annotations, - }) -} - -// ModifyMessageOptions contains the optional parameters to ModifyMessage. -type ModifyMessageOptions struct { - // DeliveryFailed indicates that the server must consider this an - // unsuccessful delivery attempt and increment the delivery count. - DeliveryFailed bool - - // UndeliverableHere indicates that the server must not redeliver - // the message to this link. - UndeliverableHere bool - - // Annotations is an optional annotation map to be merged - // with the existing message annotations, overwriting existing keys - // if necessary. - Annotations Annotations -} - -// Address returns the link's address. -func (r *Receiver) Address() string { - if r.l.source == nil { - return "" - } - return r.l.source.Address -} - -// LinkName returns associated link name or an empty string if link is not defined. -func (r *Receiver) LinkName() string { - return r.l.key.name -} - -// LinkSourceFilterValue retrieves the specified link source filter value or nil if it doesn't exist. -func (r *Receiver) LinkSourceFilterValue(name string) any { - if r.l.source == nil { - return nil - } - filter, ok := r.l.source.Filter[encoding.Symbol(name)] - if !ok { - return nil - } - return filter.Value -} - -// Close closes the Receiver and AMQP link. -// - ctx controls waiting for the peer to acknowledge the close -// -// If the context's deadline expires or is cancelled before the operation -// completes, an error is returned. However, the operation will continue to -// execute in the background. Subsequent calls will return a *LinkError -// that contains the context's error message. -func (r *Receiver) Close(ctx context.Context) error { - return r.l.closeLink(ctx) -} - -// sendDisposition sends a disposition frame to the peer -func (r *Receiver) sendDisposition(ctx context.Context, first uint32, last *uint32, state encoding.DeliveryState) error { - fr := &frames.PerformDisposition{ - Role: encoding.RoleReceiver, - First: first, - Last: last, - Settled: r.l.receiverSettleMode == nil || *r.l.receiverSettleMode == ReceiverSettleModeFirst, - State: state, - } - - sent := make(chan error, 1) - select { - case r.txDisposition <- frameBodyEnvelope{Ctx: ctx, FrameBody: fr, Sent: sent}: - debug.Log(2, "TX (Receiver %p): mux txDisposition %s", r, fr) - case <-r.l.done: - return r.l.doneErr - } - - select { - case err := <-sent: - return err - case <-r.l.done: - return r.l.doneErr - } -} - -func (r *Receiver) messageDisposition(ctx context.Context, msg *Message, state encoding.DeliveryState) error { - if msg.settled { - return nil - } - - // NOTE: we MUST add to the in-flight map before sending the disposition. if not, it's possible - // to receive the ack'ing disposition frame *before* the in-flight map has been updated which - // will cause the below <-wait to never trigger. - - var wait chan error - if r.l.receiverSettleMode != nil && *r.l.receiverSettleMode == ReceiverSettleModeSecond { - debug.Log(3, "TX (Receiver %p): delivery ID %d is in flight", r, msg.deliveryID) - wait = r.inFlight.add(msg) - } - - if err := r.sendDisposition(ctx, msg.deliveryID, nil, state); err != nil { - return err - } - - if wait == nil { - // mode first, there will be no settlement ack - r.deleteUnsettled(msg) - r.onSettlement(1) - return nil - } - - select { - case err := <-wait: - // err has three possibilities - // - nil, meaning the peer acknowledged the settlement - // - an *Error, meaning the peer rejected the message with a provided error - // - a non-AMQP error. this comes from calls to inFlight.clear() during mux unwind. - // only for the first two cases is the message considered settled - - if amqpErr := (&Error{}); err == nil || errors.As(err, &amqpErr) { - debug.Log(3, "RX (Receiver %p): delivery ID %d has been settled", r, msg.deliveryID) - // we've received confirmation of disposition - return err - } - - debug.Log(3, "RX (Receiver %p): error settling delivery ID %d: %v", r, msg.deliveryID, err) - return err - - case <-ctx.Done(): - // didn't receive the ack in the time allotted, leave message as unsettled - // TODO: if the ack arrives later, we need to remove the message from the unsettled map and reclaim the credit - return ctx.Err() - } -} - -// onSettlement is to be called after message settlement. -// - count is the number of messages that were settled -func (r *Receiver) onSettlement(count uint32) { - if !r.autoSendFlow { - return - } - - r.settlementCountMu.Lock() - r.settlementCount += count - r.settlementCountMu.Unlock() - - select { - case r.receiverReady <- struct{}{}: - // woke up - default: - // wake pending - } -} - -func (r *Receiver) addUnsettled(msg *Message) { - r.unsettledMessagesLock.Lock() - r.unsettledMessages[string(msg.DeliveryTag)] = struct{}{} - r.unsettledMessagesLock.Unlock() -} - -func (r *Receiver) deleteUnsettled(msg *Message) { - r.unsettledMessagesLock.Lock() - delete(r.unsettledMessages, string(msg.DeliveryTag)) - r.unsettledMessagesLock.Unlock() -} - -func (r *Receiver) countUnsettled() int { - r.unsettledMessagesLock.RLock() - count := len(r.unsettledMessages) - r.unsettledMessagesLock.RUnlock() - return count -} - -func newReceiver(source string, session *Session, opts *ReceiverOptions) (*Receiver, error) { - l := newLink(session, encoding.RoleReceiver) - l.source = &frames.Source{Address: source} - l.target = new(frames.Target) - l.linkCredit = defaultLinkCredit - r := &Receiver{ - l: l, - autoSendFlow: true, - receiverReady: make(chan struct{}, 1), - txDisposition: make(chan frameBodyEnvelope), - } - - r.messagesQ = queue.NewHolder(queue.New[Message](int(session.incomingWindow))) - - if opts == nil { - return r, nil - } - - for _, v := range opts.Capabilities { - r.l.target.Capabilities = append(r.l.target.Capabilities, encoding.Symbol(v)) - } - if opts.Credit > 0 { - r.l.linkCredit = uint32(opts.Credit) - } else if opts.Credit < 0 { - r.l.linkCredit = 0 - r.autoSendFlow = false - } - if opts.Durability > DurabilityUnsettledState { - return nil, fmt.Errorf("invalid Durability %d", opts.Durability) - } - r.l.target.Durable = opts.Durability - if opts.DynamicAddress { - r.l.source.Address = "" - r.l.dynamicAddr = opts.DynamicAddress - } - if opts.ExpiryPolicy != "" { - if err := encoding.ValidateExpiryPolicy(opts.ExpiryPolicy); err != nil { - return nil, err - } - r.l.target.ExpiryPolicy = opts.ExpiryPolicy - } - r.l.target.Timeout = opts.ExpiryTimeout - if opts.Filters != nil { - r.l.source.Filter = make(encoding.Filter) - for _, f := range opts.Filters { - f(r.l.source.Filter) - } - } - if opts.MaxMessageSize > 0 { - r.l.maxMessageSize = opts.MaxMessageSize - } - if opts.Name != "" { - r.l.key.name = opts.Name - } - if opts.Properties != nil { - r.l.properties = make(map[encoding.Symbol]any) - for k, v := range opts.Properties { - if k == "" { - return nil, errors.New("link property key must not be empty") - } - r.l.properties[encoding.Symbol(k)] = v - } - } - if opts.RequestedSenderSettleMode != nil { - if rsm := *opts.RequestedSenderSettleMode; rsm > SenderSettleModeMixed { - return nil, fmt.Errorf("invalid RequestedSenderSettleMode %d", rsm) - } - r.l.senderSettleMode = opts.RequestedSenderSettleMode - } - if opts.SettlementMode != nil { - if rsm := *opts.SettlementMode; rsm > ReceiverSettleModeSecond { - return nil, fmt.Errorf("invalid SettlementMode %d", rsm) - } - r.l.receiverSettleMode = opts.SettlementMode - } - r.l.target.Address = opts.TargetAddress - for _, v := range opts.SourceCapabilities { - r.l.source.Capabilities = append(r.l.source.Capabilities, encoding.Symbol(v)) - } - if opts.SourceDurability != DurabilityNone { - r.l.source.Durable = opts.SourceDurability - } - if opts.SourceExpiryPolicy != ExpiryPolicySessionEnd { - r.l.source.ExpiryPolicy = opts.SourceExpiryPolicy - } - if opts.SourceExpiryTimeout != 0 { - r.l.source.Timeout = opts.SourceExpiryTimeout - } - return r, nil -} - -// attach sends the Attach performative to establish the link with its parent session. -// this is automatically called by the new*Link constructors. -func (r *Receiver) attach(ctx context.Context) error { - if err := r.l.attach(ctx, func(pa *frames.PerformAttach) { - pa.Role = encoding.RoleReceiver - if pa.Source == nil { - pa.Source = new(frames.Source) - } - pa.Source.Dynamic = r.l.dynamicAddr - }, func(pa *frames.PerformAttach) { - if r.l.source == nil { - r.l.source = new(frames.Source) - } - // if dynamic address requested, copy assigned name to address - if r.l.dynamicAddr && pa.Source != nil { - r.l.source.Address = pa.Source.Address - } - // deliveryCount is a sequence number, must initialize to sender's initial sequence number - r.l.deliveryCount = pa.InitialDeliveryCount - r.unsettledMessages = map[string]struct{}{} - // copy the received filter values - if pa.Source != nil { - r.l.source.Filter = pa.Source.Filter - } - }); err != nil { - return err - } - - return nil -} - -func nop() {} - -type receiverTestHooks struct { - MuxStart func() - MuxSelect func() -} - -func (r *Receiver) mux(hooks receiverTestHooks) { - if hooks.MuxSelect == nil { - hooks.MuxSelect = nop - } - if hooks.MuxStart == nil { - hooks.MuxStart = nop - } - - defer func() { - // unblock any in flight message dispositions - r.inFlight.clear(r.l.doneErr) - - if !r.autoSendFlow { - // unblock any pending drain requests - r.creditor.EndDrain() - } - - close(r.l.done) - }() - - hooks.MuxStart() - - if r.autoSendFlow { - r.l.doneErr = r.muxFlow(r.l.linkCredit, false) - } - - for { - msgLen := r.messagesQ.Len() - - r.settlementCountMu.Lock() - // counter that accumulates the settled delivery count. - // once the threshold has been reached, the counter is - // reset and a flow frame is sent. - previousSettlementCount := r.settlementCount - if previousSettlementCount >= r.l.linkCredit { - r.settlementCount = 0 - } - r.settlementCountMu.Unlock() - - // once we have pending credit equal to or greater than our available credit, reclaim it. - // we do this instead of settlementCount > 0 to prevent flow frames from being too chatty. - // NOTE: we compare the settlementCount against the current link credit instead of some - // fixed threshold to ensure credit is reclaimed in cases where the number of unsettled - // messages remains high for whatever reason. - if r.autoSendFlow && previousSettlementCount > 0 && previousSettlementCount >= r.l.linkCredit { - debug.Log(1, "RX (Receiver %p) (auto): source: %q, inflight: %d, linkCredit: %d, deliveryCount: %d, messages: %d, unsettled: %d, settlementCount: %d, settleMode: %s", - r, r.l.source.Address, r.inFlight.len(), r.l.linkCredit, r.l.deliveryCount, msgLen, r.countUnsettled(), previousSettlementCount, r.l.receiverSettleMode.String()) - r.l.doneErr = r.creditor.IssueCredit(previousSettlementCount) - } else if r.l.linkCredit == 0 { - debug.Log(1, "RX (Receiver %p) (pause): source: %q, inflight: %d, linkCredit: %d, deliveryCount: %d, messages: %d, unsettled: %d, settlementCount: %d, settleMode: %s", - r, r.l.source.Address, r.inFlight.len(), r.l.linkCredit, r.l.deliveryCount, msgLen, r.countUnsettled(), previousSettlementCount, r.l.receiverSettleMode.String()) - } - - if r.l.doneErr != nil { - return - } - - drain, credits := r.creditor.FlowBits(r.l.linkCredit) - if drain || credits > 0 { - debug.Log(1, "RX (Receiver %p) (flow): source: %q, inflight: %d, curLinkCredit: %d, newLinkCredit: %d, drain: %v, deliveryCount: %d, messages: %d, unsettled: %d, settlementCount: %d, settleMode: %s", - r, r.l.source.Address, r.inFlight.len(), r.l.linkCredit, credits, drain, r.l.deliveryCount, msgLen, r.countUnsettled(), previousSettlementCount, r.l.receiverSettleMode.String()) - - // send a flow frame. - r.l.doneErr = r.muxFlow(credits, drain) - } - - if r.l.doneErr != nil { - return - } - - txDisposition := r.txDisposition - closed := r.l.close - if r.l.closeInProgress { - // swap out channel so it no longer triggers - closed = nil - - // disable sending of disposition frames once closing is in progress. - // this is to prevent races between mux shutdown and clearing of - // any in-flight dispositions. - txDisposition = nil - } - - hooks.MuxSelect() - - select { - case q := <-r.l.rxQ.Wait(): - // populated queue - fr := *q.Dequeue() - r.l.rxQ.Release(q) - - // if muxHandleFrame returns an error it means the mux must terminate. - // note that in the case of a client-side close due to an error, nil - // is returned in order to keep the mux running to ack the detach frame. - if err := r.muxHandleFrame(fr); err != nil { - r.l.doneErr = err - return - } - - case env := <-txDisposition: - r.l.txFrame(env.Ctx, env.FrameBody, env.Sent) - - case <-r.receiverReady: - continue - - case <-closed: - if r.l.closeInProgress { - // a client-side close due to protocol error is in progress - continue - } - - // receiver is being closed by the client - r.l.closeInProgress = true - fr := &frames.PerformDetach{ - Handle: r.l.handle, - Closed: true, - } - r.l.txFrame(context.Background(), fr, nil) - - case <-r.l.session.done: - r.l.doneErr = r.l.session.doneErr - return - } - } -} - -// muxFlow sends tr to the session mux. -// l.linkCredit will also be updated to `linkCredit` -func (r *Receiver) muxFlow(linkCredit uint32, drain bool) error { - var ( - deliveryCount = r.l.deliveryCount - ) - - fr := &frames.PerformFlow{ - Handle: &r.l.handle, - DeliveryCount: &deliveryCount, - LinkCredit: &linkCredit, // max number of messages, - Drain: drain, - } - - // Update credit. This must happen before entering loop below - // because incoming messages handled while waiting to transmit - // flow increment deliveryCount. This causes the credit to become - // out of sync with the server. - - if !drain { - // if we're draining we don't want to touch our internal credit - we're not changing it so any issued credits - // are still valid until drain completes, at which point they will be naturally zeroed. - r.l.linkCredit = linkCredit - } - - select { - case r.l.session.tx <- frameBodyEnvelope{Ctx: context.Background(), FrameBody: fr}: - debug.Log(2, "TX (Receiver %p): mux frame to Session (%p): %d, %s", r, r.l.session, r.l.session.channel, fr) - return nil - case <-r.l.close: - return nil - case <-r.l.session.done: - return r.l.session.doneErr - } -} - -// muxHandleFrame processes fr based on type. -func (r *Receiver) muxHandleFrame(fr frames.FrameBody) error { - debug.Log(2, "RX (Receiver %p): %s", r, fr) - switch fr := fr.(type) { - // message frame - case *frames.PerformTransfer: - r.muxReceive(*fr) - - // flow control frame - case *frames.PerformFlow: - if !fr.Echo { - // if the 'drain' flag has been set in the frame sent to the _receiver_ then - // we signal whomever is waiting (the service has seen and acknowledged our drain) - if fr.Drain && !r.autoSendFlow { - r.l.linkCredit = 0 // we have no active credits at this point. - r.creditor.EndDrain() - } - return nil - } - - var ( - // copy because sent by pointer below; prevent race - linkCredit = r.l.linkCredit - deliveryCount = r.l.deliveryCount - ) - - // send flow - resp := &frames.PerformFlow{ - Handle: &r.l.handle, - DeliveryCount: &deliveryCount, - LinkCredit: &linkCredit, // max number of messages - } - - select { - case r.l.session.tx <- frameBodyEnvelope{Ctx: context.Background(), FrameBody: resp}: - debug.Log(2, "TX (Receiver %p): mux frame to Session (%p): %d, %s", r, r.l.session, r.l.session.channel, resp) - case <-r.l.close: - return nil - case <-r.l.session.done: - return r.l.session.doneErr - } - - case *frames.PerformDisposition: - // Unblock receivers waiting for message disposition - // bubble disposition error up to the receiver - var dispositionError error - if state, ok := fr.State.(*encoding.StateRejected); ok { - // state.Error isn't required to be filled out. For instance if you dead letter a message - // you will get a rejected response that doesn't contain an error. - if state.Error != nil { - dispositionError = state.Error - } - } - // removal from the in-flight map will also remove the message from the unsettled map - count := r.inFlight.remove(fr.First, fr.Last, dispositionError, func(msg *Message) { - r.deleteUnsettled(msg) - msg.settled = true - }) - r.onSettlement(count) - - default: - return r.l.muxHandleFrame(fr) - } - - return nil -} - -func (r *Receiver) muxReceive(fr frames.PerformTransfer) { - if !r.more { - // this is the first transfer of a message, - // record the delivery ID, message format, - // and delivery Tag - if fr.DeliveryID != nil { - r.msg.deliveryID = *fr.DeliveryID - } - if fr.MessageFormat != nil { - r.msg.Format = *fr.MessageFormat - } - r.msg.DeliveryTag = fr.DeliveryTag - - // these fields are required on first transfer of a message - if fr.DeliveryID == nil { - r.l.closeWithError(ErrCondNotAllowed, "received message without a delivery-id") - return - } - if fr.MessageFormat == nil { - r.l.closeWithError(ErrCondNotAllowed, "received message without a message-format") - return - } - if fr.DeliveryTag == nil { - r.l.closeWithError(ErrCondNotAllowed, "received message without a delivery-tag") - return - } - } else { - // this is a continuation of a multipart message - // some fields may be omitted on continuation transfers, - // but if they are included they must be consistent - // with the first. - - if fr.DeliveryID != nil && *fr.DeliveryID != r.msg.deliveryID { - msg := fmt.Sprintf( - "received continuation transfer with inconsistent delivery-id: %d != %d", - *fr.DeliveryID, r.msg.deliveryID, - ) - r.l.closeWithError(ErrCondNotAllowed, msg) - return - } - if fr.MessageFormat != nil && *fr.MessageFormat != r.msg.Format { - msg := fmt.Sprintf( - "received continuation transfer with inconsistent message-format: %d != %d", - *fr.MessageFormat, r.msg.Format, - ) - r.l.closeWithError(ErrCondNotAllowed, msg) - return - } - if fr.DeliveryTag != nil && !bytes.Equal(fr.DeliveryTag, r.msg.DeliveryTag) { - msg := fmt.Sprintf( - "received continuation transfer with inconsistent delivery-tag: %q != %q", - fr.DeliveryTag, r.msg.DeliveryTag, - ) - r.l.closeWithError(ErrCondNotAllowed, msg) - return - } - } - - // discard message if it's been aborted - if fr.Aborted { - r.msgBuf.Reset() - r.msg = Message{} - r.more = false - return - } - - // ensure maxMessageSize will not be exceeded - if r.l.maxMessageSize != 0 && uint64(r.msgBuf.Len())+uint64(len(fr.Payload)) > r.l.maxMessageSize { - r.l.closeWithError(ErrCondMessageSizeExceeded, fmt.Sprintf("received message larger than max size of %d", r.l.maxMessageSize)) - return - } - - // add the payload the the buffer - r.msgBuf.Append(fr.Payload) - - // mark as settled if at least one frame is settled - r.msg.settled = r.msg.settled || fr.Settled - - // save in-progress status - r.more = fr.More - - if fr.More { - return - } - - // last frame in message - err := r.msg.Unmarshal(&r.msgBuf) - if err != nil { - r.l.closeWithError(ErrCondInternalError, err.Error()) - return - } - - // send to receiver - if !r.msg.settled { - r.addUnsettled(&r.msg) - debug.Log(3, "RX (Receiver %p): add unsettled delivery ID %d", r, r.msg.deliveryID) - } - - q := r.messagesQ.Acquire() - q.Enqueue(r.msg) - msgLen := q.Len() - r.messagesQ.Release(q) - - // reset progress - r.msgBuf.Reset() - r.msg = Message{} - - // decrement link-credit after entire message received - r.l.deliveryCount++ - r.l.linkCredit-- - debug.Log(3, "RX (Receiver %p) link %s - deliveryCount: %d, linkCredit: %d, len(messages): %d", r, r.l.key.name, r.l.deliveryCount, r.l.linkCredit, msgLen) -} - -// inFlight tracks in-flight message dispositions allowing receivers -// to block waiting for the server to respond when an appropriate -// settlement mode is configured. -type inFlight struct { - mu sync.RWMutex - m map[uint32]inFlightInfo -} - -type inFlightInfo struct { - wait chan error - msg *Message -} - -func (f *inFlight) add(msg *Message) chan error { - wait := make(chan error, 1) - - f.mu.Lock() - if f.m == nil { - f.m = make(map[uint32]inFlightInfo) - } - - f.m[msg.deliveryID] = inFlightInfo{wait: wait, msg: msg} - f.mu.Unlock() - - return wait -} - -func (f *inFlight) remove(first uint32, last *uint32, err error, handler func(*Message)) uint32 { - f.mu.Lock() - - if f.m == nil { - f.mu.Unlock() - return 0 - } - - ll := first - if last != nil { - ll = *last - } - - count := uint32(0) - for i := first; i <= ll; i++ { - info, ok := f.m[i] - if ok { - handler(info.msg) - info.wait <- err - delete(f.m, i) - count++ - } - } - - f.mu.Unlock() - return count -} - -func (f *inFlight) clear(err error) { - f.mu.Lock() - for id, info := range f.m { - info.wait <- err - delete(f.m, id) - } - f.mu.Unlock() -} - -func (f *inFlight) len() int { - f.mu.RLock() - defer f.mu.RUnlock() - return len(f.m) -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/sasl.go b/sdk/messaging/azeventhubs/internal/go-amqp/sasl.go deleted file mode 100644 index 11d185140dbf..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/sasl.go +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "context" - "fmt" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/debug" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/frames" -) - -// SASL Mechanisms -const ( - saslMechanismPLAIN encoding.Symbol = "PLAIN" - saslMechanismANONYMOUS encoding.Symbol = "ANONYMOUS" - saslMechanismEXTERNAL encoding.Symbol = "EXTERNAL" - saslMechanismXOAUTH2 encoding.Symbol = "XOAUTH2" -) - -// SASLType represents a SASL configuration to use during authentication. -type SASLType func(c *Conn) error - -// ConnSASLPlain enables SASL PLAIN authentication for the connection. -// -// SASL PLAIN transmits credentials in plain text and should only be used -// on TLS/SSL enabled connection. -func SASLTypePlain(username, password string) SASLType { - // TODO: how widely used is hostname? should it be supported - return func(c *Conn) error { - // make handlers map if no other mechanism has - if c.saslHandlers == nil { - c.saslHandlers = make(map[encoding.Symbol]stateFunc) - } - - // add the handler the the map - c.saslHandlers[saslMechanismPLAIN] = func(ctx context.Context) (stateFunc, error) { - // send saslInit with PLAIN payload - init := &frames.SASLInit{ - Mechanism: "PLAIN", - InitialResponse: []byte("\x00" + username + "\x00" + password), - Hostname: "", - } - fr := frames.Frame{ - Type: frames.TypeSASL, - Body: init, - } - debug.Log(1, "TX (ConnSASLPlain %p): %s", c, fr) - timeout, err := c.getWriteTimeout(ctx) - if err != nil { - return nil, err - } - if err = c.writeFrame(timeout, fr); err != nil { - return nil, err - } - - // go to c.saslOutcome to handle the server response - return c.saslOutcome, nil - } - return nil - } -} - -// ConnSASLAnonymous enables SASL ANONYMOUS authentication for the connection. -func SASLTypeAnonymous() SASLType { - return func(c *Conn) error { - // make handlers map if no other mechanism has - if c.saslHandlers == nil { - c.saslHandlers = make(map[encoding.Symbol]stateFunc) - } - - // add the handler the the map - c.saslHandlers[saslMechanismANONYMOUS] = func(ctx context.Context) (stateFunc, error) { - init := &frames.SASLInit{ - Mechanism: saslMechanismANONYMOUS, - InitialResponse: []byte("anonymous"), - } - fr := frames.Frame{ - Type: frames.TypeSASL, - Body: init, - } - debug.Log(1, "TX (ConnSASLAnonymous %p): %s", c, fr) - timeout, err := c.getWriteTimeout(ctx) - if err != nil { - return nil, err - } - if err = c.writeFrame(timeout, fr); err != nil { - return nil, err - } - - // go to c.saslOutcome to handle the server response - return c.saslOutcome, nil - } - return nil - } -} - -// ConnSASLExternal enables SASL EXTERNAL authentication for the connection. -// The value for resp is dependent on the type of authentication (empty string is common for TLS). -// See https://datatracker.ietf.org/doc/html/rfc4422#appendix-A for additional info. -func SASLTypeExternal(resp string) SASLType { - return func(c *Conn) error { - // make handlers map if no other mechanism has - if c.saslHandlers == nil { - c.saslHandlers = make(map[encoding.Symbol]stateFunc) - } - - // add the handler the the map - c.saslHandlers[saslMechanismEXTERNAL] = func(ctx context.Context) (stateFunc, error) { - init := &frames.SASLInit{ - Mechanism: saslMechanismEXTERNAL, - InitialResponse: []byte(resp), - } - fr := frames.Frame{ - Type: frames.TypeSASL, - Body: init, - } - debug.Log(1, "TX (ConnSASLExternal %p): %s", c, fr) - timeout, err := c.getWriteTimeout(ctx) - if err != nil { - return nil, err - } - if err = c.writeFrame(timeout, fr); err != nil { - return nil, err - } - - // go to c.saslOutcome to handle the server response - return c.saslOutcome, nil - } - return nil - } -} - -// ConnSASLXOAUTH2 enables SASL XOAUTH2 authentication for the connection. -// -// The saslMaxFrameSizeOverride parameter allows the limit that governs the maximum frame size this client will allow -// itself to generate to be raised for the sasl-init frame only. Set this when the size of the size of the SASL XOAUTH2 -// initial client response (which contains the username and bearer token) would otherwise breach the 512 byte min-max-frame-size -// (http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#definition-MIN-MAX-FRAME-SIZE). Pass -1 -// to keep the default. -// -// SASL XOAUTH2 transmits the bearer in plain text and should only be used -// on TLS/SSL enabled connection. -func SASLTypeXOAUTH2(username, bearer string, saslMaxFrameSizeOverride uint32) SASLType { - return func(c *Conn) error { - // make handlers map if no other mechanism has - if c.saslHandlers == nil { - c.saslHandlers = make(map[encoding.Symbol]stateFunc) - } - - response, err := saslXOAUTH2InitialResponse(username, bearer) - if err != nil { - return err - } - - handler := saslXOAUTH2Handler{ - conn: c, - maxFrameSizeOverride: saslMaxFrameSizeOverride, - response: response, - } - // add the handler the the map - c.saslHandlers[saslMechanismXOAUTH2] = handler.init - return nil - } -} - -type saslXOAUTH2Handler struct { - conn *Conn - maxFrameSizeOverride uint32 - response []byte - errorResponse []byte // https://developers.google.com/gmail/imap/xoauth2-protocol#error_response -} - -func (s saslXOAUTH2Handler) init(ctx context.Context) (stateFunc, error) { - originalPeerMaxFrameSize := s.conn.peerMaxFrameSize - if s.maxFrameSizeOverride > s.conn.peerMaxFrameSize { - s.conn.peerMaxFrameSize = s.maxFrameSizeOverride - } - timeout, err := s.conn.getWriteTimeout(ctx) - if err != nil { - return nil, err - } - err = s.conn.writeFrame(timeout, frames.Frame{ - Type: frames.TypeSASL, - Body: &frames.SASLInit{ - Mechanism: saslMechanismXOAUTH2, - InitialResponse: s.response, - }, - }) - s.conn.peerMaxFrameSize = originalPeerMaxFrameSize - if err != nil { - return nil, err - } - - return s.step, nil -} - -func (s saslXOAUTH2Handler) step(ctx context.Context) (stateFunc, error) { - // read challenge or outcome frame - fr, err := s.conn.readFrame() - if err != nil { - return nil, err - } - - switch v := fr.Body.(type) { - case *frames.SASLOutcome: - // check if auth succeeded - if v.Code != encoding.CodeSASLOK { - return nil, fmt.Errorf("SASL XOAUTH2 auth failed with code %#00x: %s : %s", - v.Code, v.AdditionalData, s.errorResponse) - } - - // return to c.negotiateProto - s.conn.saslComplete = true - return s.conn.negotiateProto, nil - case *frames.SASLChallenge: - if s.errorResponse == nil { - s.errorResponse = v.Challenge - - timeout, err := s.conn.getWriteTimeout(ctx) - if err != nil { - return nil, err - } - - // The SASL protocol requires clients to send an empty response to this challenge. - err = s.conn.writeFrame(timeout, frames.Frame{ - Type: frames.TypeSASL, - Body: &frames.SASLResponse{ - Response: []byte{}, - }, - }) - if err != nil { - return nil, err - } - return s.step, nil - } else { - return nil, fmt.Errorf("SASL XOAUTH2 unexpected additional error response received during "+ - "exchange. Initial error response: %s, additional response: %s", s.errorResponse, v.Challenge) - } - default: - return nil, fmt.Errorf("sasl: unexpected frame type %T", fr.Body) - } -} - -func saslXOAUTH2InitialResponse(username string, bearer string) ([]byte, error) { - if len(bearer) == 0 { - return []byte{}, fmt.Errorf("unacceptable bearer token") - } - for _, char := range bearer { - if char < '\x20' || char > '\x7E' { - return []byte{}, fmt.Errorf("unacceptable bearer token") - } - } - for _, char := range username { - if char == '\x01' { - return []byte{}, fmt.Errorf("unacceptable username") - } - } - return []byte("user=" + username + "\x01auth=Bearer " + bearer + "\x01\x01"), nil -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/sender.go b/sdk/messaging/azeventhubs/internal/go-amqp/sender.go deleted file mode 100644 index bb130ba17b2d..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/sender.go +++ /dev/null @@ -1,476 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "context" - "encoding/binary" - "errors" - "fmt" - "sync" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/buffer" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/debug" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/frames" -) - -// Sender sends messages on a single AMQP link. -type Sender struct { - l link - transfers chan transferEnvelope // sender uses to send transfer frames - - mu sync.Mutex // protects buf and nextDeliveryTag - buf buffer.Buffer - nextDeliveryTag uint64 -} - -// LinkName() is the name of the link used for this Sender. -func (s *Sender) LinkName() string { - return s.l.key.name -} - -// MaxMessageSize is the maximum size of a single message. -func (s *Sender) MaxMessageSize() uint64 { - return s.l.maxMessageSize -} - -// SendOptions contains any optional values for the Sender.Send method. -type SendOptions struct { - // Indicates the message is to be sent as settled when settlement mode is SenderSettleModeMixed. - // If the settlement mode is SenderSettleModeUnsettled and Settled is true, an error is returned. - Settled bool -} - -// Send sends a Message. -// -// Blocks until the message is sent or an error occurs. If the peer is -// configured for receiver settlement mode second, the call also blocks -// until the peer confirms message settlement. -// -// - ctx controls waiting for the message to be sent and possibly confirmed -// - msg is the message to send -// - opts contains optional values, pass nil to accept the defaults -// -// If the context's deadline expires or is cancelled before the operation -// completes, the message is in an unknown state of transmission. -// -// Send is safe for concurrent use. Since only a single message can be -// sent on a link at a time, this is most useful when settlement confirmation -// has been requested (receiver settle mode is second). In this case, -// additional messages can be sent while the current goroutine is waiting -// for the confirmation. -func (s *Sender) Send(ctx context.Context, msg *Message, opts *SendOptions) error { - // check if the link is dead. while it's safe to call s.send - // in this case, this will avoid some allocations etc. - select { - case <-s.l.done: - return s.l.doneErr - default: - // link is still active - } - done, err := s.send(ctx, msg, opts) - if err != nil { - return err - } - - // wait for transfer to be confirmed - select { - case state := <-done: - if state, ok := state.(*encoding.StateRejected); ok { - if state.Error != nil { - return state.Error - } - return errors.New("the peer rejected the message without specifying an error") - } - return nil - case <-s.l.done: - return s.l.doneErr - case <-ctx.Done(): - // TODO: if the message is not settled and we never received a disposition, how can we consider the message as sent? - return ctx.Err() - } -} - -// send is separated from Send so that the mutex unlock can be deferred without -// locking the transfer confirmation that happens in Send. -func (s *Sender) send(ctx context.Context, msg *Message, opts *SendOptions) (chan encoding.DeliveryState, error) { - const ( - maxDeliveryTagLength = 32 - maxTransferFrameHeader = 66 // determined by calcMaxTransferFrameHeader - ) - if len(msg.DeliveryTag) > maxDeliveryTagLength { - return nil, fmt.Errorf("delivery tag is over the allowed %v bytes, len: %v", maxDeliveryTagLength, len(msg.DeliveryTag)) - } - - s.mu.Lock() - defer s.mu.Unlock() - - s.buf.Reset() - err := msg.Marshal(&s.buf) - if err != nil { - return nil, err - } - - if s.l.maxMessageSize != 0 && uint64(s.buf.Len()) > s.l.maxMessageSize { - return nil, fmt.Errorf("encoded message size exceeds max of %d", s.l.maxMessageSize) - } - - senderSettled := senderSettleModeValue(s.l.senderSettleMode) == SenderSettleModeSettled - if opts != nil { - if opts.Settled && senderSettleModeValue(s.l.senderSettleMode) == SenderSettleModeUnsettled { - return nil, errors.New("can't send message as settled when sender settlement mode is unsettled") - } else if opts.Settled { - senderSettled = true - } - } - - var ( - maxPayloadSize = int64(s.l.session.conn.peerMaxFrameSize) - maxTransferFrameHeader - ) - - deliveryTag := msg.DeliveryTag - if len(deliveryTag) == 0 { - // use uint64 encoded as []byte as deliveryTag - deliveryTag = make([]byte, 8) - binary.BigEndian.PutUint64(deliveryTag, s.nextDeliveryTag) - s.nextDeliveryTag++ - } - - fr := frames.PerformTransfer{ - Handle: s.l.handle, - DeliveryID: &needsDeliveryID, - DeliveryTag: deliveryTag, - MessageFormat: &msg.Format, - More: s.buf.Len() > 0, - } - - for fr.More { - buf, _ := s.buf.Next(maxPayloadSize) - fr.Payload = append([]byte(nil), buf...) - fr.More = s.buf.Len() > 0 - if !fr.More { - // SSM=settled: overrides RSM; no acks. - // SSM=unsettled: sender should wait for receiver to ack - // RSM=first: receiver considers it settled immediately, but must still send ack (SSM=unsettled only) - // RSM=second: receiver sends ack and waits for return ack from sender (SSM=unsettled only) - - // mark final transfer as settled when sender mode is settled - fr.Settled = senderSettled - - // set done on last frame - fr.Done = make(chan encoding.DeliveryState, 1) - } - - // NOTE: we MUST send a copy of fr here since we modify it post send - - sent := make(chan error, 1) - select { - case s.transfers <- transferEnvelope{Ctx: ctx, Frame: fr, Sent: sent}: - // frame was sent to our mux - case <-s.l.done: - return nil, s.l.doneErr - case <-ctx.Done(): - return nil, &Error{Condition: ErrCondTransferLimitExceeded, Description: fmt.Sprintf("credit limit exceeded for sending link %s", s.l.key.name)} - } - - select { - case err := <-sent: - if err != nil { - return nil, err - } - case <-s.l.done: - return nil, s.l.doneErr - } - - // clear values that are only required on first message - fr.DeliveryID = nil - fr.DeliveryTag = nil - fr.MessageFormat = nil - } - - return fr.Done, nil -} - -// Address returns the link's address. -func (s *Sender) Address() string { - if s.l.target == nil { - return "" - } - return s.l.target.Address -} - -// Close closes the Sender and AMQP link. -// - ctx controls waiting for the peer to acknowledge the close -// -// If the context's deadline expires or is cancelled before the operation -// completes, an error is returned. However, the operation will continue to -// execute in the background. Subsequent calls will return a *LinkError -// that contains the context's error message. -func (s *Sender) Close(ctx context.Context) error { - return s.l.closeLink(ctx) -} - -// newSendingLink creates a new sending link and attaches it to the session -func newSender(target string, session *Session, opts *SenderOptions) (*Sender, error) { - l := newLink(session, encoding.RoleSender) - l.target = &frames.Target{Address: target} - l.source = new(frames.Source) - s := &Sender{ - l: l, - } - - if opts == nil { - return s, nil - } - - for _, v := range opts.Capabilities { - s.l.source.Capabilities = append(s.l.source.Capabilities, encoding.Symbol(v)) - } - if opts.Durability > DurabilityUnsettledState { - return nil, fmt.Errorf("invalid Durability %d", opts.Durability) - } - s.l.source.Durable = opts.Durability - if opts.DynamicAddress { - s.l.target.Address = "" - s.l.dynamicAddr = opts.DynamicAddress - } - if opts.ExpiryPolicy != "" { - if err := encoding.ValidateExpiryPolicy(opts.ExpiryPolicy); err != nil { - return nil, err - } - s.l.source.ExpiryPolicy = opts.ExpiryPolicy - } - s.l.source.Timeout = opts.ExpiryTimeout - if opts.Name != "" { - s.l.key.name = opts.Name - } - if opts.Properties != nil { - s.l.properties = make(map[encoding.Symbol]any) - for k, v := range opts.Properties { - if k == "" { - return nil, errors.New("link property key must not be empty") - } - s.l.properties[encoding.Symbol(k)] = v - } - } - if opts.RequestedReceiverSettleMode != nil { - if rsm := *opts.RequestedReceiverSettleMode; rsm > ReceiverSettleModeSecond { - return nil, fmt.Errorf("invalid RequestedReceiverSettleMode %d", rsm) - } - s.l.receiverSettleMode = opts.RequestedReceiverSettleMode - } - if opts.SettlementMode != nil { - if ssm := *opts.SettlementMode; ssm > SenderSettleModeMixed { - return nil, fmt.Errorf("invalid SettlementMode %d", ssm) - } - s.l.senderSettleMode = opts.SettlementMode - } - s.l.source.Address = opts.SourceAddress - for _, v := range opts.TargetCapabilities { - s.l.target.Capabilities = append(s.l.target.Capabilities, encoding.Symbol(v)) - } - if opts.TargetDurability != DurabilityNone { - s.l.target.Durable = opts.TargetDurability - } - if opts.TargetExpiryPolicy != ExpiryPolicySessionEnd { - s.l.target.ExpiryPolicy = opts.TargetExpiryPolicy - } - if opts.TargetExpiryTimeout != 0 { - s.l.target.Timeout = opts.TargetExpiryTimeout - } - return s, nil -} - -func (s *Sender) attach(ctx context.Context) error { - if err := s.l.attach(ctx, func(pa *frames.PerformAttach) { - pa.Role = encoding.RoleSender - if pa.Target == nil { - pa.Target = new(frames.Target) - } - pa.Target.Dynamic = s.l.dynamicAddr - }, func(pa *frames.PerformAttach) { - if s.l.target == nil { - s.l.target = new(frames.Target) - } - - // if dynamic address requested, copy assigned name to address - if s.l.dynamicAddr && pa.Target != nil { - s.l.target.Address = pa.Target.Address - } - }); err != nil { - return err - } - - s.transfers = make(chan transferEnvelope) - - return nil -} - -type senderTestHooks struct { - MuxTransfer func() -} - -func (s *Sender) mux(hooks senderTestHooks) { - if hooks.MuxTransfer == nil { - hooks.MuxTransfer = nop - } - - defer func() { - close(s.l.done) - }() - -Loop: - for { - var outgoingTransfers chan transferEnvelope - if s.l.linkCredit > 0 { - debug.Log(1, "TX (Sender %p) (enable): target: %q, link credit: %d, deliveryCount: %d", s, s.l.target.Address, s.l.linkCredit, s.l.deliveryCount) - outgoingTransfers = s.transfers - } else { - debug.Log(1, "TX (Sender %p) (pause): target: %q, link credit: %d, deliveryCount: %d", s, s.l.target.Address, s.l.linkCredit, s.l.deliveryCount) - } - - closed := s.l.close - if s.l.closeInProgress { - // swap out channel so it no longer triggers - closed = nil - - // disable sending once closing is in progress. - // this prevents races with mux shutdown and - // the peer sending disposition frames. - outgoingTransfers = nil - } - - select { - // received frame - case q := <-s.l.rxQ.Wait(): - // populated queue - fr := *q.Dequeue() - s.l.rxQ.Release(q) - - // if muxHandleFrame returns an error it means the mux must terminate. - // note that in the case of a client-side close due to an error, nil - // is returned in order to keep the mux running to ack the detach frame. - if err := s.muxHandleFrame(fr); err != nil { - s.l.doneErr = err - return - } - - // send data - case env := <-outgoingTransfers: - hooks.MuxTransfer() - select { - case s.l.session.txTransfer <- env: - debug.Log(2, "TX (Sender %p): mux transfer to Session: %d, %s", s, s.l.session.channel, env.Frame) - // decrement link-credit after entire message transferred - if !env.Frame.More { - s.l.deliveryCount++ - s.l.linkCredit-- - // we are the sender and we keep track of the peer's link credit - debug.Log(3, "TX (Sender %p): link: %s, link credit: %d", s, s.l.key.name, s.l.linkCredit) - } - continue Loop - case <-s.l.close: - continue Loop - case <-s.l.session.done: - continue Loop - } - - case <-closed: - if s.l.closeInProgress { - // a client-side close due to protocol error is in progress - continue - } - - // sender is being closed by the client - s.l.closeInProgress = true - fr := &frames.PerformDetach{ - Handle: s.l.handle, - Closed: true, - } - s.l.txFrame(context.Background(), fr, nil) - - case <-s.l.session.done: - s.l.doneErr = s.l.session.doneErr - return - } - } -} - -// muxHandleFrame processes fr based on type. -// depending on the peer's RSM, it might return a disposition frame for sending -func (s *Sender) muxHandleFrame(fr frames.FrameBody) error { - debug.Log(2, "RX (Sender %p): %s", s, fr) - switch fr := fr.(type) { - // flow control frame - case *frames.PerformFlow: - // the sender's link-credit variable MUST be set according to this formula when flow information is given by the receiver: - // link-credit(snd) := delivery-count(rcv) + link-credit(rcv) - delivery-count(snd) - linkCredit := *fr.LinkCredit - s.l.deliveryCount - if fr.DeliveryCount != nil { - // DeliveryCount can be nil if the receiver hasn't processed - // the attach. That shouldn't be the case here, but it's - // what ActiveMQ does. - linkCredit += *fr.DeliveryCount - } - - s.l.linkCredit = linkCredit - - if !fr.Echo { - return nil - } - - var ( - // copy because sent by pointer below; prevent race - deliveryCount = s.l.deliveryCount - ) - - // send flow - resp := &frames.PerformFlow{ - Handle: &s.l.handle, - DeliveryCount: &deliveryCount, - LinkCredit: &linkCredit, // max number of messages - } - - select { - case s.l.session.tx <- frameBodyEnvelope{Ctx: context.Background(), FrameBody: resp}: - debug.Log(2, "TX (Sender %p): mux frame to Session (%p): %d, %s", s, s.l.session, s.l.session.channel, resp) - case <-s.l.close: - return nil - case <-s.l.session.done: - return s.l.session.doneErr - } - - case *frames.PerformDisposition: - if fr.Settled { - return nil - } - - // peer is in mode second, so we must send confirmation of disposition. - // NOTE: the ack must be sent through the session so it can close out - // the in-flight disposition. - dr := &frames.PerformDisposition{ - Role: encoding.RoleSender, - First: fr.First, - Last: fr.Last, - Settled: true, - } - - select { - case s.l.session.tx <- frameBodyEnvelope{Ctx: context.Background(), FrameBody: dr}: - debug.Log(2, "TX (Sender %p): mux frame to Session (%p): %d, %s", s, s.l.session, s.l.session.channel, dr) - case <-s.l.close: - return nil - case <-s.l.session.done: - return s.l.session.doneErr - } - - return nil - - default: - return s.l.muxHandleFrame(fr) - } - - return nil -} diff --git a/sdk/messaging/azeventhubs/internal/go-amqp/session.go b/sdk/messaging/azeventhubs/internal/go-amqp/session.go deleted file mode 100644 index 7a6cfd625441..000000000000 --- a/sdk/messaging/azeventhubs/internal/go-amqp/session.go +++ /dev/null @@ -1,792 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "context" - "errors" - "fmt" - "math" - "sync" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/bitmap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/debug" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/encoding" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/frames" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp/internal/queue" -) - -// Default session options -const ( - defaultWindow = 5000 -) - -// SessionOptions contains the optional settings for configuring an AMQP session. -type SessionOptions struct { - // MaxLinks sets the maximum number of links (Senders/Receivers) - // allowed on the session. - // - // Minimum: 1. - // Default: 4294967295. - MaxLinks uint32 -} - -// Session is an AMQP session. -// -// A session multiplexes Receivers. -type Session struct { - channel uint16 // session's local channel - remoteChannel uint16 // session's remote channel, owned by conn.connReader - conn *Conn // underlying conn - tx chan frameBodyEnvelope // non-transfer frames to be sent; session must track disposition - txTransfer chan transferEnvelope // transfer frames to be sent; session must track disposition - - // frames destined for this session are added to this queue by conn.connReader - rxQ *queue.Holder[frames.FrameBody] - - // flow control - incomingWindow uint32 - outgoingWindow uint32 - needFlowCount uint32 - - handleMax uint32 - - // link management - linksMu sync.RWMutex // used to synchronize link handle allocation - linksByKey map[linkKey]*link // mapping of name+role link - handles *bitmap.Bitmap // allocated handles - - abandonedLinksMu sync.Mutex - abandonedLinks []*link - - // used for gracefully closing session - close chan struct{} // closed by calling Close(). it signals that the end performative should be sent - closeOnce sync.Once - - // part of internal public surface area - done chan struct{} // closed when the session has terminated (mux exited); DO NOT wait on this from within Session.mux() as it will never trigger! - endSent chan struct{} // closed when the end performative has been sent; once this is closed, links MUST NOT send any frames! - doneErr error // contains the mux error state; ONLY written to by the mux and MUST only be read from after done is closed! - closeErr error // contains the error state returned from Close(); ONLY Close() reads/writes this! -} - -func newSession(c *Conn, channel uint16, opts *SessionOptions) *Session { - s := &Session{ - conn: c, - channel: channel, - tx: make(chan frameBodyEnvelope), - txTransfer: make(chan transferEnvelope), - incomingWindow: defaultWindow, - outgoingWindow: defaultWindow, - handleMax: math.MaxUint32 - 1, - linksMu: sync.RWMutex{}, - linksByKey: make(map[linkKey]*link), - close: make(chan struct{}), - done: make(chan struct{}), - endSent: make(chan struct{}), - } - - if opts != nil { - if opts.MaxLinks != 0 { - // MaxLinks is the number of total links. - // handleMax is the max handle ID which starts - // at zero. so we decrement by one - s.handleMax = opts.MaxLinks - 1 - } - } - - // create handle map after options have been applied - s.handles = bitmap.New(s.handleMax) - - s.rxQ = queue.NewHolder(queue.New[frames.FrameBody](int(s.incomingWindow))) - - return s -} - -// waitForFrame waits for an incoming frame to be queued. -// it returns the next frame from the queue, or an error. -// the error is either from the context or conn.doneErr. -// not meant for consumption outside of session.go. -func (s *Session) waitForFrame(ctx context.Context) (frames.FrameBody, error) { - var q *queue.Queue[frames.FrameBody] - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-s.conn.done: - return nil, s.conn.doneErr - case q = <-s.rxQ.Wait(): - // populated queue - } - - fr := q.Dequeue() - s.rxQ.Release(q) - - return *fr, nil -} - -func (s *Session) begin(ctx context.Context) error { - // send Begin to server - begin := &frames.PerformBegin{ - NextOutgoingID: 0, - IncomingWindow: s.incomingWindow, - OutgoingWindow: s.outgoingWindow, - HandleMax: s.handleMax, - } - - if err := s.txFrameAndWait(ctx, begin); err != nil { - return err - } - - // wait for response - fr, err := s.waitForFrame(ctx) - if err != nil { - // if we exit before receiving the ack, our caller will clean up the channel. - // however, it does mean that the peer will now have assigned an outgoing - // channel ID that's not in use. - return err - } - - begin, ok := fr.(*frames.PerformBegin) - if !ok { - // this codepath is hard to hit (impossible?). if the response isn't a PerformBegin and we've not - // yet seen the remote channel number, the default clause in conn.connReader will protect us from that. - // if we have seen the remote channel number then it's likely the session.mux for that channel will - // either swallow the frame or blow up in some other way, both causing this call to hang. - // deallocate session on error. we can't call - // s.Close() as the session mux hasn't started yet. - debug.Log(1, "RX (Session %p): unexpected begin response frame %T", s, fr) - s.conn.deleteSession(s) - if err := s.conn.Close(); err != nil { - return err - } - return &ConnError{inner: fmt.Errorf("unexpected begin response: %#v", fr)} - } - - // start Session multiplexor - go s.mux(begin) - - return nil -} - -// Close closes the session. -// - ctx controls waiting for the peer to acknowledge the session is closed -// -// If the context's deadline expires or is cancelled before the operation -// completes, an error is returned. However, the operation will continue to -// execute in the background. Subsequent calls will return a *SessionError -// that contains the context's error message. -func (s *Session) Close(ctx context.Context) error { - var ctxErr error - s.closeOnce.Do(func() { - close(s.close) - - // once the mux has received the ack'ing end performative, the mux will - // exit which deletes the session and closes s.done. - select { - case <-s.done: - s.closeErr = s.doneErr - - case <-ctx.Done(): - // notify the caller that the close timed out/was cancelled. - // the mux will remain running and once the ack is received it will terminate. - ctxErr = ctx.Err() - - // record that the close timed out/was cancelled. - // subsequent calls to Close() will return this - debug.Log(1, "TX (Session %p) channel %d: %v", s, s.channel, ctxErr) - s.closeErr = &SessionError{inner: ctxErr} - } - }) - - if ctxErr != nil { - return ctxErr - } - - var sessionErr *SessionError - if errors.As(s.closeErr, &sessionErr) && sessionErr.RemoteErr == nil && sessionErr.inner == nil { - // an empty SessionError means the session was cleanly closed by the caller - return nil - } - return s.closeErr -} - -// txFrame sends a frame to the connWriter. -// - ctx is used to provide the write deadline -// - fr is the frame to write to net.Conn -// - sent is the optional channel that will contain the error if the write fails -func (s *Session) txFrame(ctx context.Context, fr frames.FrameBody, sent chan error) { - debug.Log(2, "TX (Session %p) mux frame to Conn (%p): %s", s, s.conn, fr) - s.conn.sendFrame(ctx, frames.Frame{ - Type: frames.TypeAMQP, - Channel: s.channel, - Body: fr, - }, sent) -} - -// txFrameAndWait sends a frame to the connWriter and waits for the write to complete -// - ctx is used to provide the write deadline -// - fr is the frame to write to net.Conn -func (s *Session) txFrameAndWait(ctx context.Context, fr frames.FrameBody) error { - sent := make(chan error, 1) - s.txFrame(ctx, fr, sent) - - select { - case err := <-sent: - return err - case <-s.conn.done: - return s.conn.doneErr - case <-s.done: - return s.doneErr - } -} - -// NewReceiver opens a new receiver link on the session. -// - ctx controls waiting for the peer to create a sending terminus -// - source is the name of the peer's sending terminus -// - opts contains optional values, pass nil to accept the defaults -// -// If the context's deadline expires or is cancelled before the operation -// completes, an error is returned. If the Receiver was successfully -// created, it will be cleaned up in future calls to NewReceiver. -func (s *Session) NewReceiver(ctx context.Context, source string, opts *ReceiverOptions) (*Receiver, error) { - r, err := newReceiver(source, s, opts) - if err != nil { - return nil, err - } - if err = r.attach(ctx); err != nil { - return nil, err - } - - go r.mux(receiverTestHooks{}) - - return r, nil -} - -// NewSender opens a new sender link on the session. -// - ctx controls waiting for the peer to create a receiver terminus -// - target is the name of the peer's receiver terminus -// - opts contains optional values, pass nil to accept the defaults -// -// If the context's deadline expires or is cancelled before the operation -// completes, an error is returned. If the Sender was successfully -// created, it will be cleaned up in future calls to NewSender. -func (s *Session) NewSender(ctx context.Context, target string, opts *SenderOptions) (*Sender, error) { - l, err := newSender(target, s, opts) - if err != nil { - return nil, err - } - if err = l.attach(ctx); err != nil { - return nil, err - } - - go l.mux(senderTestHooks{}) - - return l, nil -} - -func (s *Session) mux(remoteBegin *frames.PerformBegin) { - defer func() { - if s.doneErr == nil { - s.doneErr = &SessionError{} - } else if connErr := (&ConnError{}); !errors.As(s.doneErr, &connErr) { - // only wrap non-ConnError error types - var amqpErr *Error - if errors.As(s.doneErr, &amqpErr) { - s.doneErr = &SessionError{RemoteErr: amqpErr} - } else { - s.doneErr = &SessionError{inner: s.doneErr} - } - } - // Signal goroutines waiting on the session. - close(s.done) - }() - - var ( - links = make(map[uint32]*link) // mapping of remote handles to links - handlesByDeliveryID = make(map[uint32]uint32) // mapping of deliveryIDs to handles - deliveryIDByHandle = make(map[uint32]uint32) // mapping of handles to latest deliveryID - handlesByRemoteDeliveryID = make(map[uint32]uint32) // mapping of remote deliveryID to handles - - settlementByDeliveryID = make(map[uint32]chan encoding.DeliveryState) - - nextDeliveryID uint32 // tracks the next delivery ID for outgoing transfers - - // flow control values - nextOutgoingID uint32 - nextIncomingID = remoteBegin.NextOutgoingID - remoteIncomingWindow = remoteBegin.IncomingWindow - remoteOutgoingWindow = remoteBegin.OutgoingWindow - - closeInProgress bool // indicates the end performative has been sent - ) - - closeWithError := func(e1 *Error, e2 error) { - if closeInProgress { - debug.Log(3, "TX (Session %p): close already pending, discarding %v", s, e1) - return - } - - closeInProgress = true - s.doneErr = e2 - s.txFrame(context.Background(), &frames.PerformEnd{Error: e1}, nil) - close(s.endSent) - } - - for { - txTransfer := s.txTransfer - // disable txTransfer if flow control windows have been exceeded - if remoteIncomingWindow == 0 || s.outgoingWindow == 0 { - debug.Log(1, "TX (Session %p): disabling txTransfer - window exceeded. remoteIncomingWindow: %d outgoingWindow: %d", - s, remoteIncomingWindow, s.outgoingWindow) - txTransfer = nil - } - - tx := s.tx - closed := s.close - if closeInProgress { - // swap out channel so it no longer triggers - closed = nil - - // once the end performative is sent, we're not allowed to send any frames - tx = nil - txTransfer = nil - } - - // notes on client-side closing session - // when session is closed, we must keep the mux running until the ack'ing end performative - // has been received. during this window, the session is allowed to receive frames but cannot - // send them. - // client-side close happens either by user calling Session.Close() or due to mux initiated - // close due to a violation of some invariant (see sending &Error{} to s.close). in the case - // that both code paths have been triggered, we must be careful to preserve the error that - // triggered the mux initiated close so it can be surfaced to the caller. - - select { - // conn has completed, exit - case <-s.conn.done: - s.doneErr = s.conn.doneErr - return - - case <-closed: - if closeInProgress { - // a client-side close due to protocol error is in progress - continue - } - // session is being closed by the client - closeInProgress = true - s.txFrame(context.Background(), &frames.PerformEnd{}, nil) - close(s.endSent) - - // incoming frame - case q := <-s.rxQ.Wait(): - fr := *q.Dequeue() - s.rxQ.Release(q) - debug.Log(2, "RX (Session %p): %s", s, fr) - - switch body := fr.(type) { - // Disposition frames can reference transfers from more than one - // link. Send this frame to all of them. - case *frames.PerformDisposition: - start := body.First - end := start - if body.Last != nil { - end = *body.Last - } - for deliveryID := start; deliveryID <= end; deliveryID++ { - handles := handlesByDeliveryID - if body.Role == encoding.RoleSender { - handles = handlesByRemoteDeliveryID - } - - handle, ok := handles[deliveryID] - if !ok { - debug.Log(2, "RX (Session %p): role %s: didn't find deliveryID %d in handles map", s, body.Role, deliveryID) - continue - } - delete(handles, deliveryID) - - if body.Settled && body.Role == encoding.RoleReceiver { - // check if settlement confirmation was requested, if so - // confirm by closing channel - if done, ok := settlementByDeliveryID[deliveryID]; ok { - delete(settlementByDeliveryID, deliveryID) - select { - case done <- body.State: - default: - } - close(done) - } - } - - link, ok := links[handle] - if !ok { - closeWithError(&Error{ - Condition: ErrCondUnattachedHandle, - Description: "received disposition frame referencing a handle that's not in use", - }, fmt.Errorf("received disposition frame with unknown link handle %d", handle)) - continue - } - - s.muxFrameToLink(link, fr) - } - continue - case *frames.PerformFlow: - if body.NextIncomingID == nil { - // This is a protocol error: - // "[...] MUST be set if the peer has received - // the begin frame for the session" - closeWithError(&Error{ - Condition: ErrCondNotAllowed, - Description: "next-incoming-id not set after session established", - }, errors.New("protocol error: received flow without next-incoming-id after session established")) - continue - } - - // "When the endpoint receives a flow frame from its peer, - // it MUST update the next-incoming-id directly from the - // next-outgoing-id of the frame, and it MUST update the - // remote-outgoing-window directly from the outgoing-window - // of the frame." - nextIncomingID = body.NextOutgoingID - remoteOutgoingWindow = body.OutgoingWindow - - // "The remote-incoming-window is computed as follows: - // - // next-incoming-id(flow) + incoming-window(flow) - next-outgoing-id(endpoint) - // - // If the next-incoming-id field of the flow frame is not set, then remote-incoming-window is computed as follows: - // - // initial-outgoing-id(endpoint) + incoming-window(flow) - next-outgoing-id(endpoint)" - remoteIncomingWindow = body.IncomingWindow - nextOutgoingID - remoteIncomingWindow += *body.NextIncomingID - debug.Log(3, "RX (Session %p): flow - remoteOutgoingWindow: %d remoteIncomingWindow: %d nextOutgoingID: %d", s, remoteOutgoingWindow, remoteIncomingWindow, nextOutgoingID) - - // Send to link if handle is set - if body.Handle != nil { - link, ok := links[*body.Handle] - if !ok { - closeWithError(&Error{ - Condition: ErrCondUnattachedHandle, - Description: "received flow frame referencing a handle that's not in use", - }, fmt.Errorf("received flow frame with unknown link handle %d", body.Handle)) - continue - } - - s.muxFrameToLink(link, fr) - continue - } - - if body.Echo && !closeInProgress { - niID := nextIncomingID - resp := &frames.PerformFlow{ - NextIncomingID: &niID, - IncomingWindow: s.incomingWindow, - NextOutgoingID: nextOutgoingID, - OutgoingWindow: s.outgoingWindow, - } - s.txFrame(context.Background(), resp, nil) - } - - case *frames.PerformAttach: - // On Attach response link should be looked up by name, then added - // to the links map with the remote's handle contained in this - // attach frame. - // - // Note body.Role is the remote peer's role, we reverse for the local key. - s.linksMu.RLock() - link, linkOk := s.linksByKey[linkKey{name: body.Name, role: !body.Role}] - s.linksMu.RUnlock() - if !linkOk { - closeWithError(&Error{ - Condition: ErrCondNotAllowed, - Description: "received mismatched attach frame", - }, fmt.Errorf("protocol error: received mismatched attach frame %+v", body)) - continue - } - - link.remoteHandle = body.Handle - links[link.remoteHandle] = link - - s.muxFrameToLink(link, fr) - - case *frames.PerformTransfer: - s.needFlowCount++ - // "Upon receiving a transfer, the receiving endpoint will - // increment the next-incoming-id to match the implicit - // transfer-id of the incoming transfer plus one, as well - // as decrementing the remote-outgoing-window, and MAY - // (depending on policy) decrement its incoming-window." - nextIncomingID++ - // don't loop to intmax - if remoteOutgoingWindow > 0 { - remoteOutgoingWindow-- - } - link, ok := links[body.Handle] - if !ok { - closeWithError(&Error{ - Condition: ErrCondUnattachedHandle, - Description: "received transfer frame referencing a handle that's not in use", - }, fmt.Errorf("received transfer frame with unknown link handle %d", body.Handle)) - continue - } - - s.muxFrameToLink(link, fr) - - // if this message is received unsettled and link rcv-settle-mode == second, add to handlesByRemoteDeliveryID - if !body.Settled && body.DeliveryID != nil && link.receiverSettleMode != nil && *link.receiverSettleMode == ReceiverSettleModeSecond { - debug.Log(1, "RX (Session %p): adding handle to handlesByRemoteDeliveryID. delivery ID: %d", s, *body.DeliveryID) - handlesByRemoteDeliveryID[*body.DeliveryID] = body.Handle - } - - // Update peer's outgoing window if half has been consumed. - if s.needFlowCount >= s.incomingWindow/2 && !closeInProgress { - debug.Log(3, "RX (Session %p): channel %d: flow - s.needFlowCount(%d) >= s.incomingWindow(%d)/2\n", s, s.channel, s.needFlowCount, s.incomingWindow) - s.needFlowCount = 0 - nID := nextIncomingID - flow := &frames.PerformFlow{ - NextIncomingID: &nID, - IncomingWindow: s.incomingWindow, - NextOutgoingID: nextOutgoingID, - OutgoingWindow: s.outgoingWindow, - } - s.txFrame(context.Background(), flow, nil) - } - - case *frames.PerformDetach: - link, ok := links[body.Handle] - if !ok { - closeWithError(&Error{ - Condition: ErrCondUnattachedHandle, - Description: "received detach frame referencing a handle that's not in use", - }, fmt.Errorf("received detach frame with unknown link handle %d", body.Handle)) - continue - } - s.muxFrameToLink(link, fr) - - // we received a detach frame and sent it to the link. - // this was either the response to a client-side initiated - // detach or our peer detached us. either way, now that - // the link has processed the frame it's detached so we - // are safe to clean up its state. - delete(links, link.remoteHandle) - delete(deliveryIDByHandle, link.handle) - s.deallocateHandle(link) - - case *frames.PerformEnd: - // there are two possibilities: - // - this is the ack to a client-side Close() - // - the peer is ending the session so we must ack - - if closeInProgress { - return - } - - // peer detached us with an error, save it and send the ack - if body.Error != nil { - s.doneErr = body.Error - } - - fr := frames.PerformEnd{} - s.txFrame(context.Background(), &fr, nil) - - // per spec, when end is received, we're no longer allowed to receive frames - return - - default: - debug.Log(1, "RX (Session %p): unexpected frame: %s\n", s, body) - closeWithError(&Error{ - Condition: ErrCondInternalError, - Description: "session received unexpected frame", - }, fmt.Errorf("internal error: unexpected frame %T", body)) - } - - case env := <-txTransfer: - fr := &env.Frame - // record current delivery ID - var deliveryID uint32 - if fr.DeliveryID == &needsDeliveryID { - deliveryID = nextDeliveryID - fr.DeliveryID = &deliveryID - nextDeliveryID++ - deliveryIDByHandle[fr.Handle] = deliveryID - - // add to handleByDeliveryID if not sender-settled - if !fr.Settled { - handlesByDeliveryID[deliveryID] = fr.Handle - } - } else { - // if fr.DeliveryID is nil it must have been added - // to deliveryIDByHandle already - deliveryID = deliveryIDByHandle[fr.Handle] - } - - // log after the delivery ID has been assigned - debug.Log(2, "TX (Session %p): %d, %s", s, s.channel, fr) - - // frame has been sender-settled, remove from map - if fr.Settled { - delete(handlesByDeliveryID, deliveryID) - } - - s.txFrame(env.Ctx, fr, env.Sent) - if sendErr := <-env.Sent; sendErr != nil { - s.doneErr = sendErr - - // put the error back as our sender will read from this channel - env.Sent <- sendErr - return - } - - // if not settled, add done chan to map - if !fr.Settled && fr.Done != nil { - settlementByDeliveryID[deliveryID] = fr.Done - } else if fr.Done != nil { - // sender-settled, close done now that the transfer has been sent - close(fr.Done) - } - - // "Upon sending a transfer, the sending endpoint will increment - // its next-outgoing-id, decrement its remote-incoming-window, - // and MAY (depending on policy) decrement its outgoing-window." - nextOutgoingID++ - // don't decrement if we're at 0 or we could loop to int max - if remoteIncomingWindow != 0 { - remoteIncomingWindow-- - } - - case env := <-tx: - fr := env.FrameBody - debug.Log(2, "TX (Session %p): %d, %s", s, s.channel, fr) - switch fr := env.FrameBody.(type) { - case *frames.PerformDisposition: - if fr.Settled && fr.Role == encoding.RoleSender { - // sender with a peer that's in mode second; sending confirmation of disposition. - // disposition frames can reference a range of delivery IDs, although it's highly - // likely in this case there will only be one. - start := fr.First - end := start - if fr.Last != nil { - end = *fr.Last - } - for deliveryID := start; deliveryID <= end; deliveryID++ { - // send delivery state to the channel and close it to signal - // that the delivery has completed. - if done, ok := settlementByDeliveryID[deliveryID]; ok { - delete(settlementByDeliveryID, deliveryID) - select { - case done <- fr.State: - default: - } - close(done) - } - } - } - s.txFrame(env.Ctx, fr, env.Sent) - case *frames.PerformFlow: - niID := nextIncomingID - fr.NextIncomingID = &niID - fr.IncomingWindow = s.incomingWindow - fr.NextOutgoingID = nextOutgoingID - fr.OutgoingWindow = s.outgoingWindow - s.txFrame(context.Background(), fr, env.Sent) - case *frames.PerformTransfer: - panic("transfer frames must use txTransfer") - default: - s.txFrame(context.Background(), fr, env.Sent) - } - } - } -} - -func (s *Session) allocateHandle(ctx context.Context, l *link) error { - s.linksMu.Lock() - defer s.linksMu.Unlock() - - // Check if link name already exists, if so then an error should be returned - existing := s.linksByKey[l.key] - if existing != nil { - return fmt.Errorf("link with name '%v' already exists", l.key.name) - } - - next, ok := s.handles.Next() - if !ok { - if err := s.Close(ctx); err != nil { - return err - } - // handle numbers are zero-based, report the actual count - return &SessionError{inner: fmt.Errorf("reached session handle max (%d)", s.handleMax+1)} - } - - l.handle = next // allocate handle to the link - s.linksByKey[l.key] = l // add to mapping - - return nil -} - -func (s *Session) deallocateHandle(l *link) { - s.linksMu.Lock() - defer s.linksMu.Unlock() - - delete(s.linksByKey, l.key) - s.handles.Remove(l.handle) -} - -func (s *Session) abandonLink(l *link) { - s.abandonedLinksMu.Lock() - defer s.abandonedLinksMu.Unlock() - s.abandonedLinks = append(s.abandonedLinks, l) -} - -func (s *Session) freeAbandonedLinks(ctx context.Context) error { - s.abandonedLinksMu.Lock() - defer s.abandonedLinksMu.Unlock() - - debug.Log(3, "TX (Session %p): cleaning up %d abandoned links", s, len(s.abandonedLinks)) - - for _, l := range s.abandonedLinks { - dr := &frames.PerformDetach{ - Handle: l.handle, - Closed: true, - } - if err := s.txFrameAndWait(ctx, dr); err != nil { - return err - } - } - - s.abandonedLinks = nil - return nil -} - -func (s *Session) muxFrameToLink(l *link, fr frames.FrameBody) { - q := l.rxQ.Acquire() - q.Enqueue(fr) - l.rxQ.Release(q) - debug.Log(2, "RX (Session %p): mux frame to link (%p): %s, %s", s, l, l.key.name, fr) -} - -// transferEnvelope is used by senders to send transfer frames -type transferEnvelope struct { - Ctx context.Context - Frame frames.PerformTransfer - - // Sent is *never* nil as we use this for confirmation of sending - // NOTE: use a buffered channel of size 1 when populating - Sent chan error -} - -// frameBodyEnvelope is used by senders and receivers to send frames. -type frameBodyEnvelope struct { - Ctx context.Context - FrameBody frames.FrameBody - - // Sent *can* be nil depending on what frame is being sent. - // e.g. sending a disposition frame frame a receiver's settlement - // APIs will have a non-nil channel vs sending a flow frame - // NOTE: use a buffered channel of size 1 when populating - Sent chan error -} - -// the address of this var is a sentinel value indicating -// that a transfer frame is in need of a delivery ID -var needsDeliveryID uint32 From fd320285e93f4f8d839ae68679c0ffaf8053702e Mon Sep 17 00:00:00 2001 From: Richard Park Date: Fri, 5 May 2023 16:52:38 -0700 Subject: [PATCH 7/9] Point all imports at the production go-amqp --- sdk/messaging/azeventhubs/amqp_message.go | 2 +- sdk/messaging/azeventhubs/event_data.go | 2 +- sdk/messaging/azeventhubs/event_data_batch.go | 2 +- sdk/messaging/azeventhubs/event_data_batch_unit_test.go | 2 +- sdk/messaging/azeventhubs/event_data_test.go | 2 +- sdk/messaging/azeventhubs/go.mod | 4 ++-- sdk/messaging/azeventhubs/internal/amqp_fakes.go | 2 +- sdk/messaging/azeventhubs/internal/amqpwrap/amqpwrap.go | 2 +- sdk/messaging/azeventhubs/internal/amqpwrap/mock_amqp_test.go | 2 +- sdk/messaging/azeventhubs/internal/amqpwrap/rpc.go | 2 +- sdk/messaging/azeventhubs/internal/cbs.go | 2 +- sdk/messaging/azeventhubs/internal/cbs_test.go | 2 +- sdk/messaging/azeventhubs/internal/errors.go | 2 +- sdk/messaging/azeventhubs/internal/errors_test.go | 2 +- sdk/messaging/azeventhubs/internal/links_test.go | 2 +- sdk/messaging/azeventhubs/internal/links_unit_test.go | 2 +- sdk/messaging/azeventhubs/internal/mock/mock_amqp.go | 2 +- sdk/messaging/azeventhubs/internal/mock/mock_helpers.go | 2 +- sdk/messaging/azeventhubs/internal/namespace.go | 2 +- sdk/messaging/azeventhubs/internal/namespace_test.go | 2 +- sdk/messaging/azeventhubs/internal/rpc.go | 2 +- sdk/messaging/azeventhubs/internal/rpc_test.go | 2 +- sdk/messaging/azeventhubs/internal/utils/retrier_test.go | 2 +- sdk/messaging/azeventhubs/mgmt.go | 2 +- sdk/messaging/azeventhubs/partition_client.go | 2 +- sdk/messaging/azeventhubs/partition_client_unit_test.go | 2 +- sdk/messaging/azeventhubs/producer_client.go | 2 +- 27 files changed, 28 insertions(+), 28 deletions(-) diff --git a/sdk/messaging/azeventhubs/amqp_message.go b/sdk/messaging/azeventhubs/amqp_message.go index 2b6ca2db8915..2e0bc54045f5 100644 --- a/sdk/messaging/azeventhubs/amqp_message.go +++ b/sdk/messaging/azeventhubs/amqp_message.go @@ -6,7 +6,7 @@ package azeventhubs import ( "time" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" ) // AMQPAnnotatedMessage represents the AMQP message, as received from Event Hubs. diff --git a/sdk/messaging/azeventhubs/event_data.go b/sdk/messaging/azeventhubs/event_data.go index 88ff8f6cfa22..00b89a3ca0e1 100644 --- a/sdk/messaging/azeventhubs/event_data.go +++ b/sdk/messaging/azeventhubs/event_data.go @@ -10,7 +10,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/eh" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" ) // EventData is an event that can be sent, using the ProducerClient, to an Event Hub. diff --git a/sdk/messaging/azeventhubs/event_data_batch.go b/sdk/messaging/azeventhubs/event_data_batch.go index a73e7145df92..edc6517b90b5 100644 --- a/sdk/messaging/azeventhubs/event_data_batch.go +++ b/sdk/messaging/azeventhubs/event_data_batch.go @@ -11,7 +11,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/internal/uuid" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" ) // ErrEventDataTooLarge is returned when a message cannot fit into a batch when using the [azeventhubs.EventDataBatch.AddEventData] function. diff --git a/sdk/messaging/azeventhubs/event_data_batch_unit_test.go b/sdk/messaging/azeventhubs/event_data_batch_unit_test.go index 23224e5e8104..4144f09a3635 100644 --- a/sdk/messaging/azeventhubs/event_data_batch_unit_test.go +++ b/sdk/messaging/azeventhubs/event_data_batch_unit_test.go @@ -10,8 +10,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/mock" + "github.com/Azure/go-amqp" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azeventhubs/event_data_test.go b/sdk/messaging/azeventhubs/event_data_test.go index 7e749069c62c..0c58c9bde4ea 100644 --- a/sdk/messaging/azeventhubs/event_data_test.go +++ b/sdk/messaging/azeventhubs/event_data_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azeventhubs/go.mod b/sdk/messaging/azeventhubs/go.mod index 893c06dc1db4..db6ebc2cd1d6 100644 --- a/sdk/messaging/azeventhubs/go.mod +++ b/sdk/messaging/azeventhubs/go.mod @@ -8,15 +8,15 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 + github.com/Azure/go-amqp v1.0.0 github.com/golang/mock v1.6.0 github.com/joho/godotenv v1.4.0 github.com/stretchr/testify v1.7.1 - nhooyr.io/websocket v1.8.7 + nhooyr.io/websocket v1.8.7 ) require ( code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect - github.com/Azure/go-amqp v1.0.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gofrs/uuid v3.3.0+incompatible // indirect diff --git a/sdk/messaging/azeventhubs/internal/amqp_fakes.go b/sdk/messaging/azeventhubs/internal/amqp_fakes.go index 9f354a0f19dd..a64783a7f207 100644 --- a/sdk/messaging/azeventhubs/internal/amqp_fakes.go +++ b/sdk/messaging/azeventhubs/internal/amqp_fakes.go @@ -7,7 +7,7 @@ import ( "context" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" ) type FakeNSForPartClient struct { diff --git a/sdk/messaging/azeventhubs/internal/amqpwrap/amqpwrap.go b/sdk/messaging/azeventhubs/internal/amqpwrap/amqpwrap.go index dda9958c591e..c1b3524b7ed7 100644 --- a/sdk/messaging/azeventhubs/internal/amqpwrap/amqpwrap.go +++ b/sdk/messaging/azeventhubs/internal/amqpwrap/amqpwrap.go @@ -10,7 +10,7 @@ import ( "errors" "time" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" ) // AMQPReceiver is implemented by *amqp.Receiver diff --git a/sdk/messaging/azeventhubs/internal/amqpwrap/mock_amqp_test.go b/sdk/messaging/azeventhubs/internal/amqpwrap/mock_amqp_test.go index f32dd94aee1e..6f48c0d524e5 100644 --- a/sdk/messaging/azeventhubs/internal/amqpwrap/mock_amqp_test.go +++ b/sdk/messaging/azeventhubs/internal/amqpwrap/mock_amqp_test.go @@ -12,7 +12,7 @@ import ( context "context" reflect "reflect" - go_amqp "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + go_amqp "github.com/Azure/go-amqp" gomock "github.com/golang/mock/gomock" ) diff --git a/sdk/messaging/azeventhubs/internal/amqpwrap/rpc.go b/sdk/messaging/azeventhubs/internal/amqpwrap/rpc.go index 12bab2b2fa88..0a7b7a132ff9 100644 --- a/sdk/messaging/azeventhubs/internal/amqpwrap/rpc.go +++ b/sdk/messaging/azeventhubs/internal/amqpwrap/rpc.go @@ -6,7 +6,7 @@ package amqpwrap import ( "context" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" ) // RPCResponse is the simplified response structure from an RPC like call diff --git a/sdk/messaging/azeventhubs/internal/cbs.go b/sdk/messaging/azeventhubs/internal/cbs.go index e428ef9056ba..4d41921d35fd 100644 --- a/sdk/messaging/azeventhubs/internal/cbs.go +++ b/sdk/messaging/azeventhubs/internal/cbs.go @@ -11,7 +11,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/auth" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" ) const ( diff --git a/sdk/messaging/azeventhubs/internal/cbs_test.go b/sdk/messaging/azeventhubs/internal/cbs_test.go index 13a28559fcff..baa589a96a0e 100644 --- a/sdk/messaging/azeventhubs/internal/cbs_test.go +++ b/sdk/messaging/azeventhubs/internal/cbs_test.go @@ -9,8 +9,8 @@ import ( "testing" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/auth" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/mock" + "github.com/Azure/go-amqp" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azeventhubs/internal/errors.go b/sdk/messaging/azeventhubs/internal/errors.go index c9e011725dfc..71d77e52c7eb 100644 --- a/sdk/messaging/azeventhubs/internal/errors.go +++ b/sdk/messaging/azeventhubs/internal/errors.go @@ -15,7 +15,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" ) type errNonRetriable struct { diff --git a/sdk/messaging/azeventhubs/internal/errors_test.go b/sdk/messaging/azeventhubs/internal/errors_test.go index e51ad2cab27f..b85fd1f768fb 100644 --- a/sdk/messaging/azeventhubs/internal/errors_test.go +++ b/sdk/messaging/azeventhubs/internal/errors_test.go @@ -12,7 +12,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azeventhubs/internal/links_test.go b/sdk/messaging/azeventhubs/internal/links_test.go index 50d9894bc9a9..40be4df96d74 100644 --- a/sdk/messaging/azeventhubs/internal/links_test.go +++ b/sdk/messaging/azeventhubs/internal/links_test.go @@ -13,8 +13,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/test" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azeventhubs/internal/links_unit_test.go b/sdk/messaging/azeventhubs/internal/links_unit_test.go index f869135067c6..9f405f4ac011 100644 --- a/sdk/messaging/azeventhubs/internal/links_unit_test.go +++ b/sdk/messaging/azeventhubs/internal/links_unit_test.go @@ -9,9 +9,9 @@ import ( "testing" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/mock" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/test" + "github.com/Azure/go-amqp" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azeventhubs/internal/mock/mock_amqp.go b/sdk/messaging/azeventhubs/internal/mock/mock_amqp.go index 4b67e349957a..400aa972eea2 100644 --- a/sdk/messaging/azeventhubs/internal/mock/mock_amqp.go +++ b/sdk/messaging/azeventhubs/internal/mock/mock_amqp.go @@ -13,7 +13,7 @@ import ( reflect "reflect" amqpwrap "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" - amqp "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + amqp "github.com/Azure/go-amqp" gomock "github.com/golang/mock/gomock" ) diff --git a/sdk/messaging/azeventhubs/internal/mock/mock_helpers.go b/sdk/messaging/azeventhubs/internal/mock/mock_helpers.go index 32577acb9db5..7ce47cd9c9d2 100644 --- a/sdk/messaging/azeventhubs/internal/mock/mock_helpers.go +++ b/sdk/messaging/azeventhubs/internal/mock/mock_helpers.go @@ -8,7 +8,7 @@ import ( "fmt" "time" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" gomock "github.com/golang/mock/gomock" ) diff --git a/sdk/messaging/azeventhubs/internal/namespace.go b/sdk/messaging/azeventhubs/internal/namespace.go index 8f6b8dc34c8d..6fff62906bac 100644 --- a/sdk/messaging/azeventhubs/internal/namespace.go +++ b/sdk/messaging/azeventhubs/internal/namespace.go @@ -18,9 +18,9 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/auth" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/sbauth" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/utils" + "github.com/Azure/go-amqp" ) var rootUserAgent = telemetry.Format("azeventhubs", Version) diff --git a/sdk/messaging/azeventhubs/internal/namespace_test.go b/sdk/messaging/azeventhubs/internal/namespace_test.go index 13973b73355a..5d0bd2615d74 100644 --- a/sdk/messaging/azeventhubs/internal/namespace_test.go +++ b/sdk/messaging/azeventhubs/internal/namespace_test.go @@ -16,9 +16,9 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/auth" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/sbauth" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/test" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azeventhubs/internal/rpc.go b/sdk/messaging/azeventhubs/internal/rpc.go index 8d5bf1ff1765..ffad41d6dd15 100644 --- a/sdk/messaging/azeventhubs/internal/rpc.go +++ b/sdk/messaging/azeventhubs/internal/rpc.go @@ -14,7 +14,7 @@ import ( azlog "github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/internal/uuid" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" ) const ( diff --git a/sdk/messaging/azeventhubs/internal/rpc_test.go b/sdk/messaging/azeventhubs/internal/rpc_test.go index cf39bd695c17..44c6affed55e 100644 --- a/sdk/messaging/azeventhubs/internal/rpc_test.go +++ b/sdk/messaging/azeventhubs/internal/rpc_test.go @@ -12,9 +12,9 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/mock" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/test" + "github.com/Azure/go-amqp" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azeventhubs/internal/utils/retrier_test.go b/sdk/messaging/azeventhubs/internal/utils/retrier_test.go index acae2d432949..cc3997bc3b06 100644 --- a/sdk/messaging/azeventhubs/internal/utils/retrier_test.go +++ b/sdk/messaging/azeventhubs/internal/utils/retrier_test.go @@ -14,7 +14,7 @@ import ( azlog "github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azeventhubs/mgmt.go b/sdk/messaging/azeventhubs/mgmt.go index 333bf717d2a7..8c31e00bf198 100644 --- a/sdk/messaging/azeventhubs/mgmt.go +++ b/sdk/messaging/azeventhubs/mgmt.go @@ -13,7 +13,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/eh" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" ) // EventHubProperties represents properties of the Event Hub, like the number of partitions. diff --git a/sdk/messaging/azeventhubs/partition_client.go b/sdk/messaging/azeventhubs/partition_client.go index 654099f53016..238cb275babc 100644 --- a/sdk/messaging/azeventhubs/partition_client.go +++ b/sdk/messaging/azeventhubs/partition_client.go @@ -14,7 +14,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" ) // DefaultConsumerGroup is the name of the default consumer group in the Event Hubs service. diff --git a/sdk/messaging/azeventhubs/partition_client_unit_test.go b/sdk/messaging/azeventhubs/partition_client_unit_test.go index cbb570fadd8b..7560dbf7e209 100644 --- a/sdk/messaging/azeventhubs/partition_client_unit_test.go +++ b/sdk/messaging/azeventhubs/partition_client_unit_test.go @@ -10,8 +10,8 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/test" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azeventhubs/producer_client.go b/sdk/messaging/azeventhubs/producer_client.go index 4d85956ff2ac..f887e265838b 100644 --- a/sdk/messaging/azeventhubs/producer_client.go +++ b/sdk/messaging/azeventhubs/producer_client.go @@ -17,7 +17,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp" + "github.com/Azure/go-amqp" ) // WebSocketConnParams are passed to your web socket creation function (ClientOptions.NewWebSocketConn) From 60b671f6a0bd49d10baee1d64ade5a117139c628 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Fri, 5 May 2023 17:05:48 -0700 Subject: [PATCH 8/9] Remove the internal go-amqp, use the public go-amqp package. --- sdk/messaging/azservicebus/amqp_message.go | 2 +- sdk/messaging/azservicebus/go.mod | 4 +- sdk/messaging/azservicebus/go.sum | 11 +- .../azservicebus/internal/amqpLinks_test.go | 2 +- .../azservicebus/internal/amqp_test_utils.go | 2 +- .../internal/amqplinks_unit_test.go | 2 +- .../internal/amqpwrap/amqpwrap.go | 2 +- .../internal/amqpwrap/mock_amqp_test.go | 2 +- .../azservicebus/internal/amqpwrap/rpc.go | 2 +- sdk/messaging/azservicebus/internal/cbs.go | 2 +- sdk/messaging/azservicebus/internal/errors.go | 2 +- .../azservicebus/internal/errors_test.go | 2 +- .../internal/exported/error_test.go | 2 +- .../azservicebus/internal/go-amqp/LICENSE | 22 - .../azservicebus/internal/go-amqp/conn.go | 1135 --------- .../azservicebus/internal/go-amqp/const.go | 96 - .../azservicebus/internal/go-amqp/creditor.go | 119 - .../azservicebus/internal/go-amqp/errors.go | 107 - .../go-amqp/internal/bitmap/bitmap.go | 99 - .../go-amqp/internal/buffer/buffer.go | 180 -- .../internal/go-amqp/internal/debug/debug.go | 20 - .../go-amqp/internal/debug/debug_debug.go | 51 - .../go-amqp/internal/encoding/decode.go | 1150 --------- .../go-amqp/internal/encoding/encode.go | 573 ----- .../go-amqp/internal/encoding/types.go | 2155 ----------------- .../go-amqp/internal/frames/frames.go | 1543 ------------ .../go-amqp/internal/frames/parsing.go | 162 -- .../internal/go-amqp/internal/queue/queue.go | 164 -- .../go-amqp/internal/shared/shared.go | 36 - .../azservicebus/internal/go-amqp/link.go | 390 --- .../internal/go-amqp/link_options.go | 241 -- .../azservicebus/internal/go-amqp/message.go | 492 ---- .../azservicebus/internal/go-amqp/receiver.go | 897 ------- .../azservicebus/internal/go-amqp/sasl.go | 262 -- .../azservicebus/internal/go-amqp/sender.go | 482 ---- .../azservicebus/internal/go-amqp/session.go | 792 ------ sdk/messaging/azservicebus/internal/mgmt.go | 2 +- .../internal/mock/emulation/events.go | 2 +- .../internal/mock/emulation/mock_data.go | 2 +- .../mock/emulation/mock_data_receiver.go | 2 +- .../mock/emulation/mock_data_sender.go | 2 +- .../mock/emulation/mock_data_session.go | 2 +- .../internal/mock/emulation/mock_data_test.go | 2 +- .../internal/mock/emulation/queue.go | 2 +- .../internal/mock/emulation/queue_test.go | 2 +- .../azservicebus/internal/mock/mock_amqp.go | 2 +- .../internal/mock/mock_helpers.go | 2 +- .../azservicebus/internal/mock/mock_rpc.go | 2 +- .../azservicebus/internal/namespace.go | 2 +- .../azservicebus/internal/namespace_test.go | 2 +- sdk/messaging/azservicebus/internal/rpc.go | 2 +- .../azservicebus/internal/rpc_test.go | 2 +- .../internal/test/test_helpers.go | 2 +- .../internal/utils/retrier_test.go | 2 +- sdk/messaging/azservicebus/message.go | 2 +- sdk/messaging/azservicebus/messageSettler.go | 2 +- sdk/messaging/azservicebus/message_batch.go | 2 +- .../azservicebus/message_batch_test.go | 2 +- sdk/messaging/azservicebus/message_test.go | 2 +- sdk/messaging/azservicebus/receiver.go | 2 +- .../azservicebus/receiver_helpers_test.go | 2 +- .../azservicebus/receiver_simulated_test.go | 2 +- sdk/messaging/azservicebus/receiver_test.go | 4 +- .../azservicebus/receiver_unit_test.go | 2 +- sdk/messaging/azservicebus/sender.go | 2 +- .../azservicebus/sender_unit_test.go | 2 +- .../azservicebus/session_receiver.go | 2 +- .../azservicebus/session_receiver_test.go | 2 +- 68 files changed, 52 insertions(+), 11219 deletions(-) delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/LICENSE delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/conn.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/const.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/creditor.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/errors.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/internal/bitmap/bitmap.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/internal/buffer/buffer.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/internal/debug/debug.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/internal/debug/debug_debug.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/internal/encoding/decode.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/internal/encoding/encode.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/internal/encoding/types.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/internal/frames/frames.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/internal/frames/parsing.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/internal/queue/queue.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/internal/shared/shared.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/link.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/link_options.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/message.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/receiver.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/sasl.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/sender.go delete mode 100644 sdk/messaging/azservicebus/internal/go-amqp/session.go diff --git a/sdk/messaging/azservicebus/amqp_message.go b/sdk/messaging/azservicebus/amqp_message.go index c7484257994f..b96eb98d19c4 100644 --- a/sdk/messaging/azservicebus/amqp_message.go +++ b/sdk/messaging/azservicebus/amqp_message.go @@ -6,7 +6,7 @@ package azservicebus import ( "time" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" ) // AMQPAnnotatedMessage represents the AMQP message, as received from Service Bus. diff --git a/sdk/messaging/azservicebus/go.mod b/sdk/messaging/azservicebus/go.mod index 3b70a5f5868e..1c391b1f80c9 100644 --- a/sdk/messaging/azservicebus/go.mod +++ b/sdk/messaging/azservicebus/go.mod @@ -8,6 +8,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 + github.com/Azure/go-amqp v1.0.0 ) require ( @@ -16,7 +17,7 @@ require ( // used in stress tests github.com/microsoft/ApplicationInsights-Go v0.4.4 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.7.1 // used in examples only nhooyr.io/websocket v1.8.7 @@ -30,7 +31,6 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/gofrs/uuid v3.3.0+incompatible // indirect github.com/golang-jwt/jwt v3.2.1+incompatible // indirect - github.com/google/go-cmp v0.5.1 // indirect github.com/google/uuid v1.1.1 // indirect github.com/klauspost/compress v1.10.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect diff --git a/sdk/messaging/azservicebus/go.sum b/sdk/messaging/azservicebus/go.sum index 69c9fadf8fe8..1cefd2440d19 100644 --- a/sdk/messaging/azservicebus/go.sum +++ b/sdk/messaging/azservicebus/go.sum @@ -6,12 +6,15 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0 h1:Yoicul8bnVdQrhDMTHxdE github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM= github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 h1:leh5DwKv6Ihwi+h60uHtn6UWAxBbZ0q8DwQVMzf61zw= github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/go-amqp v1.0.0 h1:QfCugi1M+4F2JDTRgVnRw7PYXLXZ9hmqk3+9+oJh3OA= +github.com/Azure/go-amqp v1.0.0/go.mod h1:+bg0x3ce5+Q3ahCEXnCsGG3ETpDQe3MEVnOuT2ywPwc= github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 h1:WVsrXCnHlDDX8ls+tootqRE87/hL9S/g4ewig9RsD/c= github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= @@ -42,8 +45,7 @@ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -84,8 +86,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tedsuo/ifrit v0.0.0-20180802180643-bea94bb476cc/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -129,7 +131,6 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= diff --git a/sdk/messaging/azservicebus/internal/amqpLinks_test.go b/sdk/messaging/azservicebus/internal/amqpLinks_test.go index 4b063bc1a3d6..fe1558fb20e1 100644 --- a/sdk/messaging/azservicebus/internal/amqpLinks_test.go +++ b/sdk/messaging/azservicebus/internal/amqpLinks_test.go @@ -15,9 +15,9 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/test" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/utils" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/internal/amqp_test_utils.go b/sdk/messaging/azservicebus/internal/amqp_test_utils.go index ef336e3f2650..20dbf52cb7a7 100644 --- a/sdk/messaging/azservicebus/internal/amqp_test_utils.go +++ b/sdk/messaging/azservicebus/internal/amqp_test_utils.go @@ -11,8 +11,8 @@ import ( azlog "github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/utils" + "github.com/Azure/go-amqp" ) type FakeNS struct { diff --git a/sdk/messaging/azservicebus/internal/amqplinks_unit_test.go b/sdk/messaging/azservicebus/internal/amqplinks_unit_test.go index 70e06658f287..dcc7907cd33c 100644 --- a/sdk/messaging/azservicebus/internal/amqplinks_unit_test.go +++ b/sdk/messaging/azservicebus/internal/amqplinks_unit_test.go @@ -13,10 +13,10 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/mock/emulation" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/test" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/utils" + "github.com/Azure/go-amqp" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/internal/amqpwrap/amqpwrap.go b/sdk/messaging/azservicebus/internal/amqpwrap/amqpwrap.go index 6e62d0b383d6..154e6ecb1d09 100644 --- a/sdk/messaging/azservicebus/internal/amqpwrap/amqpwrap.go +++ b/sdk/messaging/azservicebus/internal/amqpwrap/amqpwrap.go @@ -10,7 +10,7 @@ import ( "errors" "time" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" ) // AMQPReceiver is implemented by *amqp.Receiver diff --git a/sdk/messaging/azservicebus/internal/amqpwrap/mock_amqp_test.go b/sdk/messaging/azservicebus/internal/amqpwrap/mock_amqp_test.go index 0fa5e88b86ac..5970412670e6 100644 --- a/sdk/messaging/azservicebus/internal/amqpwrap/mock_amqp_test.go +++ b/sdk/messaging/azservicebus/internal/amqpwrap/mock_amqp_test.go @@ -12,7 +12,7 @@ import ( context "context" reflect "reflect" - go_amqp "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + go_amqp "github.com/Azure/go-amqp" gomock "github.com/golang/mock/gomock" ) diff --git a/sdk/messaging/azservicebus/internal/amqpwrap/rpc.go b/sdk/messaging/azservicebus/internal/amqpwrap/rpc.go index 2bb4e75d890e..4804f1176939 100644 --- a/sdk/messaging/azservicebus/internal/amqpwrap/rpc.go +++ b/sdk/messaging/azservicebus/internal/amqpwrap/rpc.go @@ -6,7 +6,7 @@ package amqpwrap import ( "context" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" ) // RPCResponse is the simplified response structure from an RPC like call diff --git a/sdk/messaging/azservicebus/internal/cbs.go b/sdk/messaging/azservicebus/internal/cbs.go index fd484d93561c..5a43d5e1f7c8 100644 --- a/sdk/messaging/azservicebus/internal/cbs.go +++ b/sdk/messaging/azservicebus/internal/cbs.go @@ -11,7 +11,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/auth" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" ) const ( diff --git a/sdk/messaging/azservicebus/internal/errors.go b/sdk/messaging/azservicebus/internal/errors.go index b87d0621587a..cdeb63d97581 100644 --- a/sdk/messaging/azservicebus/internal/errors.go +++ b/sdk/messaging/azservicebus/internal/errors.go @@ -15,7 +15,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" ) type errNonRetriable struct { diff --git a/sdk/messaging/azservicebus/internal/errors_test.go b/sdk/messaging/azservicebus/internal/errors_test.go index 82658a0b60e6..1343417dcfbc 100644 --- a/sdk/messaging/azservicebus/internal/errors_test.go +++ b/sdk/messaging/azservicebus/internal/errors_test.go @@ -15,7 +15,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/internal/exported/error_test.go b/sdk/messaging/azservicebus/internal/exported/error_test.go index 4fe69bb82344..a64d85d8fb22 100644 --- a/sdk/messaging/azservicebus/internal/exported/error_test.go +++ b/sdk/messaging/azservicebus/internal/exported/error_test.go @@ -6,7 +6,7 @@ package exported import ( "testing" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/internal/go-amqp/LICENSE b/sdk/messaging/azservicebus/internal/go-amqp/LICENSE deleted file mode 100644 index 387b3e7e0f3b..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ - MIT License - - Copyright (C) 2017 Kale Blankenship - Portions Copyright (C) Microsoft Corporation - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE diff --git a/sdk/messaging/azservicebus/internal/go-amqp/conn.go b/sdk/messaging/azservicebus/internal/go-amqp/conn.go deleted file mode 100644 index f6e4b808f922..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/conn.go +++ /dev/null @@ -1,1135 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "bytes" - "context" - "crypto/tls" - "errors" - "fmt" - "math" - "net" - "net/url" - "sync" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/bitmap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/buffer" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/debug" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/frames" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/shared" -) - -// Default connection options -const ( - defaultIdleTimeout = 1 * time.Minute - defaultMaxFrameSize = 65536 - defaultMaxSessions = 65536 - defaultWriteTimeout = 30 * time.Second -) - -// ConnOptions contains the optional settings for configuring an AMQP connection. -type ConnOptions struct { - // ContainerID sets the container-id to use when opening the connection. - // - // A container ID will be randomly generated if this option is not used. - ContainerID string - - // HostName sets the hostname sent in the AMQP - // Open frame and TLS ServerName (if not otherwise set). - HostName string - - // IdleTimeout specifies the maximum period between - // receiving frames from the peer. - // - // Specify a value less than zero to disable idle timeout. - // - // Default: 1 minute (60000000000). - IdleTimeout time.Duration - - // MaxFrameSize sets the maximum frame size that - // the connection will accept. - // - // Must be 512 or greater. - // - // Default: 512. - MaxFrameSize uint32 - - // MaxSessions sets the maximum number of channels. - // The value must be greater than zero. - // - // Default: 65535. - MaxSessions uint16 - - // Properties sets an entry in the connection properties map sent to the server. - Properties map[string]any - - // SASLType contains the specified SASL authentication mechanism. - SASLType SASLType - - // TLSConfig sets the tls.Config to be used during - // TLS negotiation. - // - // This option is for advanced usage, in most scenarios - // providing a URL scheme of "amqps://" is sufficient. - TLSConfig *tls.Config - - // WriteTimeout controls the write deadline when writing AMQP frames to the - // underlying net.Conn and no caller provided context.Context is available or - // the context contains no deadline (e.g. context.Background()). - // The timeout is set per write. - // - // Setting to a value less than zero means no timeout is set, so writes - // defer to the underlying behavior of net.Conn with no write deadline. - // - // Default: 30s - WriteTimeout time.Duration - - // test hook - dialer dialer -} - -// Dial connects to an AMQP server. -// -// If the addr includes a scheme, it must be "amqp", "amqps", or "amqp+ssl". -// If no port is provided, 5672 will be used for "amqp" and 5671 for "amqps" or "amqp+ssl". -// -// If username and password information is not empty it's used as SASL PLAIN -// credentials, equal to passing ConnSASLPlain option. -// -// opts: pass nil to accept the default values. -func Dial(ctx context.Context, addr string, opts *ConnOptions) (*Conn, error) { - c, err := dialConn(ctx, addr, opts) - if err != nil { - return nil, err - } - err = c.start(ctx) - if err != nil { - return nil, err - } - return c, nil -} - -// NewConn establishes a new AMQP client connection over conn. -// opts: pass nil to accept the default values. -func NewConn(ctx context.Context, conn net.Conn, opts *ConnOptions) (*Conn, error) { - c, err := newConn(conn, opts) - if err != nil { - return nil, err - } - err = c.start(ctx) - if err != nil { - return nil, err - } - return c, nil -} - -// Conn is an AMQP connection. -type Conn struct { - net net.Conn // underlying connection - dialer dialer // used for testing purposes, it allows faking dialing TCP/TLS endpoints - writeTimeout time.Duration // controls write deadline in absense of a context - - // TLS - tlsNegotiation bool // negotiate TLS - tlsComplete bool // TLS negotiation complete - tlsConfig *tls.Config // TLS config, default used if nil (ServerName set to Client.hostname) - - // SASL - saslHandlers map[encoding.Symbol]stateFunc // map of supported handlers keyed by SASL mechanism, SASL not negotiated if nil - saslComplete bool // SASL negotiation complete; internal *except* for SASL auth methods - - // local settings - maxFrameSize uint32 // max frame size to accept - channelMax uint16 // maximum number of channels to allow - hostname string // hostname of remote server (set explicitly or parsed from URL) - idleTimeout time.Duration // maximum period between receiving frames - properties map[encoding.Symbol]any // additional properties sent upon connection open - containerID string // set explicitly or randomly generated - - // peer settings - peerIdleTimeout time.Duration // maximum period between sending frames - peerMaxFrameSize uint32 // maximum frame size peer will accept - - // conn state - done chan struct{} // indicates the connection has terminated - doneErr error // contains the error state returned from Close(); DO NOT TOUCH outside of conn.go until done has been closed! - - // connReader and connWriter management - rxtxExit chan struct{} // signals connReader and connWriter to exit - closeOnce sync.Once // ensures that close() is only called once - - // session tracking - channels *bitmap.Bitmap - sessionsByChannel map[uint16]*Session - sessionsByChannelMu sync.RWMutex - - abandonedSessionsMu sync.Mutex - abandonedSessions []*Session - - // connReader - rxBuf buffer.Buffer // incoming bytes buffer - rxDone chan struct{} // closed when connReader exits - rxErr error // contains last error reading from c.net; DO NOT TOUCH outside of connReader until rxDone has been closed! - - // connWriter - txFrame chan frameEnvelope // AMQP frames to be sent by connWriter - txBuf buffer.Buffer // buffer for marshaling frames before transmitting - txDone chan struct{} // closed when connWriter exits - txErr error // contains last error writing to c.net; DO NOT TOUCH outside of connWriter until txDone has been closed! -} - -// used to abstract the underlying dialer for testing purposes -type dialer interface { - NetDialerDial(ctx context.Context, c *Conn, host, port string) error - TLSDialWithDialer(ctx context.Context, c *Conn, host, port string) error -} - -// implements the dialer interface -type defaultDialer struct{} - -func (defaultDialer) NetDialerDial(ctx context.Context, c *Conn, host, port string) (err error) { - dialer := &net.Dialer{} - c.net, err = dialer.DialContext(ctx, "tcp", net.JoinHostPort(host, port)) - return -} - -func (defaultDialer) TLSDialWithDialer(ctx context.Context, c *Conn, host, port string) (err error) { - dialer := &tls.Dialer{Config: c.tlsConfig} - c.net, err = dialer.DialContext(ctx, "tcp", net.JoinHostPort(host, port)) - return -} - -func dialConn(ctx context.Context, addr string, opts *ConnOptions) (*Conn, error) { - u, err := url.Parse(addr) - if err != nil { - return nil, err - } - host, port := u.Hostname(), u.Port() - if port == "" { - port = "5672" - if u.Scheme == "amqps" || u.Scheme == "amqp+ssl" { - port = "5671" - } - } - - var cp ConnOptions - if opts != nil { - cp = *opts - } - - // prepend SASL credentials when the user/pass segment is not empty - if u.User != nil { - pass, _ := u.User.Password() - cp.SASLType = SASLTypePlain(u.User.Username(), pass) - } - - if cp.HostName == "" { - cp.HostName = host - } - - c, err := newConn(nil, &cp) - if err != nil { - return nil, err - } - - switch u.Scheme { - case "amqp", "": - err = c.dialer.NetDialerDial(ctx, c, host, port) - case "amqps", "amqp+ssl": - c.initTLSConfig() - c.tlsNegotiation = false - err = c.dialer.TLSDialWithDialer(ctx, c, host, port) - default: - err = fmt.Errorf("unsupported scheme %q", u.Scheme) - } - - if err != nil { - return nil, err - } - return c, nil -} - -func newConn(netConn net.Conn, opts *ConnOptions) (*Conn, error) { - c := &Conn{ - dialer: defaultDialer{}, - net: netConn, - maxFrameSize: defaultMaxFrameSize, - peerMaxFrameSize: defaultMaxFrameSize, - channelMax: defaultMaxSessions - 1, // -1 because channel-max starts at zero - idleTimeout: defaultIdleTimeout, - containerID: shared.RandString(40), - done: make(chan struct{}), - rxtxExit: make(chan struct{}), - rxDone: make(chan struct{}), - txFrame: make(chan frameEnvelope), - txDone: make(chan struct{}), - sessionsByChannel: map[uint16]*Session{}, - writeTimeout: defaultWriteTimeout, - } - - // apply options - if opts == nil { - opts = &ConnOptions{} - } - - if opts.WriteTimeout > 0 { - c.writeTimeout = opts.WriteTimeout - } else if opts.WriteTimeout < 0 { - c.writeTimeout = 0 - } - if opts.ContainerID != "" { - c.containerID = opts.ContainerID - } - if opts.HostName != "" { - c.hostname = opts.HostName - } - if opts.IdleTimeout > 0 { - c.idleTimeout = opts.IdleTimeout - } else if opts.IdleTimeout < 0 { - c.idleTimeout = 0 - } - if opts.MaxFrameSize > 0 && opts.MaxFrameSize < 512 { - return nil, fmt.Errorf("invalid MaxFrameSize value %d", opts.MaxFrameSize) - } else if opts.MaxFrameSize > 512 { - c.maxFrameSize = opts.MaxFrameSize - } - if opts.MaxSessions > 0 { - c.channelMax = opts.MaxSessions - } - if opts.SASLType != nil { - if err := opts.SASLType(c); err != nil { - return nil, err - } - } - if opts.Properties != nil { - c.properties = make(map[encoding.Symbol]any) - for key, val := range opts.Properties { - c.properties[encoding.Symbol(key)] = val - } - } - if opts.TLSConfig != nil { - c.tlsConfig = opts.TLSConfig.Clone() - } - if opts.dialer != nil { - c.dialer = opts.dialer - } - return c, nil -} - -func (c *Conn) initTLSConfig() { - // create a new config if not already set - if c.tlsConfig == nil { - c.tlsConfig = new(tls.Config) - } - - // TLS config must have ServerName or InsecureSkipVerify set - if c.tlsConfig.ServerName == "" && !c.tlsConfig.InsecureSkipVerify { - c.tlsConfig.ServerName = c.hostname - } -} - -// start establishes the connection and begins multiplexing network IO. -// It is an error to call Start() on a connection that's been closed. -func (c *Conn) start(ctx context.Context) (err error) { - // if the context has a deadline or is cancellable, start the interruptor goroutine. - // this will close the underlying net.Conn in response to the context. - - if ctx.Done() != nil { - done := make(chan struct{}) - interruptRes := make(chan error, 1) - - defer func() { - close(done) - if ctxErr := <-interruptRes; ctxErr != nil { - // return context error to caller - err = ctxErr - } - }() - - go func() { - select { - case <-ctx.Done(): - c.closeDuringStart() - interruptRes <- ctx.Err() - case <-done: - interruptRes <- nil - } - }() - } - - if err = c.startImpl(ctx); err != nil { - return err - } - - // we can't create the channel bitmap until the connection has been established. - // this is because our peer can tell us the max channels they support. - c.channels = bitmap.New(uint32(c.channelMax)) - - go c.connWriter() - go c.connReader() - - return -} - -func (c *Conn) startImpl(ctx context.Context) error { - // set connection establishment deadline as required - if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() { - _ = c.net.SetDeadline(deadline) - - // remove connection establishment deadline - defer func() { - _ = c.net.SetDeadline(time.Time{}) - }() - } - - // run connection establishment state machine - for state := c.negotiateProto; state != nil; { - var err error - state, err = state(ctx) - // check if err occurred - if err != nil { - c.closeDuringStart() - return err - } - } - - return nil -} - -// Close closes the connection. -func (c *Conn) Close() error { - c.close() - - // wait until the reader/writer goroutines have exited before proceeding. - // this is to prevent a race between calling Close() and a reader/writer - // goroutine calling close() due to a terminal error. - <-c.txDone - <-c.rxDone - - var connErr *ConnError - if errors.As(c.doneErr, &connErr) && connErr.RemoteErr == nil && connErr.inner == nil { - // an empty ConnectionError means the connection was closed by the caller - return nil - } - - // there was an error during shut-down or connReader/connWriter - // experienced a terminal error - return c.doneErr -} - -// close is called once, either from Close() or when connReader/connWriter exits -func (c *Conn) close() { - c.closeOnce.Do(func() { - defer close(c.done) - - close(c.rxtxExit) - - // wait for writing to stop, allows it to send the final close frame - <-c.txDone - - closeErr := c.net.Close() - - // check rxDone after closing net, otherwise may block - // for up to c.idleTimeout - <-c.rxDone - - if errors.Is(c.rxErr, net.ErrClosed) { - // this is the expected error when the connection is closed, swallow it - c.rxErr = nil - } - - if c.txErr == nil && c.rxErr == nil && closeErr == nil { - // if there are no errors, it means user initiated close() and we shut down cleanly - c.doneErr = &ConnError{} - } else if amqpErr, ok := c.rxErr.(*Error); ok { - // we experienced a peer-initiated close that contained an Error. return it - c.doneErr = &ConnError{RemoteErr: amqpErr} - } else if c.txErr != nil { - // c.txErr is already wrapped in a ConnError - c.doneErr = c.txErr - } else if c.rxErr != nil { - c.doneErr = &ConnError{inner: c.rxErr} - } else { - c.doneErr = &ConnError{inner: closeErr} - } - }) -} - -// closeDuringStart is a special close to be used only during startup (i.e. c.start() and any of its children) -func (c *Conn) closeDuringStart() { - c.closeOnce.Do(func() { - c.net.Close() - }) -} - -// NewSession starts a new session on the connection. -// - ctx controls waiting for the peer to acknowledge the session -// - opts contains optional values, pass nil to accept the defaults -// -// If the context's deadline expires or is cancelled before the operation -// completes, an error is returned. If the Session was successfully -// created, it will be cleaned up in future calls to NewSession. -func (c *Conn) NewSession(ctx context.Context, opts *SessionOptions) (*Session, error) { - // clean up any abandoned sessions first - if err := c.freeAbandonedSessions(ctx); err != nil { - return nil, err - } - - session, err := c.newSession(opts) - if err != nil { - return nil, err - } - - if err := session.begin(ctx); err != nil { - c.abandonSession(session) - return nil, err - } - - return session, nil -} - -func (c *Conn) freeAbandonedSessions(ctx context.Context) error { - c.abandonedSessionsMu.Lock() - defer c.abandonedSessionsMu.Unlock() - - debug.Log(3, "TX (Conn %p): cleaning up %d abandoned sessions", c, len(c.abandonedSessions)) - - for _, s := range c.abandonedSessions { - fr := frames.PerformEnd{} - if err := s.txFrameAndWait(ctx, &fr); err != nil { - return err - } - } - - c.abandonedSessions = nil - return nil -} - -func (c *Conn) newSession(opts *SessionOptions) (*Session, error) { - c.sessionsByChannelMu.Lock() - defer c.sessionsByChannelMu.Unlock() - - // create the next session to allocate - // note that channel always start at 0 - channel, ok := c.channels.Next() - if !ok { - if err := c.Close(); err != nil { - return nil, err - } - return nil, &ConnError{inner: fmt.Errorf("reached connection channel max (%d)", c.channelMax)} - } - session := newSession(c, uint16(channel), opts) - c.sessionsByChannel[session.channel] = session - - return session, nil -} - -func (c *Conn) deleteSession(s *Session) { - c.sessionsByChannelMu.Lock() - defer c.sessionsByChannelMu.Unlock() - - delete(c.sessionsByChannel, s.channel) - c.channels.Remove(uint32(s.channel)) -} - -func (c *Conn) abandonSession(s *Session) { - c.abandonedSessionsMu.Lock() - defer c.abandonedSessionsMu.Unlock() - c.abandonedSessions = append(c.abandonedSessions, s) -} - -// connReader reads from the net.Conn, decodes frames, and either handles -// them here as appropriate or sends them to the session.rx channel. -func (c *Conn) connReader() { - defer func() { - close(c.rxDone) - c.close() - }() - - var sessionsByRemoteChannel = make(map[uint16]*Session) - var err error - for { - if err != nil { - debug.Log(1, "RX (connReader %p): terminal error: %v", c, err) - c.rxErr = err - return - } - - var fr frames.Frame - fr, err = c.readFrame() - if err != nil { - continue - } - - debug.Log(1, "RX (connReader %p): %s", c, fr) - - var ( - session *Session - ok bool - ) - - switch body := fr.Body.(type) { - // Server initiated close. - case *frames.PerformClose: - // connWriter will send the close performative ack on its way out. - // it's a SHOULD though, not a MUST. - if body.Error == nil { - return - } - err = body.Error - continue - - // RemoteChannel should be used when frame is Begin - case *frames.PerformBegin: - if body.RemoteChannel == nil { - // since we only support remotely-initiated sessions, this is an error - // TODO: it would be ideal to not have this kill the connection - err = fmt.Errorf("%T: nil RemoteChannel", fr.Body) - continue - } - c.sessionsByChannelMu.RLock() - session, ok = c.sessionsByChannel[*body.RemoteChannel] - c.sessionsByChannelMu.RUnlock() - if !ok { - // this can happen if NewSession() exits due to the context expiring/cancelled - // before the begin ack is received. - err = fmt.Errorf("unexpected remote channel number %d", *body.RemoteChannel) - continue - } - - session.remoteChannel = fr.Channel - sessionsByRemoteChannel[fr.Channel] = session - - case *frames.PerformEnd: - session, ok = sessionsByRemoteChannel[fr.Channel] - if !ok { - err = fmt.Errorf("%T: didn't find channel %d in sessionsByRemoteChannel (PerformEnd)", fr.Body, fr.Channel) - continue - } - // we MUST remove the remote channel from our map as soon as we receive - // the ack (i.e. before passing it on to the session mux) on the session - // ending since the numbers are recycled. - delete(sessionsByRemoteChannel, fr.Channel) - c.deleteSession(session) - - default: - // pass on performative to the correct session - session, ok = sessionsByRemoteChannel[fr.Channel] - if !ok { - err = fmt.Errorf("%T: didn't find channel %d in sessionsByRemoteChannel", fr.Body, fr.Channel) - continue - } - } - - q := session.rxQ.Acquire() - q.Enqueue(fr.Body) - session.rxQ.Release(q) - debug.Log(2, "RX (connReader %p): mux frame to Session (%p): %s", c, session, fr) - } -} - -// readFrame reads a complete frame from c.net. -// it assumes that any read deadline has already been applied. -// used externally by SASL only. -func (c *Conn) readFrame() (frames.Frame, error) { - switch { - // Cheaply reuse free buffer space when fully read. - case c.rxBuf.Len() == 0: - c.rxBuf.Reset() - - // Prevent excessive/unbounded growth by shifting data to beginning of buffer. - case int64(c.rxBuf.Size()) > int64(c.maxFrameSize): - c.rxBuf.Reclaim() - } - - var ( - currentHeader frames.Header // keep track of the current header, for frames split across multiple TCP packets - frameInProgress bool // true if in the middle of receiving data for currentHeader - ) - - for { - // need to read more if buf doesn't contain the complete frame - // or there's not enough in buf to parse the header - if frameInProgress || c.rxBuf.Len() < frames.HeaderSize { - // we MUST reset the idle timeout before each read from net.Conn - if c.idleTimeout > 0 { - _ = c.net.SetReadDeadline(time.Now().Add(c.idleTimeout)) - } - err := c.rxBuf.ReadFromOnce(c.net) - if err != nil { - return frames.Frame{}, err - } - } - - // read more if buf doesn't contain enough to parse the header - if c.rxBuf.Len() < frames.HeaderSize { - continue - } - - // parse the header if a frame isn't in progress - if !frameInProgress { - var err error - currentHeader, err = frames.ParseHeader(&c.rxBuf) - if err != nil { - return frames.Frame{}, err - } - frameInProgress = true - } - - // check size is reasonable - if currentHeader.Size > math.MaxInt32 { // make max size configurable - return frames.Frame{}, errors.New("payload too large") - } - - bodySize := int64(currentHeader.Size - frames.HeaderSize) - - // the full frame hasn't been received, keep reading - if int64(c.rxBuf.Len()) < bodySize { - continue - } - frameInProgress = false - - // check if body is empty (keepalive) - if bodySize == 0 { - debug.Log(3, "RX (connReader %p): received keep-alive frame", c) - continue - } - - // parse the frame - b, ok := c.rxBuf.Next(bodySize) - if !ok { - return frames.Frame{}, fmt.Errorf("buffer EOF; requested bytes: %d, actual size: %d", bodySize, c.rxBuf.Len()) - } - - parsedBody, err := frames.ParseBody(buffer.New(b)) - if err != nil { - return frames.Frame{}, err - } - - return frames.Frame{Channel: currentHeader.Channel, Body: parsedBody}, nil - } -} - -// frameEnvelope is used when sending a frame to connWriter to be written to net.Conn -type frameEnvelope struct { - Ctx context.Context - Frame frames.Frame - - // optional channel that is closed on successful write to net.Conn or contains the write error - // NOTE: use a buffered channel of size 1 when populating - Sent chan error -} - -func (c *Conn) connWriter() { - defer func() { - close(c.txDone) - c.close() - }() - - var ( - // keepalives are sent at a rate of 1/2 idle timeout - keepaliveInterval = c.peerIdleTimeout / 2 - // 0 disables keepalives - keepalivesEnabled = keepaliveInterval > 0 - // set if enable, nil if not; nil channels block forever - keepalive <-chan time.Time - ) - - if keepalivesEnabled { - ticker := time.NewTicker(keepaliveInterval) - defer ticker.Stop() - keepalive = ticker.C - } - - var err error - for { - if err != nil { - debug.Log(1, "TX (connWriter %p): terminal error: %v", c, err) - c.txErr = err - return - } - - select { - // frame write request - case env := <-c.txFrame: - timeout, ctxErr := c.getWriteTimeout(env.Ctx) - if ctxErr != nil { - debug.Log(1, "TX (connWriter %p) deadline exceeded: %s", c, env.Frame) - if env.Sent != nil { - env.Sent <- ctxErr - } - continue - } - - debug.Log(1, "TX (connWriter %p) timeout %s: %s", c, timeout, env.Frame) - err = c.writeFrame(timeout, env.Frame) - if env.Sent != nil { - if err == nil { - close(env.Sent) - } else { - env.Sent <- err - } - } - - // keepalive timer - case <-keepalive: - debug.Log(3, "TX (connWriter %p): sending keep-alive frame", c) - _ = c.net.SetWriteDeadline(time.Now().Add(c.writeTimeout)) - if _, err = c.net.Write(keepaliveFrame); err != nil { - err = &ConnError{inner: err} - } - // It would be slightly more efficient in terms of network - // resources to reset the timer each time a frame is sent. - // However, keepalives are small (8 bytes) and the interval - // is usually on the order of minutes. It does not seem - // worth it to add extra operations in the write path to - // avoid. (To properly reset a timer it needs to be stopped, - // possibly drained, then reset.) - - // connection complete - case <-c.rxtxExit: - // send close performative. note that the spec says we - // SHOULD wait for the ack but we don't HAVE to, in order - // to be resilient to bad actors etc. so we just send - // the close performative and exit. - fr := frames.Frame{ - Type: frames.TypeAMQP, - Body: &frames.PerformClose{}, - } - debug.Log(1, "TX (connWriter %p): %s", c, fr) - c.txErr = c.writeFrame(c.writeTimeout, fr) - return - } - } -} - -// writeFrame writes a frame to the network. -// used externally by SASL only. -// - timeout - the write deadline to set. zero means no deadline -// -// errors are wrapped in a ConnError as they can be returned to outside callers. -func (c *Conn) writeFrame(timeout time.Duration, fr frames.Frame) error { - // writeFrame into txBuf - c.txBuf.Reset() - err := frames.Write(&c.txBuf, fr) - if err != nil { - return &ConnError{inner: err} - } - - // validate the frame isn't exceeding peer's max frame size - requiredFrameSize := c.txBuf.Len() - if uint64(requiredFrameSize) > uint64(c.peerMaxFrameSize) { - return &ConnError{inner: fmt.Errorf("%T frame size %d larger than peer's max frame size %d", fr, requiredFrameSize, c.peerMaxFrameSize)} - } - - if timeout == 0 { - _ = c.net.SetWriteDeadline(time.Time{}) - } else if timeout > 0 { - _ = c.net.SetWriteDeadline(time.Now().Add(timeout)) - } - - // write to network - n, err := c.net.Write(c.txBuf.Bytes()) - if l := c.txBuf.Len(); n > 0 && n < l && err != nil { - debug.Log(1, "TX (writeFrame %p): wrote %d bytes less than len %d: %v", c, n, l, err) - } - if err != nil { - err = &ConnError{inner: err} - } - return err -} - -// writeProtoHeader writes an AMQP protocol header to the -// network -func (c *Conn) writeProtoHeader(pID protoID) error { - _, err := c.net.Write([]byte{'A', 'M', 'Q', 'P', byte(pID), 1, 0, 0}) - return err -} - -// keepaliveFrame is an AMQP frame with no body, used for keepalives -var keepaliveFrame = []byte{0x00, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00} - -// SendFrame is used by sessions and links to send frames across the network. -// - ctx is used to provide the write deadline -// - fr is the frame to write to net.Conn -// - sent is the optional channel that will contain the error if the write fails -func (c *Conn) sendFrame(ctx context.Context, fr frames.Frame, sent chan error) { - select { - case c.txFrame <- frameEnvelope{Ctx: ctx, Frame: fr, Sent: sent}: - debug.Log(2, "TX (Conn %p): mux frame to connWriter: %s", c, fr) - case <-c.done: - if sent != nil { - sent <- c.doneErr - } - } -} - -// stateFunc is a state in a state machine. -// -// The state is advanced by returning the next state. -// The state machine concludes when nil is returned. -type stateFunc func(context.Context) (stateFunc, error) - -// negotiateProto determines which proto to negotiate next. -// used externally by SASL only. -func (c *Conn) negotiateProto(ctx context.Context) (stateFunc, error) { - // in the order each must be negotiated - switch { - case c.tlsNegotiation && !c.tlsComplete: - return c.exchangeProtoHeader(protoTLS) - case c.saslHandlers != nil && !c.saslComplete: - return c.exchangeProtoHeader(protoSASL) - default: - return c.exchangeProtoHeader(protoAMQP) - } -} - -type protoID uint8 - -// protocol IDs received in protoHeaders -const ( - protoAMQP protoID = 0x0 - protoTLS protoID = 0x2 - protoSASL protoID = 0x3 -) - -// exchangeProtoHeader performs the round trip exchange of protocol -// headers, validation, and returns the protoID specific next state. -func (c *Conn) exchangeProtoHeader(pID protoID) (stateFunc, error) { - // write the proto header - if err := c.writeProtoHeader(pID); err != nil { - return nil, err - } - - // read response header - p, err := c.readProtoHeader() - if err != nil { - return nil, err - } - - if pID != p.ProtoID { - return nil, fmt.Errorf("unexpected protocol header %#00x, expected %#00x", p.ProtoID, pID) - } - - // go to the proto specific state - switch pID { - case protoAMQP: - return c.openAMQP, nil - case protoTLS: - return c.startTLS, nil - case protoSASL: - return c.negotiateSASL, nil - default: - return nil, fmt.Errorf("unknown protocol ID %#02x", p.ProtoID) - } -} - -// readProtoHeader reads a protocol header packet from c.rxProto. -func (c *Conn) readProtoHeader() (protoHeader, error) { - const protoHeaderSize = 8 - - // only read from the network once our buffer has been exhausted. - // TODO: this preserves existing behavior as some tests rely on this - // implementation detail (it lets you replay a stream of bytes). we - // might want to consider removing this and fixing the tests as the - // protocol doesn't actually work this way. - if c.rxBuf.Len() == 0 { - for { - err := c.rxBuf.ReadFromOnce(c.net) - if err != nil { - return protoHeader{}, err - } - - // read more if buf doesn't contain enough to parse the header - if c.rxBuf.Len() >= protoHeaderSize { - break - } - } - } - - buf, ok := c.rxBuf.Next(protoHeaderSize) - if !ok { - return protoHeader{}, errors.New("invalid protoHeader") - } - // bounds check hint to compiler; see golang.org/issue/14808 - _ = buf[protoHeaderSize-1] - - if !bytes.Equal(buf[:4], []byte{'A', 'M', 'Q', 'P'}) { - return protoHeader{}, fmt.Errorf("unexpected protocol %q", buf[:4]) - } - - p := protoHeader{ - ProtoID: protoID(buf[4]), - Major: buf[5], - Minor: buf[6], - Revision: buf[7], - } - - if p.Major != 1 || p.Minor != 0 || p.Revision != 0 { - return protoHeader{}, fmt.Errorf("unexpected protocol version %d.%d.%d", p.Major, p.Minor, p.Revision) - } - - return p, nil -} - -// startTLS wraps the conn with TLS and returns to Client.negotiateProto -func (c *Conn) startTLS(ctx context.Context) (stateFunc, error) { - c.initTLSConfig() - - _ = c.net.SetReadDeadline(time.Time{}) // clear timeout - - // wrap existing net.Conn and perform TLS handshake - tlsConn := tls.Client(c.net, c.tlsConfig) - if err := tlsConn.HandshakeContext(ctx); err != nil { - return nil, err - } - - // swap net.Conn - c.net = tlsConn - c.tlsComplete = true - - // go to next protocol - return c.negotiateProto, nil -} - -// openAMQP round trips the AMQP open performative -func (c *Conn) openAMQP(ctx context.Context) (stateFunc, error) { - // send open frame - open := &frames.PerformOpen{ - ContainerID: c.containerID, - Hostname: c.hostname, - MaxFrameSize: c.maxFrameSize, - ChannelMax: c.channelMax, - IdleTimeout: c.idleTimeout / 2, // per spec, advertise half our idle timeout - Properties: c.properties, - } - fr := frames.Frame{ - Type: frames.TypeAMQP, - Body: open, - Channel: 0, - } - debug.Log(1, "TX (openAMQP %p): %s", c, fr) - timeout, err := c.getWriteTimeout(ctx) - if err != nil { - return nil, err - } - if err = c.writeFrame(timeout, fr); err != nil { - return nil, err - } - - // get the response - fr, err = c.readSingleFrame() - if err != nil { - return nil, err - } - debug.Log(1, "RX (openAMQP %p): %s", c, fr) - o, ok := fr.Body.(*frames.PerformOpen) - if !ok { - return nil, fmt.Errorf("openAMQP: unexpected frame type %T", fr.Body) - } - - // update peer settings - if o.MaxFrameSize > 0 { - c.peerMaxFrameSize = o.MaxFrameSize - } - if o.IdleTimeout > 0 { - // TODO: reject very small idle timeouts - c.peerIdleTimeout = o.IdleTimeout - } - if o.ChannelMax < c.channelMax { - c.channelMax = o.ChannelMax - } - - // connection established, exit state machine - return nil, nil -} - -// negotiateSASL returns the SASL handler for the first matched -// mechanism specified by the server -func (c *Conn) negotiateSASL(context.Context) (stateFunc, error) { - // read mechanisms frame - fr, err := c.readSingleFrame() - if err != nil { - return nil, err - } - debug.Log(1, "RX (negotiateSASL %p): %s", c, fr) - sm, ok := fr.Body.(*frames.SASLMechanisms) - if !ok { - return nil, fmt.Errorf("negotiateSASL: unexpected frame type %T", fr.Body) - } - - // return first match in c.saslHandlers based on order received - for _, mech := range sm.Mechanisms { - if state, ok := c.saslHandlers[mech]; ok { - return state, nil - } - } - - // no match - return nil, fmt.Errorf("no supported auth mechanism (%v)", sm.Mechanisms) // TODO: send "auth not supported" frame? -} - -// saslOutcome processes the SASL outcome frame and return Client.negotiateProto -// on success. -// -// SASL handlers return this stateFunc when the mechanism specific negotiation -// has completed. -// used externally by SASL only. -func (c *Conn) saslOutcome(context.Context) (stateFunc, error) { - // read outcome frame - fr, err := c.readSingleFrame() - if err != nil { - return nil, err - } - debug.Log(1, "RX (saslOutcome %p): %s", c, fr) - so, ok := fr.Body.(*frames.SASLOutcome) - if !ok { - return nil, fmt.Errorf("saslOutcome: unexpected frame type %T", fr.Body) - } - - // check if auth succeeded - if so.Code != encoding.CodeSASLOK { - return nil, fmt.Errorf("SASL PLAIN auth failed with code %#00x: %s", so.Code, so.AdditionalData) // implement Stringer for so.Code - } - - // return to c.negotiateProto - c.saslComplete = true - return c.negotiateProto, nil -} - -// readSingleFrame is used during connection establishment to read a single frame. -// -// After setup, conn.connReader handles incoming frames. -func (c *Conn) readSingleFrame() (frames.Frame, error) { - fr, err := c.readFrame() - if err != nil { - return frames.Frame{}, err - } - - return fr, nil -} - -// getWriteTimeout returns the timeout as calculated from the context's deadline -// or the default write timeout if the context has no deadline. -// if the context has timed out or was cancelled, an error is returned. -func (c *Conn) getWriteTimeout(ctx context.Context) (time.Duration, error) { - if deadline, ok := ctx.Deadline(); ok { - until := time.Until(deadline) - if until <= 0 { - return 0, context.DeadlineExceeded - } - return until, nil - } - return c.writeTimeout, nil -} - -type protoHeader struct { - ProtoID protoID - Major uint8 - Minor uint8 - Revision uint8 -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/const.go b/sdk/messaging/azservicebus/internal/go-amqp/const.go deleted file mode 100644 index 70a2aeff6b61..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/const.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding" - -// Sender Settlement Modes -const ( - // Sender will send all deliveries initially unsettled to the receiver. - SenderSettleModeUnsettled SenderSettleMode = encoding.SenderSettleModeUnsettled - - // Sender will send all deliveries settled to the receiver. - SenderSettleModeSettled SenderSettleMode = encoding.SenderSettleModeSettled - - // Sender MAY send a mixture of settled and unsettled deliveries to the receiver. - SenderSettleModeMixed SenderSettleMode = encoding.SenderSettleModeMixed -) - -// SenderSettleMode specifies how the sender will settle messages. -type SenderSettleMode = encoding.SenderSettleMode - -func senderSettleModeValue(m *SenderSettleMode) SenderSettleMode { - if m == nil { - return SenderSettleModeMixed - } - return *m -} - -// Receiver Settlement Modes -const ( - // Receiver is the first to consider the message as settled. - // Once the corresponding disposition frame is sent, the message - // is considered to be settled. - ReceiverSettleModeFirst ReceiverSettleMode = encoding.ReceiverSettleModeFirst - - // Receiver is the second to consider the message as settled. - // Once the corresponding disposition frame is sent, the settlement - // is considered in-flight and the message will not be considered as - // settled until the sender replies acknowledging the settlement. - ReceiverSettleModeSecond ReceiverSettleMode = encoding.ReceiverSettleModeSecond -) - -// ReceiverSettleMode specifies how the receiver will settle messages. -type ReceiverSettleMode = encoding.ReceiverSettleMode - -func receiverSettleModeValue(m *ReceiverSettleMode) ReceiverSettleMode { - if m == nil { - return ReceiverSettleModeFirst - } - return *m -} - -// Durability Policies -const ( - // No terminus state is retained durably. - DurabilityNone Durability = encoding.DurabilityNone - - // Only the existence and configuration of the terminus is - // retained durably. - DurabilityConfiguration Durability = encoding.DurabilityConfiguration - - // In addition to the existence and configuration of the - // terminus, the unsettled state for durable messages is - // retained durably. - DurabilityUnsettledState Durability = encoding.DurabilityUnsettledState -) - -// Durability specifies the durability of a link. -type Durability = encoding.Durability - -// Expiry Policies -const ( - // The expiry timer starts when terminus is detached. - ExpiryPolicyLinkDetach ExpiryPolicy = encoding.ExpiryLinkDetach - - // The expiry timer starts when the most recently - // associated session is ended. - ExpiryPolicySessionEnd ExpiryPolicy = encoding.ExpirySessionEnd - - // The expiry timer starts when most recently associated - // connection is closed. - ExpiryPolicyConnectionClose ExpiryPolicy = encoding.ExpiryConnectionClose - - // The terminus never expires. - ExpiryPolicyNever ExpiryPolicy = encoding.ExpiryNever -) - -// ExpiryPolicy specifies when the expiry timer of a terminus -// starts counting down from the timeout value. -// -// If the link is subsequently re-attached before the terminus is expired, -// then the count down is aborted. If the conditions for the -// terminus-expiry-policy are subsequently re-met, the expiry timer restarts -// from its originally configured timeout value. -type ExpiryPolicy = encoding.ExpiryPolicy diff --git a/sdk/messaging/azservicebus/internal/go-amqp/creditor.go b/sdk/messaging/azservicebus/internal/go-amqp/creditor.go deleted file mode 100644 index 184702bca7d2..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/creditor.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) Microsoft Corporation - -package amqp - -import ( - "context" - "errors" - "sync" -) - -type creditor struct { - mu sync.Mutex - - // future values for the next flow frame. - pendingDrain bool - creditsToAdd uint32 - - // drained is set when a drain is active and we're waiting - // for the corresponding flow from the remote. - drained chan struct{} -} - -var ( - errLinkDraining = errors.New("link is currently draining, no credits can be added") - errAlreadyDraining = errors.New("drain already in process") -) - -// EndDrain ends the current drain, unblocking any active Drain calls. -func (mc *creditor) EndDrain() { - mc.mu.Lock() - defer mc.mu.Unlock() - - if mc.drained != nil { - close(mc.drained) - mc.drained = nil - } -} - -// FlowBits gets gets the proper values for the next flow frame -// and resets the internal state. -// Returns: -// -// (drain: true, credits: 0) if a flow is needed (drain) -// (drain: false, credits > 0) if a flow is needed (issue credit) -// (drain: false, credits == 0) if no flow needed. -func (mc *creditor) FlowBits(currentCredits uint32) (bool, uint32) { - mc.mu.Lock() - defer mc.mu.Unlock() - - drain := mc.pendingDrain - var credits uint32 - - if mc.pendingDrain { - // only send one drain request - mc.pendingDrain = false - } - - // either: - // drain is true (ie, we're going to send a drain frame, and the credits for it should be 0) - // mc.creditsToAdd == 0 (no flow frame needed, no new credits are being issued) - if drain || mc.creditsToAdd == 0 { - credits = 0 - } else { - credits = mc.creditsToAdd + currentCredits - } - - mc.creditsToAdd = 0 - - return drain, credits -} - -// Drain initiates a drain and blocks until EndDrain is called. -// If the context's deadline expires or is cancelled before the operation -// completes, the drain might not have happened. -func (mc *creditor) Drain(ctx context.Context, r *Receiver) error { - mc.mu.Lock() - - if mc.drained != nil { - mc.mu.Unlock() - return errAlreadyDraining - } - - mc.drained = make(chan struct{}) - // use a local copy to avoid racing with EndDrain() - drained := mc.drained - mc.pendingDrain = true - - mc.mu.Unlock() - - // cause mux() to check our flow conditions. - select { - case r.receiverReady <- struct{}{}: - default: - } - - // send drain, wait for responding flow frame - select { - case <-drained: - return nil - case <-r.l.done: - return r.l.doneErr - case <-ctx.Done(): - return ctx.Err() - } -} - -// IssueCredit queues up additional credits to be requested at the next -// call of FlowBits() -func (mc *creditor) IssueCredit(credits uint32) error { - mc.mu.Lock() - defer mc.mu.Unlock() - - if mc.drained != nil { - return errLinkDraining - } - - mc.creditsToAdd += credits - return nil -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/errors.go b/sdk/messaging/azservicebus/internal/go-amqp/errors.go deleted file mode 100644 index 126fc330ab05..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/errors.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding" -) - -// ErrCond is an AMQP defined error condition. -// See http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-amqp-error for info on their meaning. -type ErrCond = encoding.ErrCond - -// Error Conditions -const ( - // AMQP Errors - ErrCondDecodeError ErrCond = "amqp:decode-error" - ErrCondFrameSizeTooSmall ErrCond = "amqp:frame-size-too-small" - ErrCondIllegalState ErrCond = "amqp:illegal-state" - ErrCondInternalError ErrCond = "amqp:internal-error" - ErrCondInvalidField ErrCond = "amqp:invalid-field" - ErrCondNotAllowed ErrCond = "amqp:not-allowed" - ErrCondNotFound ErrCond = "amqp:not-found" - ErrCondNotImplemented ErrCond = "amqp:not-implemented" - ErrCondPreconditionFailed ErrCond = "amqp:precondition-failed" - ErrCondResourceDeleted ErrCond = "amqp:resource-deleted" - ErrCondResourceLimitExceeded ErrCond = "amqp:resource-limit-exceeded" - ErrCondResourceLocked ErrCond = "amqp:resource-locked" - ErrCondUnauthorizedAccess ErrCond = "amqp:unauthorized-access" - - // Connection Errors - ErrCondConnectionForced ErrCond = "amqp:connection:forced" - ErrCondConnectionRedirect ErrCond = "amqp:connection:redirect" - ErrCondFramingError ErrCond = "amqp:connection:framing-error" - - // Session Errors - ErrCondErrantLink ErrCond = "amqp:session:errant-link" - ErrCondHandleInUse ErrCond = "amqp:session:handle-in-use" - ErrCondUnattachedHandle ErrCond = "amqp:session:unattached-handle" - ErrCondWindowViolation ErrCond = "amqp:session:window-violation" - - // Link Errors - ErrCondDetachForced ErrCond = "amqp:link:detach-forced" - ErrCondLinkRedirect ErrCond = "amqp:link:redirect" - ErrCondMessageSizeExceeded ErrCond = "amqp:link:message-size-exceeded" - ErrCondStolen ErrCond = "amqp:link:stolen" - ErrCondTransferLimitExceeded ErrCond = "amqp:link:transfer-limit-exceeded" -) - -// Error is an AMQP error. -type Error = encoding.Error - -// LinkError is returned by methods on Sender/Receiver when the link has closed. -type LinkError struct { - // RemoteErr contains any error information provided by the peer if the peer detached the link. - RemoteErr *Error - - inner error -} - -// Error implements the error interface for LinkError. -func (e *LinkError) Error() string { - if e.RemoteErr == nil && e.inner == nil { - return "amqp: link closed" - } else if e.RemoteErr != nil { - return e.RemoteErr.Error() - } - return e.inner.Error() -} - -// ConnError is returned by methods on Conn and propagated to Session and Senders/Receivers -// when the connection has been closed. -type ConnError struct { - // RemoteErr contains any error information provided by the peer if the peer closed the AMQP connection. - RemoteErr *Error - - inner error -} - -// Error implements the error interface for ConnectionError. -func (e *ConnError) Error() string { - if e.RemoteErr == nil && e.inner == nil { - return "amqp: connection closed" - } else if e.RemoteErr != nil { - return e.RemoteErr.Error() - } - return e.inner.Error() -} - -// SessionError is returned by methods on Session and propagated to Senders/Receivers -// when the session has been closed. -type SessionError struct { - // RemoteErr contains any error information provided by the peer if the peer closed the session. - RemoteErr *Error - - inner error -} - -// Error implements the error interface for SessionError. -func (e *SessionError) Error() string { - if e.RemoteErr == nil && e.inner == nil { - return "amqp: session closed" - } else if e.RemoteErr != nil { - return e.RemoteErr.Error() - } - return e.inner.Error() -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/internal/bitmap/bitmap.go b/sdk/messaging/azservicebus/internal/go-amqp/internal/bitmap/bitmap.go deleted file mode 100644 index d4d682e9199e..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/internal/bitmap/bitmap.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package bitmap - -import ( - "math/bits" -) - -// bitmap is a lazily initialized bitmap -type Bitmap struct { - max uint32 - bits []uint64 -} - -func New(max uint32) *Bitmap { - return &Bitmap{max: max} -} - -// add sets n in the bitmap. -// -// bits will be expanded as needed. -// -// If n is greater than max, the call has no effect. -func (b *Bitmap) Add(n uint32) { - if n > b.max { - return - } - - var ( - idx = n / 64 - offset = n % 64 - ) - - if l := len(b.bits); int(idx) >= l { - b.bits = append(b.bits, make([]uint64, int(idx)-l+1)...) - } - - b.bits[idx] |= 1 << offset -} - -// remove clears n from the bitmap. -// -// If n is not set or greater than max the call has not effect. -func (b *Bitmap) Remove(n uint32) { - var ( - idx = n / 64 - offset = n % 64 - ) - - if int(idx) >= len(b.bits) { - return - } - - b.bits[idx] &= ^uint64(1 << offset) -} - -// next sets and returns the lowest unset bit in the bitmap. -// -// bits will be expanded if necessary. -// -// If there are no unset bits below max, the second return -// value will be false. -func (b *Bitmap) Next() (uint32, bool) { - // find the first unset bit - for i, v := range b.bits { - // skip if all bits are set - if v == ^uint64(0) { - continue - } - - var ( - offset = bits.TrailingZeros64(^v) // invert and count zeroes - next = uint32(i*64 + offset) - ) - - // check if in bounds - if next > b.max { - return next, false - } - - // set bit - b.bits[i] |= 1 << uint32(offset) - return next, true - } - - // no unset bits in the current slice, - // check if the full range has been allocated - if uint64(len(b.bits)*64) > uint64(b.max) { - return 0, false - } - - // full range not allocated, append entry with first - // bit set - b.bits = append(b.bits, 1) - - // return the value of the first bit - return uint32(len(b.bits)-1) * 64, true -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/internal/buffer/buffer.go b/sdk/messaging/azservicebus/internal/go-amqp/internal/buffer/buffer.go deleted file mode 100644 index b82e5fab76a6..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/internal/buffer/buffer.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package buffer - -import ( - "encoding/binary" - "io" -) - -// buffer is similar to bytes.Buffer but specialized for this package -type Buffer struct { - b []byte - i int -} - -func New(b []byte) *Buffer { - return &Buffer{b: b} -} - -func (b *Buffer) Next(n int64) ([]byte, bool) { - if b.readCheck(n) { - buf := b.b[b.i:len(b.b)] - b.i = len(b.b) - return buf, false - } - - buf := b.b[b.i : b.i+int(n)] - b.i += int(n) - return buf, true -} - -func (b *Buffer) Skip(n int) { - b.i += n -} - -func (b *Buffer) Reset() { - b.b = b.b[:0] - b.i = 0 -} - -// reclaim shifts used buffer space to the beginning of the -// underlying slice. -func (b *Buffer) Reclaim() { - l := b.Len() - copy(b.b[:l], b.b[b.i:]) - b.b = b.b[:l] - b.i = 0 -} - -func (b *Buffer) readCheck(n int64) bool { - return int64(b.i)+n > int64(len(b.b)) -} - -func (b *Buffer) ReadByte() (byte, error) { - if b.readCheck(1) { - return 0, io.EOF - } - - byte_ := b.b[b.i] - b.i++ - return byte_, nil -} - -func (b *Buffer) PeekByte() (byte, error) { - if b.readCheck(1) { - return 0, io.EOF - } - - return b.b[b.i], nil -} - -func (b *Buffer) ReadUint16() (uint16, error) { - if b.readCheck(2) { - return 0, io.EOF - } - - n := binary.BigEndian.Uint16(b.b[b.i:]) - b.i += 2 - return n, nil -} - -func (b *Buffer) ReadUint32() (uint32, error) { - if b.readCheck(4) { - return 0, io.EOF - } - - n := binary.BigEndian.Uint32(b.b[b.i:]) - b.i += 4 - return n, nil -} - -func (b *Buffer) ReadUint64() (uint64, error) { - if b.readCheck(8) { - return 0, io.EOF - } - - n := binary.BigEndian.Uint64(b.b[b.i : b.i+8]) - b.i += 8 - return n, nil -} - -func (b *Buffer) ReadFromOnce(r io.Reader) error { - const minRead = 512 - - l := len(b.b) - if cap(b.b)-l < minRead { - total := l * 2 - if total == 0 { - total = minRead - } - new := make([]byte, l, total) - copy(new, b.b) - b.b = new - } - - n, err := r.Read(b.b[l:cap(b.b)]) - b.b = b.b[:l+n] - return err -} - -func (b *Buffer) Append(p []byte) { - b.b = append(b.b, p...) -} - -func (b *Buffer) AppendByte(bb byte) { - b.b = append(b.b, bb) -} - -func (b *Buffer) AppendString(s string) { - b.b = append(b.b, s...) -} - -func (b *Buffer) Len() int { - return len(b.b) - b.i -} - -func (b *Buffer) Size() int { - return b.i -} - -func (b *Buffer) Bytes() []byte { - return b.b[b.i:] -} - -func (b *Buffer) Detach() []byte { - temp := b.b - b.b = nil - b.i = 0 - return temp -} - -func (b *Buffer) AppendUint16(n uint16) { - b.b = append(b.b, - byte(n>>8), - byte(n), - ) -} - -func (b *Buffer) AppendUint32(n uint32) { - b.b = append(b.b, - byte(n>>24), - byte(n>>16), - byte(n>>8), - byte(n), - ) -} - -func (b *Buffer) AppendUint64(n uint64) { - b.b = append(b.b, - byte(n>>56), - byte(n>>48), - byte(n>>40), - byte(n>>32), - byte(n>>24), - byte(n>>16), - byte(n>>8), - byte(n), - ) -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/internal/debug/debug.go b/sdk/messaging/azservicebus/internal/go-amqp/internal/debug/debug.go deleted file mode 100644 index 3e6821e1f723..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/internal/debug/debug.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -//go:build !debug -// +build !debug - -package debug - -// dummy functions used when debugging is not enabled - -// Log writes the formatted string to stderr. -// Level indicates the verbosity of the messages to log. -// The greater the value, the more verbose messages will be logged. -func Log(_ int, _ string, _ ...any) {} - -// Assert panics if the specified condition is false. -func Assert(bool) {} - -// Assert panics with the provided message if the specified condition is false. -func Assertf(bool, string, ...any) {} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/internal/debug/debug_debug.go b/sdk/messaging/azservicebus/internal/go-amqp/internal/debug/debug_debug.go deleted file mode 100644 index 96d53768a5c9..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/internal/debug/debug_debug.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -//go:build debug -// +build debug - -package debug - -import ( - "fmt" - "log" - "os" - "strconv" -) - -var ( - debugLevel = 1 - logger = log.New(os.Stderr, "", log.Lmicroseconds) -) - -func init() { - level, err := strconv.Atoi(os.Getenv("DEBUG_LEVEL")) - if err != nil { - return - } - - debugLevel = level -} - -// Log writes the formatted string to stderr. -// Level indicates the verbosity of the messages to log. -// The greater the value, the more verbose messages will be logged. -func Log(level int, format string, v ...any) { - if level <= debugLevel { - logger.Printf(format, v...) - } -} - -// Assert panics if the specified condition is false. -func Assert(condition bool) { - if !condition { - panic("assertion failed!") - } -} - -// Assert panics with the provided message if the specified condition is false. -func Assertf(condition bool, msg string, v ...any) { - if !condition { - panic(fmt.Sprintf(msg, v...)) - } -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding/decode.go b/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding/decode.go deleted file mode 100644 index ad360628547a..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding/decode.go +++ /dev/null @@ -1,1150 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package encoding - -import ( - "encoding/binary" - "errors" - "fmt" - "math" - "reflect" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/buffer" -) - -// unmarshaler is fulfilled by types that can unmarshal -// themselves from AMQP data. -type unmarshaler interface { - Unmarshal(r *buffer.Buffer) error -} - -// unmarshal decodes AMQP encoded data into i. -// -// The decoding method is based on the type of i. -// -// If i implements unmarshaler, i.Unmarshal() will be called. -// -// Pointers to primitive types will be decoded via the appropriate read[Type] function. -// -// If i is a pointer to a pointer (**Type), it will be dereferenced and a new instance -// of (*Type) is allocated via reflection. -// -// Common map types (map[string]string, map[Symbol]any, and -// map[any]any), will be decoded via conversion to the mapStringAny, -// mapSymbolAny, and mapAnyAny types. -func Unmarshal(r *buffer.Buffer, i any) error { - if tryReadNull(r) { - return nil - } - - switch t := i.(type) { - case *int: - val, err := readInt(r) - if err != nil { - return err - } - *t = val - case *int8: - val, err := readSbyte(r) - if err != nil { - return err - } - *t = val - case *int16: - val, err := readShort(r) - if err != nil { - return err - } - *t = val - case *int32: - val, err := readInt32(r) - if err != nil { - return err - } - *t = val - case *int64: - val, err := readLong(r) - if err != nil { - return err - } - *t = val - case *uint64: - val, err := readUlong(r) - if err != nil { - return err - } - *t = val - case *uint32: - val, err := readUint32(r) - if err != nil { - return err - } - *t = val - case **uint32: // fastpath for uint32 pointer fields - val, err := readUint32(r) - if err != nil { - return err - } - *t = &val - case *uint16: - val, err := readUshort(r) - if err != nil { - return err - } - *t = val - case *uint8: - val, err := ReadUbyte(r) - if err != nil { - return err - } - *t = val - case *float32: - val, err := readFloat(r) - if err != nil { - return err - } - *t = val - case *float64: - val, err := readDouble(r) - if err != nil { - return err - } - *t = val - case *string: - val, err := ReadString(r) - if err != nil { - return err - } - *t = val - case *Symbol: - s, err := ReadString(r) - if err != nil { - return err - } - *t = Symbol(s) - case *[]byte: - val, err := readBinary(r) - if err != nil { - return err - } - *t = val - case *bool: - b, err := readBool(r) - if err != nil { - return err - } - *t = b - case *time.Time: - ts, err := readTimestamp(r) - if err != nil { - return err - } - *t = ts - case *[]int8: - return (*arrayInt8)(t).Unmarshal(r) - case *[]uint16: - return (*arrayUint16)(t).Unmarshal(r) - case *[]int16: - return (*arrayInt16)(t).Unmarshal(r) - case *[]uint32: - return (*arrayUint32)(t).Unmarshal(r) - case *[]int32: - return (*arrayInt32)(t).Unmarshal(r) - case *[]uint64: - return (*arrayUint64)(t).Unmarshal(r) - case *[]int64: - return (*arrayInt64)(t).Unmarshal(r) - case *[]float32: - return (*arrayFloat)(t).Unmarshal(r) - case *[]float64: - return (*arrayDouble)(t).Unmarshal(r) - case *[]bool: - return (*arrayBool)(t).Unmarshal(r) - case *[]string: - return (*arrayString)(t).Unmarshal(r) - case *[]Symbol: - return (*arraySymbol)(t).Unmarshal(r) - case *[][]byte: - return (*arrayBinary)(t).Unmarshal(r) - case *[]time.Time: - return (*arrayTimestamp)(t).Unmarshal(r) - case *[]UUID: - return (*arrayUUID)(t).Unmarshal(r) - case *[]any: - return (*list)(t).Unmarshal(r) - case *map[any]any: - return (*mapAnyAny)(t).Unmarshal(r) - case *map[string]any: - return (*mapStringAny)(t).Unmarshal(r) - case *map[Symbol]any: - return (*mapSymbolAny)(t).Unmarshal(r) - case *DeliveryState: - type_, _, err := PeekMessageType(r.Bytes()) - if err != nil { - return err - } - - switch AMQPType(type_) { - case TypeCodeStateAccepted: - *t = new(StateAccepted) - case TypeCodeStateModified: - *t = new(StateModified) - case TypeCodeStateReceived: - *t = new(StateReceived) - case TypeCodeStateRejected: - *t = new(StateRejected) - case TypeCodeStateReleased: - *t = new(StateReleased) - default: - return fmt.Errorf("unexpected type %d for deliveryState", type_) - } - return Unmarshal(r, *t) - - case *any: - v, err := ReadAny(r) - if err != nil { - return err - } - *t = v - - case unmarshaler: - return t.Unmarshal(r) - default: - // handle **T - v := reflect.Indirect(reflect.ValueOf(i)) - - // can't unmarshal into a non-pointer - if v.Kind() != reflect.Ptr { - return fmt.Errorf("unable to unmarshal %T", i) - } - - // if nil pointer, allocate a new value to - // unmarshal into - if v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - - return Unmarshal(r, v.Interface()) - } - return nil -} - -// unmarshalComposite is a helper for use in a composite's unmarshal() function. -// -// The composite from r will be unmarshaled into zero or more fields. An error -// will be returned if typ does not match the decoded type. -func UnmarshalComposite(r *buffer.Buffer, type_ AMQPType, fields ...UnmarshalField) error { - cType, numFields, err := readCompositeHeader(r) - if err != nil { - return err - } - - // check type matches expectation - if cType != type_ { - return fmt.Errorf("invalid header %#0x for %#0x", cType, type_) - } - - // Validate the field count is less than or equal to the number of fields - // provided. Fields may be omitted by the sender if they are not set. - if numFields > int64(len(fields)) { - return fmt.Errorf("invalid field count %d for %#0x", numFields, type_) - } - - for i, field := range fields[:numFields] { - // If the field is null and handleNull is set, call it. - if tryReadNull(r) { - if field.HandleNull != nil { - err = field.HandleNull() - if err != nil { - return err - } - } - continue - } - - // Unmarshal each of the received fields. - err = Unmarshal(r, field.Field) - if err != nil { - return fmt.Errorf("unmarshaling field %d: %v", i, err) - } - } - - // check and call handleNull for the remaining fields - for _, field := range fields[numFields:] { - if field.HandleNull != nil { - err = field.HandleNull() - if err != nil { - return err - } - } - } - - return nil -} - -// unmarshalField is a struct that contains a field to be unmarshaled into. -// -// An optional nullHandler can be set. If the composite field being unmarshaled -// is null and handleNull is not nil, nullHandler will be called. -type UnmarshalField struct { - Field any - HandleNull NullHandler -} - -// nullHandler is a function to be called when a composite's field -// is null. -type NullHandler func() error - -func readType(r *buffer.Buffer) (AMQPType, error) { - n, err := r.ReadByte() - return AMQPType(n), err -} - -func peekType(r *buffer.Buffer) (AMQPType, error) { - n, err := r.PeekByte() - return AMQPType(n), err -} - -// readCompositeHeader reads and consumes the composite header from r. -func readCompositeHeader(r *buffer.Buffer) (_ AMQPType, fields int64, _ error) { - type_, err := readType(r) - if err != nil { - return 0, 0, err - } - - // compsites always start with 0x0 - if type_ != 0 { - return 0, 0, fmt.Errorf("invalid composite header %#02x", type_) - } - - // next, the composite type is encoded as an AMQP uint8 - v, err := readUlong(r) - if err != nil { - return 0, 0, err - } - - // fields are represented as a list - fields, err = readListHeader(r) - - return AMQPType(v), fields, err -} - -func readListHeader(r *buffer.Buffer) (length int64, _ error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - listLength := r.Len() - - switch type_ { - case TypeCodeList0: - return 0, nil - case TypeCodeList8: - buf, ok := r.Next(2) - if !ok { - return 0, errors.New("invalid length") - } - _ = buf[1] - - size := int(buf[0]) - if size > listLength-1 { - return 0, errors.New("invalid length") - } - length = int64(buf[1]) - case TypeCodeList32: - buf, ok := r.Next(8) - if !ok { - return 0, errors.New("invalid length") - } - _ = buf[7] - - size := int(binary.BigEndian.Uint32(buf[:4])) - if size > listLength-4 { - return 0, errors.New("invalid length") - } - length = int64(binary.BigEndian.Uint32(buf[4:8])) - default: - return 0, fmt.Errorf("type code %#02x is not a recognized list type", type_) - } - - return length, nil -} - -func readArrayHeader(r *buffer.Buffer) (length int64, _ error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - arrayLength := r.Len() - - switch type_ { - case TypeCodeArray8: - buf, ok := r.Next(2) - if !ok { - return 0, errors.New("invalid length") - } - _ = buf[1] - - size := int(buf[0]) - if size > arrayLength-1 { - return 0, errors.New("invalid length") - } - length = int64(buf[1]) - case TypeCodeArray32: - buf, ok := r.Next(8) - if !ok { - return 0, errors.New("invalid length") - } - _ = buf[7] - - size := binary.BigEndian.Uint32(buf[:4]) - if int(size) > arrayLength-4 { - return 0, fmt.Errorf("invalid length for type %02x", type_) - } - length = int64(binary.BigEndian.Uint32(buf[4:8])) - default: - return 0, fmt.Errorf("type code %#02x is not a recognized array type", type_) - } - return length, nil -} - -func ReadString(r *buffer.Buffer) (string, error) { - type_, err := readType(r) - if err != nil { - return "", err - } - - var length int64 - switch type_ { - case TypeCodeStr8, TypeCodeSym8: - n, err := r.ReadByte() - if err != nil { - return "", err - } - length = int64(n) - case TypeCodeStr32, TypeCodeSym32: - buf, ok := r.Next(4) - if !ok { - return "", fmt.Errorf("invalid length for type %#02x", type_) - } - length = int64(binary.BigEndian.Uint32(buf)) - default: - return "", fmt.Errorf("type code %#02x is not a recognized string type", type_) - } - - buf, ok := r.Next(length) - if !ok { - return "", errors.New("invalid length") - } - return string(buf), nil -} - -func readBinary(r *buffer.Buffer) ([]byte, error) { - type_, err := readType(r) - if err != nil { - return nil, err - } - - var length int64 - switch type_ { - case TypeCodeVbin8: - n, err := r.ReadByte() - if err != nil { - return nil, err - } - length = int64(n) - case TypeCodeVbin32: - buf, ok := r.Next(4) - if !ok { - return nil, fmt.Errorf("invalid length for type %#02x", type_) - } - length = int64(binary.BigEndian.Uint32(buf)) - default: - return nil, fmt.Errorf("type code %#02x is not a recognized binary type", type_) - } - - if length == 0 { - // An empty value and a nil value are distinct, - // ensure that the returned value is not nil in this case. - return make([]byte, 0), nil - } - - buf, ok := r.Next(length) - if !ok { - return nil, errors.New("invalid length") - } - return append([]byte(nil), buf...), nil -} - -func ReadAny(r *buffer.Buffer) (any, error) { - if tryReadNull(r) { - return nil, nil - } - - type_, err := peekType(r) - if err != nil { - return nil, errors.New("invalid length") - } - - switch type_ { - // composite - case 0x0: - return readComposite(r) - - // bool - case TypeCodeBool, TypeCodeBoolTrue, TypeCodeBoolFalse: - return readBool(r) - - // uint - case TypeCodeUbyte: - return ReadUbyte(r) - case TypeCodeUshort: - return readUshort(r) - case TypeCodeUint, - TypeCodeSmallUint, - TypeCodeUint0: - return readUint32(r) - case TypeCodeUlong, - TypeCodeSmallUlong, - TypeCodeUlong0: - return readUlong(r) - - // int - case TypeCodeByte: - return readSbyte(r) - case TypeCodeShort: - return readShort(r) - case TypeCodeInt, - TypeCodeSmallint: - return readInt32(r) - case TypeCodeLong, - TypeCodeSmalllong: - return readLong(r) - - // floating point - case TypeCodeFloat: - return readFloat(r) - case TypeCodeDouble: - return readDouble(r) - - // binary - case TypeCodeVbin8, TypeCodeVbin32: - return readBinary(r) - - // strings - case TypeCodeStr8, TypeCodeStr32: - return ReadString(r) - case TypeCodeSym8, TypeCodeSym32: - // symbols currently decoded as string to avoid - // exposing symbol type in message, this may need - // to change if users need to distinguish strings - // from symbols - return ReadString(r) - - // timestamp - case TypeCodeTimestamp: - return readTimestamp(r) - - // UUID - case TypeCodeUUID: - return readUUID(r) - - // arrays - case TypeCodeArray8, TypeCodeArray32: - return readAnyArray(r) - - // lists - case TypeCodeList0, TypeCodeList8, TypeCodeList32: - return readAnyList(r) - - // maps - case TypeCodeMap8: - return readAnyMap(r) - case TypeCodeMap32: - return readAnyMap(r) - - // TODO: implement - case TypeCodeDecimal32: - return nil, errors.New("decimal32 not implemented") - case TypeCodeDecimal64: - return nil, errors.New("decimal64 not implemented") - case TypeCodeDecimal128: - return nil, errors.New("decimal128 not implemented") - case TypeCodeChar: - return nil, errors.New("char not implemented") - default: - return nil, fmt.Errorf("unknown type %#02x", type_) - } -} - -func readAnyMap(r *buffer.Buffer) (any, error) { - var m map[any]any - err := (*mapAnyAny)(&m).Unmarshal(r) - if err != nil { - return nil, err - } - - if len(m) == 0 { - return m, nil - } - - stringKeys := true -Loop: - for key := range m { - switch key.(type) { - case string: - case Symbol: - default: - stringKeys = false - break Loop - } - } - - if stringKeys { - mm := make(map[string]any, len(m)) - for key, value := range m { - switch key := key.(type) { - case string: - mm[key] = value - case Symbol: - mm[string(key)] = value - } - } - return mm, nil - } - - return m, nil -} - -func readAnyList(r *buffer.Buffer) (any, error) { - var a []any - err := (*list)(&a).Unmarshal(r) - return a, err -} - -func readAnyArray(r *buffer.Buffer) (any, error) { - // get the array type - buf := r.Bytes() - if len(buf) < 1 { - return nil, errors.New("invalid length") - } - - var typeIdx int - switch AMQPType(buf[0]) { - case TypeCodeArray8: - typeIdx = 3 - case TypeCodeArray32: - typeIdx = 9 - default: - return nil, fmt.Errorf("invalid array type %02x", buf[0]) - } - if len(buf) < typeIdx+1 { - return nil, errors.New("invalid length") - } - - switch AMQPType(buf[typeIdx]) { - case TypeCodeByte: - var a []int8 - err := (*arrayInt8)(&a).Unmarshal(r) - return a, err - case TypeCodeUbyte: - var a ArrayUByte - err := a.Unmarshal(r) - return a, err - case TypeCodeUshort: - var a []uint16 - err := (*arrayUint16)(&a).Unmarshal(r) - return a, err - case TypeCodeShort: - var a []int16 - err := (*arrayInt16)(&a).Unmarshal(r) - return a, err - case TypeCodeUint0, TypeCodeSmallUint, TypeCodeUint: - var a []uint32 - err := (*arrayUint32)(&a).Unmarshal(r) - return a, err - case TypeCodeSmallint, TypeCodeInt: - var a []int32 - err := (*arrayInt32)(&a).Unmarshal(r) - return a, err - case TypeCodeUlong0, TypeCodeSmallUlong, TypeCodeUlong: - var a []uint64 - err := (*arrayUint64)(&a).Unmarshal(r) - return a, err - case TypeCodeSmalllong, TypeCodeLong: - var a []int64 - err := (*arrayInt64)(&a).Unmarshal(r) - return a, err - case TypeCodeFloat: - var a []float32 - err := (*arrayFloat)(&a).Unmarshal(r) - return a, err - case TypeCodeDouble: - var a []float64 - err := (*arrayDouble)(&a).Unmarshal(r) - return a, err - case TypeCodeBool, TypeCodeBoolTrue, TypeCodeBoolFalse: - var a []bool - err := (*arrayBool)(&a).Unmarshal(r) - return a, err - case TypeCodeStr8, TypeCodeStr32: - var a []string - err := (*arrayString)(&a).Unmarshal(r) - return a, err - case TypeCodeSym8, TypeCodeSym32: - var a []Symbol - err := (*arraySymbol)(&a).Unmarshal(r) - return a, err - case TypeCodeVbin8, TypeCodeVbin32: - var a [][]byte - err := (*arrayBinary)(&a).Unmarshal(r) - return a, err - case TypeCodeTimestamp: - var a []time.Time - err := (*arrayTimestamp)(&a).Unmarshal(r) - return a, err - case TypeCodeUUID: - var a []UUID - err := (*arrayUUID)(&a).Unmarshal(r) - return a, err - default: - return nil, fmt.Errorf("array decoding not implemented for %#02x", buf[typeIdx]) - } -} - -func readComposite(r *buffer.Buffer) (any, error) { - buf := r.Bytes() - - if len(buf) < 2 { - return nil, errors.New("invalid length for composite") - } - - // compsites start with 0x0 - if AMQPType(buf[0]) != 0x0 { - return nil, fmt.Errorf("invalid composite header %#02x", buf[0]) - } - - var compositeType uint64 - switch AMQPType(buf[1]) { - case TypeCodeSmallUlong: - if len(buf) < 3 { - return nil, errors.New("invalid length for smallulong") - } - compositeType = uint64(buf[2]) - case TypeCodeUlong: - if len(buf) < 10 { - return nil, errors.New("invalid length for ulong") - } - compositeType = binary.BigEndian.Uint64(buf[2:]) - } - - if compositeType > math.MaxUint8 { - // try as described type - var dt DescribedType - err := dt.Unmarshal(r) - return dt, err - } - - switch AMQPType(compositeType) { - // Error - case TypeCodeError: - t := new(Error) - err := t.Unmarshal(r) - return t, err - - // Lifetime Policies - case TypeCodeDeleteOnClose: - t := DeleteOnClose - err := t.Unmarshal(r) - return t, err - case TypeCodeDeleteOnNoMessages: - t := DeleteOnNoMessages - err := t.Unmarshal(r) - return t, err - case TypeCodeDeleteOnNoLinks: - t := DeleteOnNoLinks - err := t.Unmarshal(r) - return t, err - case TypeCodeDeleteOnNoLinksOrMessages: - t := DeleteOnNoLinksOrMessages - err := t.Unmarshal(r) - return t, err - - // Delivery States - case TypeCodeStateAccepted: - t := new(StateAccepted) - err := t.Unmarshal(r) - return t, err - case TypeCodeStateModified: - t := new(StateModified) - err := t.Unmarshal(r) - return t, err - case TypeCodeStateReceived: - t := new(StateReceived) - err := t.Unmarshal(r) - return t, err - case TypeCodeStateRejected: - t := new(StateRejected) - err := t.Unmarshal(r) - return t, err - case TypeCodeStateReleased: - t := new(StateReleased) - err := t.Unmarshal(r) - return t, err - - case TypeCodeOpen, - TypeCodeBegin, - TypeCodeAttach, - TypeCodeFlow, - TypeCodeTransfer, - TypeCodeDisposition, - TypeCodeDetach, - TypeCodeEnd, - TypeCodeClose, - TypeCodeSource, - TypeCodeTarget, - TypeCodeMessageHeader, - TypeCodeDeliveryAnnotations, - TypeCodeMessageAnnotations, - TypeCodeMessageProperties, - TypeCodeApplicationProperties, - TypeCodeApplicationData, - TypeCodeAMQPSequence, - TypeCodeAMQPValue, - TypeCodeFooter, - TypeCodeSASLMechanism, - TypeCodeSASLInit, - TypeCodeSASLChallenge, - TypeCodeSASLResponse, - TypeCodeSASLOutcome: - return nil, fmt.Errorf("readComposite unmarshal not implemented for %#02x", compositeType) - - default: - // try as described type - var dt DescribedType - err := dt.Unmarshal(r) - return dt, err - } -} - -func readTimestamp(r *buffer.Buffer) (time.Time, error) { - type_, err := readType(r) - if err != nil { - return time.Time{}, err - } - - if type_ != TypeCodeTimestamp { - return time.Time{}, fmt.Errorf("invalid type for timestamp %02x", type_) - } - - n, err := r.ReadUint64() - ms := int64(n) - return time.Unix(ms/1000, (ms%1000)*1000000).UTC(), err -} - -func readInt(r *buffer.Buffer) (int, error) { - type_, err := peekType(r) - if err != nil { - return 0, err - } - - switch type_ { - // Unsigned - case TypeCodeUbyte: - n, err := ReadUbyte(r) - return int(n), err - case TypeCodeUshort: - n, err := readUshort(r) - return int(n), err - case TypeCodeUint0, TypeCodeSmallUint, TypeCodeUint: - n, err := readUint32(r) - return int(n), err - case TypeCodeUlong0, TypeCodeSmallUlong, TypeCodeUlong: - n, err := readUlong(r) - return int(n), err - - // Signed - case TypeCodeByte: - n, err := readSbyte(r) - return int(n), err - case TypeCodeShort: - n, err := readShort(r) - return int(n), err - case TypeCodeSmallint, TypeCodeInt: - n, err := readInt32(r) - return int(n), err - case TypeCodeSmalllong, TypeCodeLong: - n, err := readLong(r) - return int(n), err - default: - return 0, fmt.Errorf("type code %#02x is not a recognized number type", type_) - } -} - -func readLong(r *buffer.Buffer) (int64, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - switch type_ { - case TypeCodeSmalllong: - n, err := r.ReadByte() - return int64(int8(n)), err - case TypeCodeLong: - n, err := r.ReadUint64() - return int64(n), err - default: - return 0, fmt.Errorf("invalid type for uint32 %02x", type_) - } -} - -func readInt32(r *buffer.Buffer) (int32, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - switch type_ { - case TypeCodeSmallint: - n, err := r.ReadByte() - return int32(int8(n)), err - case TypeCodeInt: - n, err := r.ReadUint32() - return int32(n), err - default: - return 0, fmt.Errorf("invalid type for int32 %02x", type_) - } -} - -func readShort(r *buffer.Buffer) (int16, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - if type_ != TypeCodeShort { - return 0, fmt.Errorf("invalid type for short %02x", type_) - } - - n, err := r.ReadUint16() - return int16(n), err -} - -func readSbyte(r *buffer.Buffer) (int8, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - if type_ != TypeCodeByte { - return 0, fmt.Errorf("invalid type for int8 %02x", type_) - } - - n, err := r.ReadByte() - return int8(n), err -} - -func ReadUbyte(r *buffer.Buffer) (uint8, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - if type_ != TypeCodeUbyte { - return 0, fmt.Errorf("invalid type for ubyte %02x", type_) - } - - return r.ReadByte() -} - -func readUshort(r *buffer.Buffer) (uint16, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - if type_ != TypeCodeUshort { - return 0, fmt.Errorf("invalid type for ushort %02x", type_) - } - - return r.ReadUint16() -} - -func readUint32(r *buffer.Buffer) (uint32, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - switch type_ { - case TypeCodeUint0: - return 0, nil - case TypeCodeSmallUint: - n, err := r.ReadByte() - return uint32(n), err - case TypeCodeUint: - return r.ReadUint32() - default: - return 0, fmt.Errorf("invalid type for uint32 %02x", type_) - } -} - -func readUlong(r *buffer.Buffer) (uint64, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - switch type_ { - case TypeCodeUlong0: - return 0, nil - case TypeCodeSmallUlong: - n, err := r.ReadByte() - return uint64(n), err - case TypeCodeUlong: - return r.ReadUint64() - default: - return 0, fmt.Errorf("invalid type for uint32 %02x", type_) - } -} - -func readFloat(r *buffer.Buffer) (float32, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - if type_ != TypeCodeFloat { - return 0, fmt.Errorf("invalid type for float32 %02x", type_) - } - - bits, err := r.ReadUint32() - return math.Float32frombits(bits), err -} - -func readDouble(r *buffer.Buffer) (float64, error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - if type_ != TypeCodeDouble { - return 0, fmt.Errorf("invalid type for float64 %02x", type_) - } - - bits, err := r.ReadUint64() - return math.Float64frombits(bits), err -} - -func readBool(r *buffer.Buffer) (bool, error) { - type_, err := readType(r) - if err != nil { - return false, err - } - - switch type_ { - case TypeCodeBool: - b, err := r.ReadByte() - return b != 0, err - case TypeCodeBoolTrue: - return true, nil - case TypeCodeBoolFalse: - return false, nil - default: - return false, fmt.Errorf("type code %#02x is not a recognized bool type", type_) - } -} - -func readUint(r *buffer.Buffer) (value uint64, _ error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - switch type_ { - case TypeCodeUint0, TypeCodeUlong0: - return 0, nil - case TypeCodeUbyte, TypeCodeSmallUint, TypeCodeSmallUlong: - n, err := r.ReadByte() - return uint64(n), err - case TypeCodeUshort: - n, err := r.ReadUint16() - return uint64(n), err - case TypeCodeUint: - n, err := r.ReadUint32() - return uint64(n), err - case TypeCodeUlong: - return r.ReadUint64() - default: - return 0, fmt.Errorf("type code %#02x is not a recognized number type", type_) - } -} - -func readUUID(r *buffer.Buffer) (UUID, error) { - var uuid UUID - - type_, err := readType(r) - if err != nil { - return uuid, err - } - - if type_ != TypeCodeUUID { - return uuid, fmt.Errorf("type code %#00x is not a UUID", type_) - } - - buf, ok := r.Next(16) - if !ok { - return uuid, errors.New("invalid length") - } - copy(uuid[:], buf) - - return uuid, nil -} - -func readMapHeader(r *buffer.Buffer) (count uint32, _ error) { - type_, err := readType(r) - if err != nil { - return 0, err - } - - length := r.Len() - - switch type_ { - case TypeCodeMap8: - buf, ok := r.Next(2) - if !ok { - return 0, errors.New("invalid length") - } - _ = buf[1] - - size := int(buf[0]) - if size > length-1 { - return 0, errors.New("invalid length") - } - count = uint32(buf[1]) - case TypeCodeMap32: - buf, ok := r.Next(8) - if !ok { - return 0, errors.New("invalid length") - } - _ = buf[7] - - size := int(binary.BigEndian.Uint32(buf[:4])) - if size > length-4 { - return 0, errors.New("invalid length") - } - count = binary.BigEndian.Uint32(buf[4:8]) - default: - return 0, fmt.Errorf("invalid map type %#02x", type_) - } - - if int(count) > r.Len() { - return 0, errors.New("invalid length") - } - return count, nil -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding/encode.go b/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding/encode.go deleted file mode 100644 index 767318c02edc..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding/encode.go +++ /dev/null @@ -1,573 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package encoding - -import ( - "encoding/binary" - "errors" - "fmt" - "math" - "time" - "unicode/utf8" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/buffer" -) - -type marshaler interface { - Marshal(*buffer.Buffer) error -} - -func Marshal(wr *buffer.Buffer, i any) error { - switch t := i.(type) { - case nil: - wr.AppendByte(byte(TypeCodeNull)) - case bool: - if t { - wr.AppendByte(byte(TypeCodeBoolTrue)) - } else { - wr.AppendByte(byte(TypeCodeBoolFalse)) - } - case *bool: - if *t { - wr.AppendByte(byte(TypeCodeBoolTrue)) - } else { - wr.AppendByte(byte(TypeCodeBoolFalse)) - } - case uint: - writeUint64(wr, uint64(t)) - case *uint: - writeUint64(wr, uint64(*t)) - case uint64: - writeUint64(wr, t) - case *uint64: - writeUint64(wr, *t) - case uint32: - writeUint32(wr, t) - case *uint32: - writeUint32(wr, *t) - case uint16: - wr.AppendByte(byte(TypeCodeUshort)) - wr.AppendUint16(t) - case *uint16: - wr.AppendByte(byte(TypeCodeUshort)) - wr.AppendUint16(*t) - case uint8: - wr.Append([]byte{ - byte(TypeCodeUbyte), - t, - }) - case *uint8: - wr.Append([]byte{ - byte(TypeCodeUbyte), - *t, - }) - case int: - writeInt64(wr, int64(t)) - case *int: - writeInt64(wr, int64(*t)) - case int8: - wr.Append([]byte{ - byte(TypeCodeByte), - uint8(t), - }) - case *int8: - wr.Append([]byte{ - byte(TypeCodeByte), - uint8(*t), - }) - case int16: - wr.AppendByte(byte(TypeCodeShort)) - wr.AppendUint16(uint16(t)) - case *int16: - wr.AppendByte(byte(TypeCodeShort)) - wr.AppendUint16(uint16(*t)) - case int32: - writeInt32(wr, t) - case *int32: - writeInt32(wr, *t) - case int64: - writeInt64(wr, t) - case *int64: - writeInt64(wr, *t) - case float32: - writeFloat(wr, t) - case *float32: - writeFloat(wr, *t) - case float64: - writeDouble(wr, t) - case *float64: - writeDouble(wr, *t) - case string: - return writeString(wr, t) - case *string: - return writeString(wr, *t) - case []byte: - return WriteBinary(wr, t) - case *[]byte: - return WriteBinary(wr, *t) - case map[any]any: - return writeMap(wr, t) - case *map[any]any: - return writeMap(wr, *t) - case map[string]any: - return writeMap(wr, t) - case *map[string]any: - return writeMap(wr, *t) - case map[Symbol]any: - return writeMap(wr, t) - case *map[Symbol]any: - return writeMap(wr, *t) - case Unsettled: - return writeMap(wr, t) - case *Unsettled: - return writeMap(wr, *t) - case time.Time: - writeTimestamp(wr, t) - case *time.Time: - writeTimestamp(wr, *t) - case []int8: - return arrayInt8(t).Marshal(wr) - case *[]int8: - return arrayInt8(*t).Marshal(wr) - case []uint16: - return arrayUint16(t).Marshal(wr) - case *[]uint16: - return arrayUint16(*t).Marshal(wr) - case []int16: - return arrayInt16(t).Marshal(wr) - case *[]int16: - return arrayInt16(*t).Marshal(wr) - case []uint32: - return arrayUint32(t).Marshal(wr) - case *[]uint32: - return arrayUint32(*t).Marshal(wr) - case []int32: - return arrayInt32(t).Marshal(wr) - case *[]int32: - return arrayInt32(*t).Marshal(wr) - case []uint64: - return arrayUint64(t).Marshal(wr) - case *[]uint64: - return arrayUint64(*t).Marshal(wr) - case []int64: - return arrayInt64(t).Marshal(wr) - case *[]int64: - return arrayInt64(*t).Marshal(wr) - case []float32: - return arrayFloat(t).Marshal(wr) - case *[]float32: - return arrayFloat(*t).Marshal(wr) - case []float64: - return arrayDouble(t).Marshal(wr) - case *[]float64: - return arrayDouble(*t).Marshal(wr) - case []bool: - return arrayBool(t).Marshal(wr) - case *[]bool: - return arrayBool(*t).Marshal(wr) - case []string: - return arrayString(t).Marshal(wr) - case *[]string: - return arrayString(*t).Marshal(wr) - case []Symbol: - return arraySymbol(t).Marshal(wr) - case *[]Symbol: - return arraySymbol(*t).Marshal(wr) - case [][]byte: - return arrayBinary(t).Marshal(wr) - case *[][]byte: - return arrayBinary(*t).Marshal(wr) - case []time.Time: - return arrayTimestamp(t).Marshal(wr) - case *[]time.Time: - return arrayTimestamp(*t).Marshal(wr) - case []UUID: - return arrayUUID(t).Marshal(wr) - case *[]UUID: - return arrayUUID(*t).Marshal(wr) - case []any: - return list(t).Marshal(wr) - case *[]any: - return list(*t).Marshal(wr) - case marshaler: - return t.Marshal(wr) - default: - return fmt.Errorf("marshal not implemented for %T", i) - } - return nil -} - -func writeInt32(wr *buffer.Buffer, n int32) { - if n < 128 && n >= -128 { - wr.Append([]byte{ - byte(TypeCodeSmallint), - byte(n), - }) - return - } - - wr.AppendByte(byte(TypeCodeInt)) - wr.AppendUint32(uint32(n)) -} - -func writeInt64(wr *buffer.Buffer, n int64) { - if n < 128 && n >= -128 { - wr.Append([]byte{ - byte(TypeCodeSmalllong), - byte(n), - }) - return - } - - wr.AppendByte(byte(TypeCodeLong)) - wr.AppendUint64(uint64(n)) -} - -func writeUint32(wr *buffer.Buffer, n uint32) { - if n == 0 { - wr.AppendByte(byte(TypeCodeUint0)) - return - } - - if n < 256 { - wr.Append([]byte{ - byte(TypeCodeSmallUint), - byte(n), - }) - return - } - - wr.AppendByte(byte(TypeCodeUint)) - wr.AppendUint32(n) -} - -func writeUint64(wr *buffer.Buffer, n uint64) { - if n == 0 { - wr.AppendByte(byte(TypeCodeUlong0)) - return - } - - if n < 256 { - wr.Append([]byte{ - byte(TypeCodeSmallUlong), - byte(n), - }) - return - } - - wr.AppendByte(byte(TypeCodeUlong)) - wr.AppendUint64(n) -} - -func writeFloat(wr *buffer.Buffer, f float32) { - wr.AppendByte(byte(TypeCodeFloat)) - wr.AppendUint32(math.Float32bits(f)) -} - -func writeDouble(wr *buffer.Buffer, f float64) { - wr.AppendByte(byte(TypeCodeDouble)) - wr.AppendUint64(math.Float64bits(f)) -} - -func writeTimestamp(wr *buffer.Buffer, t time.Time) { - wr.AppendByte(byte(TypeCodeTimestamp)) - ms := t.UnixNano() / int64(time.Millisecond) - wr.AppendUint64(uint64(ms)) -} - -// marshalField is a field to be marshaled -type MarshalField struct { - Value any // value to be marshaled, use pointers to avoid interface conversion overhead - Omit bool // indicates that this field should be omitted (set to null) -} - -// marshalComposite is a helper for us in a composite's marshal() function. -// -// The returned bytes include the composite header and fields. Fields with -// omit set to true will be encoded as null or omitted altogether if there are -// no non-null fields after them. -func MarshalComposite(wr *buffer.Buffer, code AMQPType, fields []MarshalField) error { - // lastSetIdx is the last index to have a non-omitted field. - // start at -1 as it's possible to have no fields in a composite - lastSetIdx := -1 - - // marshal each field into it's index in rawFields, - // null fields are skipped, leaving the index nil. - for i, f := range fields { - if f.Omit { - continue - } - lastSetIdx = i - } - - // write header only - if lastSetIdx == -1 { - wr.Append([]byte{ - 0x0, - byte(TypeCodeSmallUlong), - byte(code), - byte(TypeCodeList0), - }) - return nil - } - - // write header - WriteDescriptor(wr, code) - - // write fields - wr.AppendByte(byte(TypeCodeList32)) - - // write temp size, replace later - sizeIdx := wr.Len() - wr.Append([]byte{0, 0, 0, 0}) - preFieldLen := wr.Len() - - // field count - wr.AppendUint32(uint32(lastSetIdx + 1)) - - // write null to each index up to lastSetIdx - for _, f := range fields[:lastSetIdx+1] { - if f.Omit { - wr.AppendByte(byte(TypeCodeNull)) - continue - } - err := Marshal(wr, f.Value) - if err != nil { - return err - } - } - - // fix size - size := uint32(wr.Len() - preFieldLen) - buf := wr.Bytes() - binary.BigEndian.PutUint32(buf[sizeIdx:], size) - - return nil -} - -func WriteDescriptor(wr *buffer.Buffer, code AMQPType) { - wr.Append([]byte{ - 0x0, - byte(TypeCodeSmallUlong), - byte(code), - }) -} - -func writeString(wr *buffer.Buffer, str string) error { - if !utf8.ValidString(str) { - return errors.New("not a valid UTF-8 string") - } - l := len(str) - - switch { - // Str8 - case l < 256: - wr.Append([]byte{ - byte(TypeCodeStr8), - byte(l), - }) - wr.AppendString(str) - return nil - - // Str32 - case uint(l) < math.MaxUint32: - wr.AppendByte(byte(TypeCodeStr32)) - wr.AppendUint32(uint32(l)) - wr.AppendString(str) - return nil - - default: - return errors.New("too long") - } -} - -func WriteBinary(wr *buffer.Buffer, bin []byte) error { - l := len(bin) - - switch { - // List8 - case l < 256: - wr.Append([]byte{ - byte(TypeCodeVbin8), - byte(l), - }) - wr.Append(bin) - return nil - - // List32 - case uint(l) < math.MaxUint32: - wr.AppendByte(byte(TypeCodeVbin32)) - wr.AppendUint32(uint32(l)) - wr.Append(bin) - return nil - - default: - return errors.New("too long") - } -} - -func writeMap(wr *buffer.Buffer, m any) error { - startIdx := wr.Len() - wr.Append([]byte{ - byte(TypeCodeMap32), // type - 0, 0, 0, 0, // size placeholder - 0, 0, 0, 0, // length placeholder - }) - - var pairs int - switch m := m.(type) { - case map[any]any: - pairs = len(m) * 2 - for key, val := range m { - err := Marshal(wr, key) - if err != nil { - return err - } - err = Marshal(wr, val) - if err != nil { - return err - } - } - case map[string]any: - pairs = len(m) * 2 - for key, val := range m { - err := writeString(wr, key) - if err != nil { - return err - } - err = Marshal(wr, val) - if err != nil { - return err - } - } - case map[Symbol]any: - pairs = len(m) * 2 - for key, val := range m { - err := key.Marshal(wr) - if err != nil { - return err - } - err = Marshal(wr, val) - if err != nil { - return err - } - } - case Unsettled: - pairs = len(m) * 2 - for key, val := range m { - err := writeString(wr, key) - if err != nil { - return err - } - err = Marshal(wr, val) - if err != nil { - return err - } - } - case Filter: - pairs = len(m) * 2 - for key, val := range m { - err := key.Marshal(wr) - if err != nil { - return err - } - err = val.Marshal(wr) - if err != nil { - return err - } - } - case Annotations: - pairs = len(m) * 2 - for key, val := range m { - switch key := key.(type) { - case string: - err := Symbol(key).Marshal(wr) - if err != nil { - return err - } - case Symbol: - err := key.Marshal(wr) - if err != nil { - return err - } - case int64: - writeInt64(wr, key) - case int: - writeInt64(wr, int64(key)) - default: - return fmt.Errorf("unsupported Annotations key type %T", key) - } - - err := Marshal(wr, val) - if err != nil { - return err - } - } - default: - return fmt.Errorf("unsupported map type %T", m) - } - - if uint(pairs) > math.MaxUint32-4 { - return errors.New("map contains too many elements") - } - - // overwrite placeholder size and length - bytes := wr.Bytes()[startIdx+1 : startIdx+9] - _ = bytes[7] // bounds check hint - - length := wr.Len() - startIdx - 1 - 4 // -1 for type, -4 for length - binary.BigEndian.PutUint32(bytes[:4], uint32(length)) - binary.BigEndian.PutUint32(bytes[4:8], uint32(pairs)) - - return nil -} - -// type length sizes -const ( - array8TLSize = 2 - array32TLSize = 5 -) - -func writeArrayHeader(wr *buffer.Buffer, length, typeSize int, type_ AMQPType) { - size := length * typeSize - - // array type - if size+array8TLSize <= math.MaxUint8 { - wr.Append([]byte{ - byte(TypeCodeArray8), // type - byte(size + array8TLSize), // size - byte(length), // length - byte(type_), // element type - }) - } else { - wr.AppendByte(byte(TypeCodeArray32)) //type - wr.AppendUint32(uint32(size + array32TLSize)) // size - wr.AppendUint32(uint32(length)) // length - wr.AppendByte(byte(type_)) // element type - } -} - -func writeVariableArrayHeader(wr *buffer.Buffer, length, elementsSizeTotal int, type_ AMQPType) { - // 0xA_ == 1, 0xB_ == 4 - // http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-types-v1.0-os.html#doc-idp82960 - elementTypeSize := 1 - if type_&0xf0 == 0xb0 { - elementTypeSize = 4 - } - - size := elementsSizeTotal + (length * elementTypeSize) // size excluding array length - if size+array8TLSize <= math.MaxUint8 { - wr.Append([]byte{ - byte(TypeCodeArray8), // type - byte(size + array8TLSize), // size - byte(length), // length - byte(type_), // element type - }) - } else { - wr.AppendByte(byte(TypeCodeArray32)) // type - wr.AppendUint32(uint32(size + array32TLSize)) // size - wr.AppendUint32(uint32(length)) // length - wr.AppendByte(byte(type_)) // element type - } -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding/types.go b/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding/types.go deleted file mode 100644 index 1941f3f35ee3..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding/types.go +++ /dev/null @@ -1,2155 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package encoding - -import ( - "encoding/binary" - "encoding/hex" - "errors" - "fmt" - "math" - "reflect" - "time" - "unicode/utf8" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/buffer" -) - -type AMQPType uint8 - -// Type codes -const ( - TypeCodeNull AMQPType = 0x40 - - // Bool - TypeCodeBool AMQPType = 0x56 // boolean with the octet 0x00 being false and octet 0x01 being true - TypeCodeBoolTrue AMQPType = 0x41 - TypeCodeBoolFalse AMQPType = 0x42 - - // Unsigned - TypeCodeUbyte AMQPType = 0x50 // 8-bit unsigned integer (1) - TypeCodeUshort AMQPType = 0x60 // 16-bit unsigned integer in network byte order (2) - TypeCodeUint AMQPType = 0x70 // 32-bit unsigned integer in network byte order (4) - TypeCodeSmallUint AMQPType = 0x52 // unsigned integer value in the range 0 to 255 inclusive (1) - TypeCodeUint0 AMQPType = 0x43 // the uint value 0 (0) - TypeCodeUlong AMQPType = 0x80 // 64-bit unsigned integer in network byte order (8) - TypeCodeSmallUlong AMQPType = 0x53 // unsigned long value in the range 0 to 255 inclusive (1) - TypeCodeUlong0 AMQPType = 0x44 // the ulong value 0 (0) - - // Signed - TypeCodeByte AMQPType = 0x51 // 8-bit two's-complement integer (1) - TypeCodeShort AMQPType = 0x61 // 16-bit two's-complement integer in network byte order (2) - TypeCodeInt AMQPType = 0x71 // 32-bit two's-complement integer in network byte order (4) - TypeCodeSmallint AMQPType = 0x54 // 8-bit two's-complement integer (1) - TypeCodeLong AMQPType = 0x81 // 64-bit two's-complement integer in network byte order (8) - TypeCodeSmalllong AMQPType = 0x55 // 8-bit two's-complement integer - - // Decimal - TypeCodeFloat AMQPType = 0x72 // IEEE 754-2008 binary32 (4) - TypeCodeDouble AMQPType = 0x82 // IEEE 754-2008 binary64 (8) - TypeCodeDecimal32 AMQPType = 0x74 // IEEE 754-2008 decimal32 using the Binary Integer Decimal encoding (4) - TypeCodeDecimal64 AMQPType = 0x84 // IEEE 754-2008 decimal64 using the Binary Integer Decimal encoding (8) - TypeCodeDecimal128 AMQPType = 0x94 // IEEE 754-2008 decimal128 using the Binary Integer Decimal encoding (16) - - // Other - TypeCodeChar AMQPType = 0x73 // a UTF-32BE encoded Unicode character (4) - TypeCodeTimestamp AMQPType = 0x83 // 64-bit two's-complement integer representing milliseconds since the unix epoch - TypeCodeUUID AMQPType = 0x98 // UUID as defined in section 4.1.2 of RFC-4122 - - // Variable Length - TypeCodeVbin8 AMQPType = 0xa0 // up to 2^8 - 1 octets of binary data (1 + variable) - TypeCodeVbin32 AMQPType = 0xb0 // up to 2^32 - 1 octets of binary data (4 + variable) - TypeCodeStr8 AMQPType = 0xa1 // up to 2^8 - 1 octets worth of UTF-8 Unicode (with no byte order mark) (1 + variable) - TypeCodeStr32 AMQPType = 0xb1 // up to 2^32 - 1 octets worth of UTF-8 Unicode (with no byte order mark) (4 +variable) - TypeCodeSym8 AMQPType = 0xa3 // up to 2^8 - 1 seven bit ASCII characters representing a symbolic value (1 + variable) - TypeCodeSym32 AMQPType = 0xb3 // up to 2^32 - 1 seven bit ASCII characters representing a symbolic value (4 + variable) - - // Compound - TypeCodeList0 AMQPType = 0x45 // the empty list (i.e. the list with no elements) (0) - TypeCodeList8 AMQPType = 0xc0 // up to 2^8 - 1 list elements with total size less than 2^8 octets (1 + compound) - TypeCodeList32 AMQPType = 0xd0 // up to 2^32 - 1 list elements with total size less than 2^32 octets (4 + compound) - TypeCodeMap8 AMQPType = 0xc1 // up to 2^8 - 1 octets of encoded map data (1 + compound) - TypeCodeMap32 AMQPType = 0xd1 // up to 2^32 - 1 octets of encoded map data (4 + compound) - TypeCodeArray8 AMQPType = 0xe0 // up to 2^8 - 1 array elements with total size less than 2^8 octets (1 + array) - TypeCodeArray32 AMQPType = 0xf0 // up to 2^32 - 1 array elements with total size less than 2^32 octets (4 + array) - - // Composites - TypeCodeOpen AMQPType = 0x10 - TypeCodeBegin AMQPType = 0x11 - TypeCodeAttach AMQPType = 0x12 - TypeCodeFlow AMQPType = 0x13 - TypeCodeTransfer AMQPType = 0x14 - TypeCodeDisposition AMQPType = 0x15 - TypeCodeDetach AMQPType = 0x16 - TypeCodeEnd AMQPType = 0x17 - TypeCodeClose AMQPType = 0x18 - - TypeCodeSource AMQPType = 0x28 - TypeCodeTarget AMQPType = 0x29 - TypeCodeError AMQPType = 0x1d - - TypeCodeMessageHeader AMQPType = 0x70 - TypeCodeDeliveryAnnotations AMQPType = 0x71 - TypeCodeMessageAnnotations AMQPType = 0x72 - TypeCodeMessageProperties AMQPType = 0x73 - TypeCodeApplicationProperties AMQPType = 0x74 - TypeCodeApplicationData AMQPType = 0x75 - TypeCodeAMQPSequence AMQPType = 0x76 - TypeCodeAMQPValue AMQPType = 0x77 - TypeCodeFooter AMQPType = 0x78 - - TypeCodeStateReceived AMQPType = 0x23 - TypeCodeStateAccepted AMQPType = 0x24 - TypeCodeStateRejected AMQPType = 0x25 - TypeCodeStateReleased AMQPType = 0x26 - TypeCodeStateModified AMQPType = 0x27 - - TypeCodeSASLMechanism AMQPType = 0x40 - TypeCodeSASLInit AMQPType = 0x41 - TypeCodeSASLChallenge AMQPType = 0x42 - TypeCodeSASLResponse AMQPType = 0x43 - TypeCodeSASLOutcome AMQPType = 0x44 - - TypeCodeDeleteOnClose AMQPType = 0x2b - TypeCodeDeleteOnNoLinks AMQPType = 0x2c - TypeCodeDeleteOnNoMessages AMQPType = 0x2d - TypeCodeDeleteOnNoLinksOrMessages AMQPType = 0x2e -) - -// Durability Policies -const ( - // No terminus state is retained durably. - DurabilityNone Durability = 0 - - // Only the existence and configuration of the terminus is - // retained durably. - DurabilityConfiguration Durability = 1 - - // In addition to the existence and configuration of the - // terminus, the unsettled state for durable messages is - // retained durably. - DurabilityUnsettledState Durability = 2 -) - -// Durability specifies the durability of a link. -type Durability uint32 - -func (d *Durability) String() string { - if d == nil { - return "" - } - - switch *d { - case DurabilityNone: - return "none" - case DurabilityConfiguration: - return "configuration" - case DurabilityUnsettledState: - return "unsettled-state" - default: - return fmt.Sprintf("unknown durability %d", *d) - } -} - -func (d Durability) Marshal(wr *buffer.Buffer) error { - return Marshal(wr, uint32(d)) -} - -func (d *Durability) Unmarshal(r *buffer.Buffer) error { - return Unmarshal(r, (*uint32)(d)) -} - -// Expiry Policies -const ( - // The expiry timer starts when terminus is detached. - ExpiryLinkDetach ExpiryPolicy = "link-detach" - - // The expiry timer starts when the most recently - // associated session is ended. - ExpirySessionEnd ExpiryPolicy = "session-end" - - // The expiry timer starts when most recently associated - // connection is closed. - ExpiryConnectionClose ExpiryPolicy = "connection-close" - - // The terminus never expires. - ExpiryNever ExpiryPolicy = "never" -) - -// ExpiryPolicy specifies when the expiry timer of a terminus -// starts counting down from the timeout value. -// -// If the link is subsequently re-attached before the terminus is expired, -// then the count down is aborted. If the conditions for the -// terminus-expiry-policy are subsequently re-met, the expiry timer restarts -// from its originally configured timeout value. -type ExpiryPolicy Symbol - -func ValidateExpiryPolicy(e ExpiryPolicy) error { - switch e { - case ExpiryLinkDetach, - ExpirySessionEnd, - ExpiryConnectionClose, - ExpiryNever: - return nil - default: - return fmt.Errorf("unknown expiry-policy %q", e) - } -} - -func (e ExpiryPolicy) Marshal(wr *buffer.Buffer) error { - return Symbol(e).Marshal(wr) -} - -func (e *ExpiryPolicy) Unmarshal(r *buffer.Buffer) error { - err := Unmarshal(r, (*Symbol)(e)) - if err != nil { - return err - } - return ValidateExpiryPolicy(*e) -} - -func (e *ExpiryPolicy) String() string { - if e == nil { - return "" - } - return string(*e) -} - -// Sender Settlement Modes -const ( - // Sender will send all deliveries initially unsettled to the receiver. - SenderSettleModeUnsettled SenderSettleMode = 0 - - // Sender will send all deliveries settled to the receiver. - SenderSettleModeSettled SenderSettleMode = 1 - - // Sender MAY send a mixture of settled and unsettled deliveries to the receiver. - SenderSettleModeMixed SenderSettleMode = 2 -) - -// SenderSettleMode specifies how the sender will settle messages. -type SenderSettleMode uint8 - -func (m SenderSettleMode) Ptr() *SenderSettleMode { - return &m -} - -func (m *SenderSettleMode) String() string { - if m == nil { - return "" - } - - switch *m { - case SenderSettleModeUnsettled: - return "unsettled" - - case SenderSettleModeSettled: - return "settled" - - case SenderSettleModeMixed: - return "mixed" - - default: - return fmt.Sprintf("unknown sender mode %d", uint8(*m)) - } -} - -func (m SenderSettleMode) Marshal(wr *buffer.Buffer) error { - return Marshal(wr, uint8(m)) -} - -func (m *SenderSettleMode) Unmarshal(r *buffer.Buffer) error { - n, err := ReadUbyte(r) - *m = SenderSettleMode(n) - return err -} - -// Receiver Settlement Modes -const ( - // Receiver will spontaneously settle all incoming transfers. - ReceiverSettleModeFirst ReceiverSettleMode = 0 - - // Receiver will only settle after sending the disposition to the - // sender and receiving a disposition indicating settlement of - // the delivery from the sender. - ReceiverSettleModeSecond ReceiverSettleMode = 1 -) - -// ReceiverSettleMode specifies how the receiver will settle messages. -type ReceiverSettleMode uint8 - -func (m ReceiverSettleMode) Ptr() *ReceiverSettleMode { - return &m -} - -func (m *ReceiverSettleMode) String() string { - if m == nil { - return "" - } - - switch *m { - case ReceiverSettleModeFirst: - return "first" - - case ReceiverSettleModeSecond: - return "second" - - default: - return fmt.Sprintf("unknown receiver mode %d", uint8(*m)) - } -} - -func (m ReceiverSettleMode) Marshal(wr *buffer.Buffer) error { - return Marshal(wr, uint8(m)) -} - -func (m *ReceiverSettleMode) Unmarshal(r *buffer.Buffer) error { - n, err := ReadUbyte(r) - *m = ReceiverSettleMode(n) - return err -} - -type Role bool - -const ( - RoleSender Role = false - RoleReceiver Role = true -) - -func (rl Role) String() string { - if rl { - return "Receiver" - } - return "Sender" -} - -func (rl *Role) Unmarshal(r *buffer.Buffer) error { - b, err := readBool(r) - *rl = Role(b) - return err -} - -func (rl Role) Marshal(wr *buffer.Buffer) error { - return Marshal(wr, (bool)(rl)) -} - -type SASLCode uint8 - -// SASL Codes -const ( - CodeSASLOK SASLCode = iota // Connection authentication succeeded. - CodeSASLAuth // Connection authentication failed due to an unspecified problem with the supplied credentials. - CodeSASLSysPerm // Connection authentication failed due to a system error that is unlikely to be corrected without intervention. -) - -func (s SASLCode) Marshal(wr *buffer.Buffer) error { - return Marshal(wr, uint8(s)) -} - -func (s *SASLCode) Unmarshal(r *buffer.Buffer) error { - n, err := ReadUbyte(r) - *s = SASLCode(n) - return err -} - -// DeliveryState encapsulates the various concrete delivery states. -// http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#section-delivery-state -// TODO: http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transactions-v1.0-os.html#type-declared -type DeliveryState interface { - deliveryState() // marker method -} - -type Unsettled map[string]DeliveryState - -func (u Unsettled) Marshal(wr *buffer.Buffer) error { - return writeMap(wr, u) -} - -func (u *Unsettled) Unmarshal(r *buffer.Buffer) error { - count, err := readMapHeader(r) - if err != nil { - return err - } - - m := make(Unsettled, count/2) - for i := uint32(0); i < count; i += 2 { - key, err := ReadString(r) - if err != nil { - return err - } - var value DeliveryState - err = Unmarshal(r, &value) - if err != nil { - return err - } - m[key] = value - } - *u = m - return nil -} - -type Filter map[Symbol]*DescribedType - -func (f Filter) Marshal(wr *buffer.Buffer) error { - return writeMap(wr, f) -} - -func (f *Filter) Unmarshal(r *buffer.Buffer) error { - count, err := readMapHeader(r) - if err != nil { - return err - } - - m := make(Filter, count/2) - for i := uint32(0); i < count; i += 2 { - key, err := ReadString(r) - if err != nil { - return err - } - var value DescribedType - err = Unmarshal(r, &value) - if err != nil { - return err - } - m[Symbol(key)] = &value - } - *f = m - return nil -} - -// peekMessageType reads the message type without -// modifying any data. -func PeekMessageType(buf []byte) (uint8, uint8, error) { - if len(buf) < 3 { - return 0, 0, errors.New("invalid message") - } - - if buf[0] != 0 { - return 0, 0, fmt.Errorf("invalid composite header %02x", buf[0]) - } - - // copied from readUlong to avoid allocations - t := AMQPType(buf[1]) - if t == TypeCodeUlong0 { - return 0, 2, nil - } - - if t == TypeCodeSmallUlong { - if len(buf[2:]) == 0 { - return 0, 0, errors.New("invalid ulong") - } - return buf[2], 3, nil - } - - if t != TypeCodeUlong { - return 0, 0, fmt.Errorf("invalid type for uint32 %02x", t) - } - - if len(buf[2:]) < 8 { - return 0, 0, errors.New("invalid ulong") - } - v := binary.BigEndian.Uint64(buf[2:10]) - - return uint8(v), 10, nil -} - -func tryReadNull(r *buffer.Buffer) bool { - if r.Len() > 0 && AMQPType(r.Bytes()[0]) == TypeCodeNull { - r.Skip(1) - return true - } - return false -} - -// Annotations keys must be of type string, int, or int64. -// -// String keys are encoded as AMQP Symbols. -type Annotations map[any]any - -func (a Annotations) Marshal(wr *buffer.Buffer) error { - return writeMap(wr, a) -} - -func (a *Annotations) Unmarshal(r *buffer.Buffer) error { - count, err := readMapHeader(r) - if err != nil { - return err - } - - m := make(Annotations, count/2) - for i := uint32(0); i < count; i += 2 { - key, err := ReadAny(r) - if err != nil { - return err - } - value, err := ReadAny(r) - if err != nil { - return err - } - m[key] = value - } - *a = m - return nil -} - -// ErrCond is one of the error conditions defined in the AMQP spec. -type ErrCond string - -func (ec ErrCond) Marshal(wr *buffer.Buffer) error { - return (Symbol)(ec).Marshal(wr) -} - -func (ec *ErrCond) Unmarshal(r *buffer.Buffer) error { - s, err := ReadString(r) - *ec = ErrCond(s) - return err -} - -/* - - - - - - -*/ - -// Error is an AMQP error. -type Error struct { - // A symbolic value indicating the error condition. - Condition ErrCond - - // descriptive text about the error condition - // - // This text supplies any supplementary details not indicated by the condition field. - // This text can be logged as an aid to resolving issues. - Description string - - // map carrying information about the error condition - Info map[string]any -} - -func (e *Error) Marshal(wr *buffer.Buffer) error { - return MarshalComposite(wr, TypeCodeError, []MarshalField{ - {Value: &e.Condition, Omit: false}, - {Value: &e.Description, Omit: e.Description == ""}, - {Value: e.Info, Omit: len(e.Info) == 0}, - }) -} - -func (e *Error) Unmarshal(r *buffer.Buffer) error { - return UnmarshalComposite(r, TypeCodeError, []UnmarshalField{ - {Field: &e.Condition, HandleNull: func() error { return errors.New("Error.Condition is required") }}, - {Field: &e.Description}, - {Field: &e.Info}, - }...) -} - -func (e *Error) String() string { - if e == nil { - return "*Error(nil)" - } - return fmt.Sprintf("*Error{Condition: %s, Description: %s, Info: %v}", - e.Condition, - e.Description, - e.Info, - ) -} - -func (e *Error) Error() string { - return e.String() -} - -/* - - - - - -*/ - -type StateReceived struct { - // When sent by the sender this indicates the first section of the message - // (with section-number 0 being the first section) for which data can be resent. - // Data from sections prior to the given section cannot be retransmitted for - // this delivery. - // - // When sent by the receiver this indicates the first section of the message - // for which all data might not yet have been received. - SectionNumber uint32 - - // When sent by the sender this indicates the first byte of the encoded section - // data of the section given by section-number for which data can be resent - // (with section-offset 0 being the first byte). Bytes from the same section - // prior to the given offset section cannot be retransmitted for this delivery. - // - // When sent by the receiver this indicates the first byte of the given section - // which has not yet been received. Note that if a receiver has received all of - // section number X (which contains N bytes of data), but none of section number - // X + 1, then it can indicate this by sending either Received(section-number=X, - // section-offset=N) or Received(section-number=X+1, section-offset=0). The state - // Received(section-number=0, section-offset=0) indicates that no message data - // at all has been transferred. - SectionOffset uint64 -} - -func (sr *StateReceived) deliveryState() {} - -func (sr *StateReceived) Marshal(wr *buffer.Buffer) error { - return MarshalComposite(wr, TypeCodeStateReceived, []MarshalField{ - {Value: &sr.SectionNumber, Omit: false}, - {Value: &sr.SectionOffset, Omit: false}, - }) -} - -func (sr *StateReceived) Unmarshal(r *buffer.Buffer) error { - return UnmarshalComposite(r, TypeCodeStateReceived, []UnmarshalField{ - {Field: &sr.SectionNumber, HandleNull: func() error { return errors.New("StateReceiver.SectionNumber is required") }}, - {Field: &sr.SectionOffset, HandleNull: func() error { return errors.New("StateReceiver.SectionOffset is required") }}, - }...) -} - -/* - - - -*/ - -type StateAccepted struct{} - -func (sr *StateAccepted) deliveryState() {} - -func (sa *StateAccepted) Marshal(wr *buffer.Buffer) error { - return MarshalComposite(wr, TypeCodeStateAccepted, nil) -} - -func (sa *StateAccepted) Unmarshal(r *buffer.Buffer) error { - return UnmarshalComposite(r, TypeCodeStateAccepted) -} - -func (sa *StateAccepted) String() string { - return "Accepted" -} - -/* - - - - -*/ - -type StateRejected struct { - Error *Error -} - -func (sr *StateRejected) deliveryState() {} - -func (sr *StateRejected) Marshal(wr *buffer.Buffer) error { - return MarshalComposite(wr, TypeCodeStateRejected, []MarshalField{ - {Value: sr.Error, Omit: sr.Error == nil}, - }) -} - -func (sr *StateRejected) Unmarshal(r *buffer.Buffer) error { - return UnmarshalComposite(r, TypeCodeStateRejected, - UnmarshalField{Field: &sr.Error}, - ) -} - -func (sr *StateRejected) String() string { - return fmt.Sprintf("Rejected{Error: %v}", sr.Error) -} - -/* - - - -*/ - -type StateReleased struct{} - -func (sr *StateReleased) deliveryState() {} - -func (sr *StateReleased) Marshal(wr *buffer.Buffer) error { - return MarshalComposite(wr, TypeCodeStateReleased, nil) -} - -func (sr *StateReleased) Unmarshal(r *buffer.Buffer) error { - return UnmarshalComposite(r, TypeCodeStateReleased) -} - -func (sr *StateReleased) String() string { - return "Released" -} - -/* - - - - - - -*/ - -type StateModified struct { - // count the transfer as an unsuccessful delivery attempt - // - // If the delivery-failed flag is set, any messages modified - // MUST have their delivery-count incremented. - DeliveryFailed bool - - // prevent redelivery - // - // If the undeliverable-here is set, then any messages released MUST NOT - // be redelivered to the modifying link endpoint. - UndeliverableHere bool - - // message attributes - // Map containing attributes to combine with the existing message-annotations - // held in the message's header section. Where the existing message-annotations - // of the message contain an entry with the same key as an entry in this field, - // the value in this field associated with that key replaces the one in the - // existing headers; where the existing message-annotations has no such value, - // the value in this map is added. - MessageAnnotations Annotations -} - -func (sr *StateModified) deliveryState() {} - -func (sm *StateModified) Marshal(wr *buffer.Buffer) error { - return MarshalComposite(wr, TypeCodeStateModified, []MarshalField{ - {Value: &sm.DeliveryFailed, Omit: !sm.DeliveryFailed}, - {Value: &sm.UndeliverableHere, Omit: !sm.UndeliverableHere}, - {Value: sm.MessageAnnotations, Omit: sm.MessageAnnotations == nil}, - }) -} - -func (sm *StateModified) Unmarshal(r *buffer.Buffer) error { - return UnmarshalComposite(r, TypeCodeStateModified, []UnmarshalField{ - {Field: &sm.DeliveryFailed}, - {Field: &sm.UndeliverableHere}, - {Field: &sm.MessageAnnotations}, - }...) -} - -func (sm *StateModified) String() string { - return fmt.Sprintf("Modified{DeliveryFailed: %t, UndeliverableHere: %t, MessageAnnotations: %v}", sm.DeliveryFailed, sm.UndeliverableHere, sm.MessageAnnotations) -} - -// symbol is an AMQP symbolic string. -type Symbol string - -func (s Symbol) Marshal(wr *buffer.Buffer) error { - l := len(s) - switch { - // Sym8 - case l < 256: - wr.Append([]byte{ - byte(TypeCodeSym8), - byte(l), - }) - wr.AppendString(string(s)) - - // Sym32 - case uint(l) < math.MaxUint32: - wr.AppendByte(uint8(TypeCodeSym32)) - wr.AppendUint32(uint32(l)) - wr.AppendString(string(s)) - default: - return errors.New("too long") - } - return nil -} - -type Milliseconds time.Duration - -func (m Milliseconds) Marshal(wr *buffer.Buffer) error { - writeUint32(wr, uint32(m/Milliseconds(time.Millisecond))) - return nil -} - -func (m *Milliseconds) Unmarshal(r *buffer.Buffer) error { - n, err := readUint(r) - *m = Milliseconds(time.Duration(n) * time.Millisecond) - return err -} - -// mapAnyAny is used to decode AMQP maps who's keys are undefined or -// inconsistently typed. -type mapAnyAny map[any]any - -func (m mapAnyAny) Marshal(wr *buffer.Buffer) error { - return writeMap(wr, map[any]any(m)) -} - -func (m *mapAnyAny) Unmarshal(r *buffer.Buffer) error { - count, err := readMapHeader(r) - if err != nil { - return err - } - - mm := make(mapAnyAny, count/2) - for i := uint32(0); i < count; i += 2 { - key, err := ReadAny(r) - if err != nil { - return err - } - value, err := ReadAny(r) - if err != nil { - return err - } - - // https://golang.org/ref/spec#Map_types: - // The comparison operators == and != must be fully defined - // for operands of the key type; thus the key type must not - // be a function, map, or slice. - switch reflect.ValueOf(key).Kind() { - case reflect.Slice, reflect.Func, reflect.Map: - return errors.New("invalid map key") - } - - mm[key] = value - } - *m = mm - return nil -} - -// mapStringAny is used to decode AMQP maps that have string keys -type mapStringAny map[string]any - -func (m mapStringAny) Marshal(wr *buffer.Buffer) error { - return writeMap(wr, map[string]any(m)) -} - -func (m *mapStringAny) Unmarshal(r *buffer.Buffer) error { - count, err := readMapHeader(r) - if err != nil { - return err - } - - mm := make(mapStringAny, count/2) - for i := uint32(0); i < count; i += 2 { - key, err := ReadString(r) - if err != nil { - return err - } - value, err := ReadAny(r) - if err != nil { - return err - } - mm[key] = value - } - *m = mm - - return nil -} - -// mapStringAny is used to decode AMQP maps that have Symbol keys -type mapSymbolAny map[Symbol]any - -func (m mapSymbolAny) Marshal(wr *buffer.Buffer) error { - return writeMap(wr, map[Symbol]any(m)) -} - -func (m *mapSymbolAny) Unmarshal(r *buffer.Buffer) error { - count, err := readMapHeader(r) - if err != nil { - return err - } - - mm := make(mapSymbolAny, count/2) - for i := uint32(0); i < count; i += 2 { - key, err := ReadString(r) - if err != nil { - return err - } - value, err := ReadAny(r) - if err != nil { - return err - } - mm[Symbol(key)] = value - } - *m = mm - return nil -} - -// UUID is a 128 bit identifier as defined in RFC 4122. -type UUID [16]byte - -// String returns the hex encoded representation described in RFC 4122, Section 3. -func (u UUID) String() string { - var buf [36]byte - hex.Encode(buf[:8], u[:4]) - buf[8] = '-' - hex.Encode(buf[9:13], u[4:6]) - buf[13] = '-' - hex.Encode(buf[14:18], u[6:8]) - buf[18] = '-' - hex.Encode(buf[19:23], u[8:10]) - buf[23] = '-' - hex.Encode(buf[24:], u[10:]) - return string(buf[:]) -} - -func (u UUID) Marshal(wr *buffer.Buffer) error { - wr.AppendByte(byte(TypeCodeUUID)) - wr.Append(u[:]) - return nil -} - -func (u *UUID) Unmarshal(r *buffer.Buffer) error { - un, err := readUUID(r) - *u = un - return err -} - -type LifetimePolicy uint8 - -const ( - DeleteOnClose = LifetimePolicy(TypeCodeDeleteOnClose) - DeleteOnNoLinks = LifetimePolicy(TypeCodeDeleteOnNoLinks) - DeleteOnNoMessages = LifetimePolicy(TypeCodeDeleteOnNoMessages) - DeleteOnNoLinksOrMessages = LifetimePolicy(TypeCodeDeleteOnNoLinksOrMessages) -) - -func (p LifetimePolicy) Marshal(wr *buffer.Buffer) error { - wr.Append([]byte{ - 0x0, - byte(TypeCodeSmallUlong), - byte(p), - byte(TypeCodeList0), - }) - return nil -} - -func (p *LifetimePolicy) Unmarshal(r *buffer.Buffer) error { - typ, fields, err := readCompositeHeader(r) - if err != nil { - return err - } - if fields != 0 { - return fmt.Errorf("invalid size %d for lifetime-policy", fields) - } - *p = LifetimePolicy(typ) - return nil -} - -type DescribedType struct { - Descriptor any - Value any -} - -func (t DescribedType) Marshal(wr *buffer.Buffer) error { - wr.AppendByte(0x0) // descriptor constructor - err := Marshal(wr, t.Descriptor) - if err != nil { - return err - } - return Marshal(wr, t.Value) -} - -func (t *DescribedType) Unmarshal(r *buffer.Buffer) error { - b, err := r.ReadByte() - if err != nil { - return err - } - - if b != 0x0 { - return fmt.Errorf("invalid described type header %02x", b) - } - - err = Unmarshal(r, &t.Descriptor) - if err != nil { - return err - } - return Unmarshal(r, &t.Value) -} - -func (t DescribedType) String() string { - return fmt.Sprintf("DescribedType{descriptor: %v, value: %v}", - t.Descriptor, - t.Value, - ) -} - -// SLICES - -// ArrayUByte allows encoding []uint8/[]byte as an array -// rather than binary data. -type ArrayUByte []uint8 - -func (a ArrayUByte) Marshal(wr *buffer.Buffer) error { - const typeSize = 1 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeUbyte) - wr.Append(a) - - return nil -} - -func (a *ArrayUByte) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeUbyte { - return fmt.Errorf("invalid type for []uint16 %02x", type_) - } - - buf, ok := r.Next(length) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - *a = append([]byte(nil), buf...) - - return nil -} - -type arrayInt8 []int8 - -func (a arrayInt8) Marshal(wr *buffer.Buffer) error { - const typeSize = 1 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeByte) - - for _, value := range a { - wr.AppendByte(uint8(value)) - } - - return nil -} - -func (a *arrayInt8) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeByte { - return fmt.Errorf("invalid type for []uint16 %02x", type_) - } - - buf, ok := r.Next(length) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]int8, length) - } else { - aa = aa[:length] - } - - for i, value := range buf { - aa[i] = int8(value) - } - - *a = aa - return nil -} - -type arrayUint16 []uint16 - -func (a arrayUint16) Marshal(wr *buffer.Buffer) error { - const typeSize = 2 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeUshort) - - for _, element := range a { - wr.AppendUint16(element) - } - - return nil -} - -func (a *arrayUint16) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeUshort { - return fmt.Errorf("invalid type for []uint16 %02x", type_) - } - - const typeSize = 2 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]uint16, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - aa[i] = binary.BigEndian.Uint16(buf[bufIdx:]) - bufIdx += 2 - } - - *a = aa - return nil -} - -type arrayInt16 []int16 - -func (a arrayInt16) Marshal(wr *buffer.Buffer) error { - const typeSize = 2 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeShort) - - for _, element := range a { - wr.AppendUint16(uint16(element)) - } - - return nil -} - -func (a *arrayInt16) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeShort { - return fmt.Errorf("invalid type for []uint16 %02x", type_) - } - - const typeSize = 2 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]int16, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - aa[i] = int16(binary.BigEndian.Uint16(buf[bufIdx : bufIdx+2])) - bufIdx += 2 - } - - *a = aa - return nil -} - -type arrayUint32 []uint32 - -func (a arrayUint32) Marshal(wr *buffer.Buffer) error { - var ( - typeSize = 1 - TypeCode = TypeCodeSmallUint - ) - for _, n := range a { - if n > math.MaxUint8 { - typeSize = 4 - TypeCode = TypeCodeUint - break - } - } - - writeArrayHeader(wr, len(a), typeSize, TypeCode) - - if TypeCode == TypeCodeUint { - for _, element := range a { - wr.AppendUint32(element) - } - } else { - for _, element := range a { - wr.AppendByte(byte(element)) - } - } - - return nil -} - -func (a *arrayUint32) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - aa := (*a)[:0] - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeUint0: - if int64(cap(aa)) < length { - aa = make([]uint32, length) - } else { - aa = aa[:length] - for i := range aa { - aa[i] = 0 - } - } - case TypeCodeSmallUint: - buf, ok := r.Next(length) - if !ok { - return errors.New("invalid length") - } - - if int64(cap(aa)) < length { - aa = make([]uint32, length) - } else { - aa = aa[:length] - } - - for i, n := range buf { - aa[i] = uint32(n) - } - case TypeCodeUint: - const typeSize = 4 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - if int64(cap(aa)) < length { - aa = make([]uint32, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - aa[i] = binary.BigEndian.Uint32(buf[bufIdx : bufIdx+4]) - bufIdx += 4 - } - default: - return fmt.Errorf("invalid type for []uint32 %02x", type_) - } - - *a = aa - return nil -} - -type arrayInt32 []int32 - -func (a arrayInt32) Marshal(wr *buffer.Buffer) error { - var ( - typeSize = 1 - TypeCode = TypeCodeSmallint - ) - for _, n := range a { - if n > math.MaxInt8 { - typeSize = 4 - TypeCode = TypeCodeInt - break - } - } - - writeArrayHeader(wr, len(a), typeSize, TypeCode) - - if TypeCode == TypeCodeInt { - for _, element := range a { - wr.AppendUint32(uint32(element)) - } - } else { - for _, element := range a { - wr.AppendByte(byte(element)) - } - } - - return nil -} - -func (a *arrayInt32) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - aa := (*a)[:0] - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeSmallint: - buf, ok := r.Next(length) - if !ok { - return errors.New("invalid length") - } - - if int64(cap(aa)) < length { - aa = make([]int32, length) - } else { - aa = aa[:length] - } - - for i, n := range buf { - aa[i] = int32(int8(n)) - } - case TypeCodeInt: - const typeSize = 4 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - if int64(cap(aa)) < length { - aa = make([]int32, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - aa[i] = int32(binary.BigEndian.Uint32(buf[bufIdx:])) - bufIdx += 4 - } - default: - return fmt.Errorf("invalid type for []int32 %02x", type_) - } - - *a = aa - return nil -} - -type arrayUint64 []uint64 - -func (a arrayUint64) Marshal(wr *buffer.Buffer) error { - var ( - typeSize = 1 - TypeCode = TypeCodeSmallUlong - ) - for _, n := range a { - if n > math.MaxUint8 { - typeSize = 8 - TypeCode = TypeCodeUlong - break - } - } - - writeArrayHeader(wr, len(a), typeSize, TypeCode) - - if TypeCode == TypeCodeUlong { - for _, element := range a { - wr.AppendUint64(element) - } - } else { - for _, element := range a { - wr.AppendByte(byte(element)) - } - } - - return nil -} - -func (a *arrayUint64) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - aa := (*a)[:0] - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeUlong0: - if int64(cap(aa)) < length { - aa = make([]uint64, length) - } else { - aa = aa[:length] - for i := range aa { - aa[i] = 0 - } - } - case TypeCodeSmallUlong: - buf, ok := r.Next(length) - if !ok { - return errors.New("invalid length") - } - - if int64(cap(aa)) < length { - aa = make([]uint64, length) - } else { - aa = aa[:length] - } - - for i, n := range buf { - aa[i] = uint64(n) - } - case TypeCodeUlong: - const typeSize = 8 - buf, ok := r.Next(length * typeSize) - if !ok { - return errors.New("invalid length") - } - - if int64(cap(aa)) < length { - aa = make([]uint64, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - aa[i] = binary.BigEndian.Uint64(buf[bufIdx : bufIdx+8]) - bufIdx += 8 - } - default: - return fmt.Errorf("invalid type for []uint64 %02x", type_) - } - - *a = aa - return nil -} - -type arrayInt64 []int64 - -func (a arrayInt64) Marshal(wr *buffer.Buffer) error { - var ( - typeSize = 1 - TypeCode = TypeCodeSmalllong - ) - for _, n := range a { - if n > math.MaxInt8 { - typeSize = 8 - TypeCode = TypeCodeLong - break - } - } - - writeArrayHeader(wr, len(a), typeSize, TypeCode) - - if TypeCode == TypeCodeLong { - for _, element := range a { - wr.AppendUint64(uint64(element)) - } - } else { - for _, element := range a { - wr.AppendByte(byte(element)) - } - } - - return nil -} - -func (a *arrayInt64) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - aa := (*a)[:0] - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeSmalllong: - buf, ok := r.Next(length) - if !ok { - return errors.New("invalid length") - } - - if int64(cap(aa)) < length { - aa = make([]int64, length) - } else { - aa = aa[:length] - } - - for i, n := range buf { - aa[i] = int64(int8(n)) - } - case TypeCodeLong: - const typeSize = 8 - buf, ok := r.Next(length * typeSize) - if !ok { - return errors.New("invalid length") - } - - if int64(cap(aa)) < length { - aa = make([]int64, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - aa[i] = int64(binary.BigEndian.Uint64(buf[bufIdx:])) - bufIdx += 8 - } - default: - return fmt.Errorf("invalid type for []uint64 %02x", type_) - } - - *a = aa - return nil -} - -type arrayFloat []float32 - -func (a arrayFloat) Marshal(wr *buffer.Buffer) error { - const typeSize = 4 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeFloat) - - for _, element := range a { - wr.AppendUint32(math.Float32bits(element)) - } - - return nil -} - -func (a *arrayFloat) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeFloat { - return fmt.Errorf("invalid type for []float32 %02x", type_) - } - - const typeSize = 4 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]float32, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - bits := binary.BigEndian.Uint32(buf[bufIdx:]) - aa[i] = math.Float32frombits(bits) - bufIdx += typeSize - } - - *a = aa - return nil -} - -type arrayDouble []float64 - -func (a arrayDouble) Marshal(wr *buffer.Buffer) error { - const typeSize = 8 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeDouble) - - for _, element := range a { - wr.AppendUint64(math.Float64bits(element)) - } - - return nil -} - -func (a *arrayDouble) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeDouble { - return fmt.Errorf("invalid type for []float64 %02x", type_) - } - - const typeSize = 8 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]float64, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - bits := binary.BigEndian.Uint64(buf[bufIdx:]) - aa[i] = math.Float64frombits(bits) - bufIdx += typeSize - } - - *a = aa - return nil -} - -type arrayBool []bool - -func (a arrayBool) Marshal(wr *buffer.Buffer) error { - const typeSize = 1 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeBool) - - for _, element := range a { - value := byte(0) - if element { - value = 1 - } - wr.AppendByte(value) - } - - return nil -} - -func (a *arrayBool) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]bool, length) - } else { - aa = aa[:length] - } - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeBool: - buf, ok := r.Next(length) - if !ok { - return errors.New("invalid length") - } - - for i, value := range buf { - if value == 0 { - aa[i] = false - } else { - aa[i] = true - } - } - - case TypeCodeBoolTrue: - for i := range aa { - aa[i] = true - } - case TypeCodeBoolFalse: - for i := range aa { - aa[i] = false - } - default: - return fmt.Errorf("invalid type for []bool %02x", type_) - } - - *a = aa - return nil -} - -type arrayString []string - -func (a arrayString) Marshal(wr *buffer.Buffer) error { - var ( - elementType = TypeCodeStr8 - elementsSizeTotal int - ) - for _, element := range a { - if !utf8.ValidString(element) { - return errors.New("not a valid UTF-8 string") - } - - elementsSizeTotal += len(element) - - if len(element) > math.MaxUint8 { - elementType = TypeCodeStr32 - } - } - - writeVariableArrayHeader(wr, len(a), elementsSizeTotal, elementType) - - if elementType == TypeCodeStr32 { - for _, element := range a { - wr.AppendUint32(uint32(len(element))) - wr.AppendString(element) - } - } else { - for _, element := range a { - wr.AppendByte(byte(len(element))) - wr.AppendString(element) - } - } - - return nil -} - -func (a *arrayString) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - const typeSize = 2 // assume all strings are at least 2 bytes - if length*typeSize > int64(r.Len()) { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]string, length) - } else { - aa = aa[:length] - } - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeStr8: - for i := range aa { - size, err := r.ReadByte() - if err != nil { - return err - } - - buf, ok := r.Next(int64(size)) - if !ok { - return errors.New("invalid length") - } - - aa[i] = string(buf) - } - case TypeCodeStr32: - for i := range aa { - buf, ok := r.Next(4) - if !ok { - return errors.New("invalid length") - } - size := int64(binary.BigEndian.Uint32(buf)) - - buf, ok = r.Next(size) - if !ok { - return errors.New("invalid length") - } - aa[i] = string(buf) - } - default: - return fmt.Errorf("invalid type for []string %02x", type_) - } - - *a = aa - return nil -} - -type arraySymbol []Symbol - -func (a arraySymbol) Marshal(wr *buffer.Buffer) error { - var ( - elementType = TypeCodeSym8 - elementsSizeTotal int - ) - for _, element := range a { - elementsSizeTotal += len(element) - - if len(element) > math.MaxUint8 { - elementType = TypeCodeSym32 - } - } - - writeVariableArrayHeader(wr, len(a), elementsSizeTotal, elementType) - - if elementType == TypeCodeSym32 { - for _, element := range a { - wr.AppendUint32(uint32(len(element))) - wr.AppendString(string(element)) - } - } else { - for _, element := range a { - wr.AppendByte(byte(len(element))) - wr.AppendString(string(element)) - } - } - - return nil -} - -func (a *arraySymbol) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - const typeSize = 2 // assume all symbols are at least 2 bytes - if length*typeSize > int64(r.Len()) { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]Symbol, length) - } else { - aa = aa[:length] - } - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeSym8: - for i := range aa { - size, err := r.ReadByte() - if err != nil { - return err - } - - buf, ok := r.Next(int64(size)) - if !ok { - return errors.New("invalid length") - } - aa[i] = Symbol(buf) - } - case TypeCodeSym32: - for i := range aa { - buf, ok := r.Next(4) - if !ok { - return errors.New("invalid length") - } - size := int64(binary.BigEndian.Uint32(buf)) - - buf, ok = r.Next(size) - if !ok { - return errors.New("invalid length") - } - aa[i] = Symbol(buf) - } - default: - return fmt.Errorf("invalid type for []Symbol %02x", type_) - } - - *a = aa - return nil -} - -type arrayBinary [][]byte - -func (a arrayBinary) Marshal(wr *buffer.Buffer) error { - var ( - elementType = TypeCodeVbin8 - elementsSizeTotal int - ) - for _, element := range a { - elementsSizeTotal += len(element) - - if len(element) > math.MaxUint8 { - elementType = TypeCodeVbin32 - } - } - - writeVariableArrayHeader(wr, len(a), elementsSizeTotal, elementType) - - if elementType == TypeCodeVbin32 { - for _, element := range a { - wr.AppendUint32(uint32(len(element))) - wr.Append(element) - } - } else { - for _, element := range a { - wr.AppendByte(byte(len(element))) - wr.Append(element) - } - } - - return nil -} - -func (a *arrayBinary) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - const typeSize = 2 // assume all binary is at least 2 bytes - if length*typeSize > int64(r.Len()) { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([][]byte, length) - } else { - aa = aa[:length] - } - - type_, err := readType(r) - if err != nil { - return err - } - switch type_ { - case TypeCodeVbin8: - for i := range aa { - size, err := r.ReadByte() - if err != nil { - return err - } - - buf, ok := r.Next(int64(size)) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - aa[i] = append([]byte(nil), buf...) - } - case TypeCodeVbin32: - for i := range aa { - buf, ok := r.Next(4) - if !ok { - return errors.New("invalid length") - } - size := binary.BigEndian.Uint32(buf) - - buf, ok = r.Next(int64(size)) - if !ok { - return errors.New("invalid length") - } - aa[i] = append([]byte(nil), buf...) - } - default: - return fmt.Errorf("invalid type for [][]byte %02x", type_) - } - - *a = aa - return nil -} - -type arrayTimestamp []time.Time - -func (a arrayTimestamp) Marshal(wr *buffer.Buffer) error { - const typeSize = 8 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeTimestamp) - - for _, element := range a { - ms := element.UnixNano() / int64(time.Millisecond) - wr.AppendUint64(uint64(ms)) - } - - return nil -} - -func (a *arrayTimestamp) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeTimestamp { - return fmt.Errorf("invalid type for []time.Time %02x", type_) - } - - const typeSize = 8 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]time.Time, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - ms := int64(binary.BigEndian.Uint64(buf[bufIdx:])) - bufIdx += typeSize - aa[i] = time.Unix(ms/1000, (ms%1000)*1000000).UTC() - } - - *a = aa - return nil -} - -type arrayUUID []UUID - -func (a arrayUUID) Marshal(wr *buffer.Buffer) error { - const typeSize = 16 - - writeArrayHeader(wr, len(a), typeSize, TypeCodeUUID) - - for _, element := range a { - wr.Append(element[:]) - } - - return nil -} - -func (a *arrayUUID) Unmarshal(r *buffer.Buffer) error { - length, err := readArrayHeader(r) - if err != nil { - return err - } - - type_, err := readType(r) - if err != nil { - return err - } - if type_ != TypeCodeUUID { - return fmt.Errorf("invalid type for []UUID %#02x", type_) - } - - const typeSize = 16 - buf, ok := r.Next(length * typeSize) - if !ok { - return fmt.Errorf("invalid length %d", length) - } - - aa := (*a)[:0] - if int64(cap(aa)) < length { - aa = make([]UUID, length) - } else { - aa = aa[:length] - } - - var bufIdx int - for i := range aa { - copy(aa[i][:], buf[bufIdx:bufIdx+16]) - bufIdx += 16 - } - - *a = aa - return nil -} - -// LIST - -type list []any - -func (l list) Marshal(wr *buffer.Buffer) error { - length := len(l) - - // type - if length == 0 { - wr.AppendByte(byte(TypeCodeList0)) - return nil - } - wr.AppendByte(byte(TypeCodeList32)) - - // size - sizeIdx := wr.Len() - wr.Append([]byte{0, 0, 0, 0}) - - // length - wr.AppendUint32(uint32(length)) - - for _, element := range l { - err := Marshal(wr, element) - if err != nil { - return err - } - } - - // overwrite size - binary.BigEndian.PutUint32(wr.Bytes()[sizeIdx:], uint32(wr.Len()-(sizeIdx+4))) - - return nil -} - -func (l *list) Unmarshal(r *buffer.Buffer) error { - length, err := readListHeader(r) - if err != nil { - return err - } - - // assume that all types are at least 1 byte - if length > int64(r.Len()) { - return fmt.Errorf("invalid length %d", length) - } - - ll := *l - if int64(cap(ll)) < length { - ll = make([]any, length) - } else { - ll = ll[:length] - } - - for i := range ll { - ll[i], err = ReadAny(r) - if err != nil { - return err - } - } - - *l = ll - return nil -} - -// multiSymbol can decode a single symbol or an array. -type MultiSymbol []Symbol - -func (ms MultiSymbol) Marshal(wr *buffer.Buffer) error { - return Marshal(wr, []Symbol(ms)) -} - -func (ms *MultiSymbol) Unmarshal(r *buffer.Buffer) error { - type_, err := peekType(r) - if err != nil { - return err - } - - if type_ == TypeCodeSym8 || type_ == TypeCodeSym32 { - s, err := ReadString(r) - if err != nil { - return err - } - - *ms = []Symbol{Symbol(s)} - return nil - } - - return Unmarshal(r, (*[]Symbol)(ms)) -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/internal/frames/frames.go b/sdk/messaging/azservicebus/internal/go-amqp/internal/frames/frames.go deleted file mode 100644 index 86a2bc04f72c..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/internal/frames/frames.go +++ /dev/null @@ -1,1543 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package frames - -import ( - "errors" - "fmt" - "strconv" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/buffer" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding" -) - -// Type contains the values for a frame's type. -type Type uint8 - -const ( - TypeAMQP Type = 0x0 - TypeSASL Type = 0x1 -) - -// String implements the fmt.Stringer interface for type Type. -func (t Type) String() string { - if t == 0 { - return "AMQP" - } - return "SASL" -} - -/* - - - - - - - - - - - - - - - - -*/ -type Source struct { - // the address of the source - // - // The address of the source MUST NOT be set when sent on a attach frame sent by - // the receiving link endpoint where the dynamic flag is set to true (that is where - // the receiver is requesting the sender to create an addressable node). - // - // The address of the source MUST be set when sent on a attach frame sent by the - // sending link endpoint where the dynamic flag is set to true (that is where the - // sender has created an addressable node at the request of the receiver and is now - // communicating the address of that created node). The generated name of the address - // SHOULD include the link name and the container-id of the remote container to allow - // for ease of identification. - Address string - - // indicates the durability of the terminus - // - // Indicates what state of the terminus will be retained durably: the state of durable - // messages, only existence and configuration of the terminus, or no state at all. - // - // 0: none - // 1: configuration - // 2: unsettled-state - Durable encoding.Durability - - // the expiry policy of the source - // - // link-detach: The expiry timer starts when terminus is detached. - // session-end: The expiry timer starts when the most recently associated session is - // ended. - // connection-close: The expiry timer starts when most recently associated connection - // is closed. - // never: The terminus never expires. - ExpiryPolicy encoding.ExpiryPolicy - - // duration that an expiring source will be retained - // - // The source starts expiring as indicated by the expiry-policy. - Timeout uint32 // seconds - - // request dynamic creation of a remote node - // - // When set to true by the receiving link endpoint, this field constitutes a request - // for the sending peer to dynamically create a node at the source. In this case the - // address field MUST NOT be set. - // - // When set to true by the sending link endpoint this field indicates creation of a - // dynamically created node. In this case the address field will contain the address - // of the created node. The generated address SHOULD include the link name and other - // available information on the initiator of the request (such as the remote - // container-id) in some recognizable form for ease of traceability. - Dynamic bool - - // properties of the dynamically created node - // - // If the dynamic field is not set to true this field MUST be left unset. - // - // When set by the receiving link endpoint, this field contains the desired - // properties of the node the receiver wishes to be created. When set by the - // sending link endpoint this field contains the actual properties of the - // dynamically created node. See subsection 3.5.9 for standard node properties. - // http://www.amqp.org/specification/1.0/node-properties - // - // lifetime-policy: The lifetime of a dynamically generated node. - // Definitionally, the lifetime will never be less than the lifetime - // of the link which caused its creation, however it is possible to - // extend the lifetime of dynamically created node using a lifetime - // policy. The value of this entry MUST be of a type which provides - // the lifetime-policy archetype. The following standard - // lifetime-policies are defined below: delete-on-close, - // delete-on-no-links, delete-on-no-messages or - // delete-on-no-links-or-messages. - // supported-dist-modes: The distribution modes that the node supports. - // The value of this entry MUST be one or more symbols which are valid - // distribution-modes. That is, the value MUST be of the same type as - // would be valid in a field defined with the following attributes: - // type="symbol" multiple="true" requires="distribution-mode" - DynamicNodeProperties map[encoding.Symbol]any // TODO: implement custom type with validation - - // the distribution mode of the link - // - // This field MUST be set by the sending end of the link if the endpoint supports more - // than one distribution-mode. This field MAY be set by the receiving end of the link - // to indicate a preference when a node supports multiple distribution modes. - DistributionMode encoding.Symbol - - // a set of predicates to filter the messages admitted onto the link - // - // The receiving endpoint sets its desired filter, the sending endpoint sets the filter - // actually in place (including any filters defaulted at the node). The receiving - // endpoint MUST check that the filter in place meets its needs and take responsibility - // for detaching if it does not. - Filter encoding.Filter - - // default outcome for unsettled transfers - // - // Indicates the outcome to be used for transfers that have not reached a terminal - // state at the receiver when the transfer is settled, including when the source - // is destroyed. The value MUST be a valid outcome (e.g., released or rejected). - DefaultOutcome any - - // descriptors for the outcomes that can be chosen on this link - // - // The values in this field are the symbolic descriptors of the outcomes that can - // be chosen on this link. This field MAY be empty, indicating that the default-outcome - // will be assumed for all message transfers (if the default-outcome is not set, and no - // outcomes are provided, then the accepted outcome MUST be supported by the source). - // - // When present, the values MUST be a symbolic descriptor of a valid outcome, - // e.g., "amqp:accepted:list". - Outcomes encoding.MultiSymbol - - // the extension capabilities the sender supports/desires - // - // http://www.amqp.org/specification/1.0/source-capabilities - Capabilities encoding.MultiSymbol -} - -func (s *Source) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeSource, []encoding.MarshalField{ - {Value: &s.Address, Omit: s.Address == ""}, - {Value: &s.Durable, Omit: s.Durable == encoding.DurabilityNone}, - {Value: &s.ExpiryPolicy, Omit: s.ExpiryPolicy == "" || s.ExpiryPolicy == encoding.ExpirySessionEnd}, - {Value: &s.Timeout, Omit: s.Timeout == 0}, - {Value: &s.Dynamic, Omit: !s.Dynamic}, - {Value: s.DynamicNodeProperties, Omit: len(s.DynamicNodeProperties) == 0}, - {Value: &s.DistributionMode, Omit: s.DistributionMode == ""}, - {Value: s.Filter, Omit: len(s.Filter) == 0}, - {Value: &s.DefaultOutcome, Omit: s.DefaultOutcome == nil}, - {Value: &s.Outcomes, Omit: len(s.Outcomes) == 0}, - {Value: &s.Capabilities, Omit: len(s.Capabilities) == 0}, - }) -} - -func (s *Source) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeSource, []encoding.UnmarshalField{ - {Field: &s.Address}, - {Field: &s.Durable}, - {Field: &s.ExpiryPolicy, HandleNull: func() error { s.ExpiryPolicy = encoding.ExpirySessionEnd; return nil }}, - {Field: &s.Timeout}, - {Field: &s.Dynamic}, - {Field: &s.DynamicNodeProperties}, - {Field: &s.DistributionMode}, - {Field: &s.Filter}, - {Field: &s.DefaultOutcome}, - {Field: &s.Outcomes}, - {Field: &s.Capabilities}, - }...) -} - -func (s Source) String() string { - return fmt.Sprintf("source{Address: %s, Durable: %d, ExpiryPolicy: %s, Timeout: %d, "+ - "Dynamic: %t, DynamicNodeProperties: %v, DistributionMode: %s, Filter: %v, DefaultOutcome: %v "+ - "Outcomes: %v, Capabilities: %v}", - s.Address, - s.Durable, - s.ExpiryPolicy, - s.Timeout, - s.Dynamic, - s.DynamicNodeProperties, - s.DistributionMode, - s.Filter, - s.DefaultOutcome, - s.Outcomes, - s.Capabilities, - ) -} - -/* - - - - - - - - - - - - -*/ -type Target struct { - // the address of the target - // - // The address of the target MUST NOT be set when sent on a attach frame sent by - // the sending link endpoint where the dynamic flag is set to true (that is where - // the sender is requesting the receiver to create an addressable node). - // - // The address of the source MUST be set when sent on a attach frame sent by the - // receiving link endpoint where the dynamic flag is set to true (that is where - // the receiver has created an addressable node at the request of the sender and - // is now communicating the address of that created node). The generated name of - // the address SHOULD include the link name and the container-id of the remote - // container to allow for ease of identification. - Address string - - // indicates the durability of the terminus - // - // Indicates what state of the terminus will be retained durably: the state of durable - // messages, only existence and configuration of the terminus, or no state at all. - // - // 0: none - // 1: configuration - // 2: unsettled-state - Durable encoding.Durability - - // the expiry policy of the target - // - // link-detach: The expiry timer starts when terminus is detached. - // session-end: The expiry timer starts when the most recently associated session is - // ended. - // connection-close: The expiry timer starts when most recently associated connection - // is closed. - // never: The terminus never expires. - ExpiryPolicy encoding.ExpiryPolicy - - // duration that an expiring target will be retained - // - // The target starts expiring as indicated by the expiry-policy. - Timeout uint32 // seconds - - // request dynamic creation of a remote node - // - // When set to true by the sending link endpoint, this field constitutes a request - // for the receiving peer to dynamically create a node at the target. In this case - // the address field MUST NOT be set. - // - // When set to true by the receiving link endpoint this field indicates creation of - // a dynamically created node. In this case the address field will contain the - // address of the created node. The generated address SHOULD include the link name - // and other available information on the initiator of the request (such as the - // remote container-id) in some recognizable form for ease of traceability. - Dynamic bool - - // properties of the dynamically created node - // - // If the dynamic field is not set to true this field MUST be left unset. - // - // When set by the sending link endpoint, this field contains the desired - // properties of the node the sender wishes to be created. When set by the - // receiving link endpoint this field contains the actual properties of the - // dynamically created node. See subsection 3.5.9 for standard node properties. - // http://www.amqp.org/specification/1.0/node-properties - // - // lifetime-policy: The lifetime of a dynamically generated node. - // Definitionally, the lifetime will never be less than the lifetime - // of the link which caused its creation, however it is possible to - // extend the lifetime of dynamically created node using a lifetime - // policy. The value of this entry MUST be of a type which provides - // the lifetime-policy archetype. The following standard - // lifetime-policies are defined below: delete-on-close, - // delete-on-no-links, delete-on-no-messages or - // delete-on-no-links-or-messages. - // supported-dist-modes: The distribution modes that the node supports. - // The value of this entry MUST be one or more symbols which are valid - // distribution-modes. That is, the value MUST be of the same type as - // would be valid in a field defined with the following attributes: - // type="symbol" multiple="true" requires="distribution-mode" - DynamicNodeProperties map[encoding.Symbol]any // TODO: implement custom type with validation - - // the extension capabilities the sender supports/desires - // - // http://www.amqp.org/specification/1.0/target-capabilities - Capabilities encoding.MultiSymbol -} - -func (t *Target) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeTarget, []encoding.MarshalField{ - {Value: &t.Address, Omit: t.Address == ""}, - {Value: &t.Durable, Omit: t.Durable == encoding.DurabilityNone}, - {Value: &t.ExpiryPolicy, Omit: t.ExpiryPolicy == "" || t.ExpiryPolicy == encoding.ExpirySessionEnd}, - {Value: &t.Timeout, Omit: t.Timeout == 0}, - {Value: &t.Dynamic, Omit: !t.Dynamic}, - {Value: t.DynamicNodeProperties, Omit: len(t.DynamicNodeProperties) == 0}, - {Value: &t.Capabilities, Omit: len(t.Capabilities) == 0}, - }) -} - -func (t *Target) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeTarget, []encoding.UnmarshalField{ - {Field: &t.Address}, - {Field: &t.Durable}, - {Field: &t.ExpiryPolicy, HandleNull: func() error { t.ExpiryPolicy = encoding.ExpirySessionEnd; return nil }}, - {Field: &t.Timeout}, - {Field: &t.Dynamic}, - {Field: &t.DynamicNodeProperties}, - {Field: &t.Capabilities}, - }...) -} - -func (t Target) String() string { - return fmt.Sprintf("source{Address: %s, Durable: %d, ExpiryPolicy: %s, Timeout: %d, "+ - "Dynamic: %t, DynamicNodeProperties: %v, Capabilities: %v}", - t.Address, - t.Durable, - t.ExpiryPolicy, - t.Timeout, - t.Dynamic, - t.DynamicNodeProperties, - t.Capabilities, - ) -} - -// frame is the decoded representation of a frame -type Frame struct { - Type Type // AMQP/SASL - Channel uint16 // channel this frame is for - Body FrameBody // body of the frame -} - -// String implements the fmt.Stringer interface for type Frame. -func (f Frame) String() string { - return fmt.Sprintf("Frame{Type: %s, Channel: %d, Body: %s}", f.Type, f.Channel, f.Body) -} - -// frameBody adds some type safety to frame encoding -type FrameBody interface { - frameBody() -} - -/* - - - - - - - - - - - - - -*/ - -type PerformOpen struct { - ContainerID string // required - Hostname string - MaxFrameSize uint32 // default: 4294967295 - ChannelMax uint16 // default: 65535 - IdleTimeout time.Duration // from milliseconds - OutgoingLocales encoding.MultiSymbol - IncomingLocales encoding.MultiSymbol - OfferedCapabilities encoding.MultiSymbol - DesiredCapabilities encoding.MultiSymbol - Properties map[encoding.Symbol]any -} - -func (o *PerformOpen) frameBody() {} - -func (o *PerformOpen) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeOpen, []encoding.MarshalField{ - {Value: &o.ContainerID, Omit: false}, - {Value: &o.Hostname, Omit: o.Hostname == ""}, - {Value: &o.MaxFrameSize, Omit: o.MaxFrameSize == 4294967295}, - {Value: &o.ChannelMax, Omit: o.ChannelMax == 65535}, - {Value: (*encoding.Milliseconds)(&o.IdleTimeout), Omit: o.IdleTimeout == 0}, - {Value: &o.OutgoingLocales, Omit: len(o.OutgoingLocales) == 0}, - {Value: &o.IncomingLocales, Omit: len(o.IncomingLocales) == 0}, - {Value: &o.OfferedCapabilities, Omit: len(o.OfferedCapabilities) == 0}, - {Value: &o.DesiredCapabilities, Omit: len(o.DesiredCapabilities) == 0}, - {Value: o.Properties, Omit: len(o.Properties) == 0}, - }) -} - -func (o *PerformOpen) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeOpen, []encoding.UnmarshalField{ - {Field: &o.ContainerID, HandleNull: func() error { return errors.New("Open.ContainerID is required") }}, - {Field: &o.Hostname}, - {Field: &o.MaxFrameSize, HandleNull: func() error { o.MaxFrameSize = 4294967295; return nil }}, - {Field: &o.ChannelMax, HandleNull: func() error { o.ChannelMax = 65535; return nil }}, - {Field: (*encoding.Milliseconds)(&o.IdleTimeout)}, - {Field: &o.OutgoingLocales}, - {Field: &o.IncomingLocales}, - {Field: &o.OfferedCapabilities}, - {Field: &o.DesiredCapabilities}, - {Field: &o.Properties}, - }...) -} - -func (o *PerformOpen) String() string { - return fmt.Sprintf("Open{ContainerID : %s, Hostname: %s, MaxFrameSize: %d, "+ - "ChannelMax: %d, IdleTimeout: %v, "+ - "OutgoingLocales: %v, IncomingLocales: %v, "+ - "OfferedCapabilities: %v, DesiredCapabilities: %v, "+ - "Properties: %v}", - o.ContainerID, - o.Hostname, - o.MaxFrameSize, - o.ChannelMax, - o.IdleTimeout, - o.OutgoingLocales, - o.IncomingLocales, - o.OfferedCapabilities, - o.DesiredCapabilities, - o.Properties, - ) -} - -/* - - - - - - - - - - - - - -*/ -type PerformBegin struct { - // the remote channel for this session - // If a session is locally initiated, the remote-channel MUST NOT be set. - // When an endpoint responds to a remotely initiated session, the remote-channel - // MUST be set to the channel on which the remote session sent the begin. - RemoteChannel *uint16 - - // the transfer-id of the first transfer id the sender will send - NextOutgoingID uint32 // required, sequence number http://www.ietf.org/rfc/rfc1982.txt - - // the initial incoming-window of the sender - IncomingWindow uint32 // required - - // the initial outgoing-window of the sender - OutgoingWindow uint32 // required - - // the maximum handle value that can be used on the session - // The handle-max value is the highest handle value that can be - // used on the session. A peer MUST NOT attempt to attach a link - // using a handle value outside the range that its partner can handle. - // A peer that receives a handle outside the supported range MUST - // close the connection with the framing-error error-code. - HandleMax uint32 // default 4294967295 - - // the extension capabilities the sender supports - // http://www.amqp.org/specification/1.0/session-capabilities - OfferedCapabilities encoding.MultiSymbol - - // the extension capabilities the sender can use if the receiver supports them - // The sender MUST NOT attempt to use any capability other than those it - // has declared in desired-capabilities field. - DesiredCapabilities encoding.MultiSymbol - - // session properties - // http://www.amqp.org/specification/1.0/session-properties - Properties map[encoding.Symbol]any -} - -func (b *PerformBegin) frameBody() {} - -func (b *PerformBegin) String() string { - return fmt.Sprintf("Begin{RemoteChannel: %v, NextOutgoingID: %d, IncomingWindow: %d, "+ - "OutgoingWindow: %d, HandleMax: %d, OfferedCapabilities: %v, DesiredCapabilities: %v, "+ - "Properties: %v}", - formatUint16Ptr(b.RemoteChannel), - b.NextOutgoingID, - b.IncomingWindow, - b.OutgoingWindow, - b.HandleMax, - b.OfferedCapabilities, - b.DesiredCapabilities, - b.Properties, - ) -} - -func formatUint16Ptr(p *uint16) string { - if p == nil { - return "" - } - return strconv.FormatUint(uint64(*p), 10) -} - -func (b *PerformBegin) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeBegin, []encoding.MarshalField{ - {Value: b.RemoteChannel, Omit: b.RemoteChannel == nil}, - {Value: &b.NextOutgoingID, Omit: false}, - {Value: &b.IncomingWindow, Omit: false}, - {Value: &b.OutgoingWindow, Omit: false}, - {Value: &b.HandleMax, Omit: b.HandleMax == 4294967295}, - {Value: &b.OfferedCapabilities, Omit: len(b.OfferedCapabilities) == 0}, - {Value: &b.DesiredCapabilities, Omit: len(b.DesiredCapabilities) == 0}, - {Value: b.Properties, Omit: b.Properties == nil}, - }) -} - -func (b *PerformBegin) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeBegin, []encoding.UnmarshalField{ - {Field: &b.RemoteChannel}, - {Field: &b.NextOutgoingID, HandleNull: func() error { return errors.New("Begin.NextOutgoingID is required") }}, - {Field: &b.IncomingWindow, HandleNull: func() error { return errors.New("Begin.IncomingWindow is required") }}, - {Field: &b.OutgoingWindow, HandleNull: func() error { return errors.New("Begin.OutgoingWindow is required") }}, - {Field: &b.HandleMax, HandleNull: func() error { b.HandleMax = 4294967295; return nil }}, - {Field: &b.OfferedCapabilities}, - {Field: &b.DesiredCapabilities}, - {Field: &b.Properties}, - }...) -} - -/* - - - - - - - - - - - - - - - - - - - -*/ -type PerformAttach struct { - // the name of the link - // - // This name uniquely identifies the link from the container of the source - // to the container of the target node, e.g., if the container of the source - // node is A, and the container of the target node is B, the link MAY be - // globally identified by the (ordered) tuple (A,B,). - Name string // required - - // the handle for the link while attached - // - // The numeric handle assigned by the the peer as a shorthand to refer to the - // link in all performatives that reference the link until the it is detached. - // - // The handle MUST NOT be used for other open links. An attempt to attach using - // a handle which is already associated with a link MUST be responded to with - // an immediate close carrying a handle-in-use session-error. - // - // To make it easier to monitor AMQP link attach frames, it is RECOMMENDED that - // implementations always assign the lowest available handle to this field. - // - // The two endpoints MAY potentially use different handles to refer to the same link. - // Link handles MAY be reused once a link is closed for both send and receive. - Handle uint32 // required - - // role of the link endpoint - // - // The role being played by the peer, i.e., whether the peer is the sender or the - // receiver of messages on the link. - Role encoding.Role - - // settlement policy for the sender - // - // The delivery settlement policy for the sender. When set at the receiver this - // indicates the desired value for the settlement mode at the sender. When set - // at the sender this indicates the actual settlement mode in use. The sender - // SHOULD respect the receiver's desired settlement mode if the receiver initiates - // the attach exchange and the sender supports the desired mode. - // - // 0: unsettled - The sender will send all deliveries initially unsettled to the receiver. - // 1: settled - The sender will send all deliveries settled to the receiver. - // 2: mixed - The sender MAY send a mixture of settled and unsettled deliveries to the receiver. - SenderSettleMode *encoding.SenderSettleMode - - // the settlement policy of the receiver - // - // The delivery settlement policy for the receiver. When set at the sender this - // indicates the desired value for the settlement mode at the receiver. - // When set at the receiver this indicates the actual settlement mode in use. - // The receiver SHOULD respect the sender's desired settlement mode if the sender - // initiates the attach exchange and the receiver supports the desired mode. - // - // 0: first - The receiver will spontaneously settle all incoming transfers. - // 1: second - The receiver will only settle after sending the disposition to - // the sender and receiving a disposition indicating settlement of - // the delivery from the sender. - ReceiverSettleMode *encoding.ReceiverSettleMode - - // the source for messages - // - // If no source is specified on an outgoing link, then there is no source currently - // attached to the link. A link with no source will never produce outgoing messages. - Source *Source - - // the target for messages - // - // If no target is specified on an incoming link, then there is no target currently - // attached to the link. A link with no target will never permit incoming messages. - Target *Target - - // unsettled delivery state - // - // This is used to indicate any unsettled delivery states when a suspended link is - // resumed. The map is keyed by delivery-tag with values indicating the delivery state. - // The local and remote delivery states for a given delivery-tag MUST be compared to - // resolve any in-doubt deliveries. If necessary, deliveries MAY be resent, or resumed - // based on the outcome of this comparison. See subsection 2.6.13. - // - // If the local unsettled map is too large to be encoded within a frame of the agreed - // maximum frame size then the session MAY be ended with the frame-size-too-small error. - // The endpoint SHOULD make use of the ability to send an incomplete unsettled map - // (see below) to avoid sending an error. - // - // The unsettled map MUST NOT contain null valued keys. - // - // When reattaching (as opposed to resuming), the unsettled map MUST be null. - Unsettled encoding.Unsettled - - // If set to true this field indicates that the unsettled map provided is not complete. - // When the map is incomplete the recipient of the map cannot take the absence of a - // delivery tag from the map as evidence of settlement. On receipt of an incomplete - // unsettled map a sending endpoint MUST NOT send any new deliveries (i.e. deliveries - // where resume is not set to true) to its partner (and a receiving endpoint which sent - // an incomplete unsettled map MUST detach with an error on receiving a transfer which - // does not have the resume flag set to true). - // - // Note that if this flag is set to true then the endpoints MUST detach and reattach at - // least once in order to send new deliveries. This flag can be useful when there are - // too many entries in the unsettled map to fit within a single frame. An endpoint can - // attach, resume, settle, and detach until enough unsettled state has been cleared for - // an attach where this flag is set to false. - IncompleteUnsettled bool // default: false - - // the sender's initial value for delivery-count - // - // This MUST NOT be null if role is sender, and it is ignored if the role is receiver. - InitialDeliveryCount uint32 // sequence number - - // the maximum message size supported by the link endpoint - // - // This field indicates the maximum message size supported by the link endpoint. - // Any attempt to deliver a message larger than this results in a message-size-exceeded - // link-error. If this field is zero or unset, there is no maximum size imposed by the - // link endpoint. - MaxMessageSize uint64 - - // the extension capabilities the sender supports - // http://www.amqp.org/specification/1.0/link-capabilities - OfferedCapabilities encoding.MultiSymbol - - // the extension capabilities the sender can use if the receiver supports them - // - // The sender MUST NOT attempt to use any capability other than those it - // has declared in desired-capabilities field. - DesiredCapabilities encoding.MultiSymbol - - // link properties - // http://www.amqp.org/specification/1.0/link-properties - Properties map[encoding.Symbol]any -} - -func (a *PerformAttach) frameBody() {} - -func (a PerformAttach) String() string { - return fmt.Sprintf("Attach{Name: %s, Handle: %d, Role: %s, SenderSettleMode: %s, ReceiverSettleMode: %s, "+ - "Source: %v, Target: %v, Unsettled: %v, IncompleteUnsettled: %t, InitialDeliveryCount: %d, MaxMessageSize: %d, "+ - "OfferedCapabilities: %v, DesiredCapabilities: %v, Properties: %v}", - a.Name, - a.Handle, - a.Role, - a.SenderSettleMode, - a.ReceiverSettleMode, - a.Source, - a.Target, - a.Unsettled, - a.IncompleteUnsettled, - a.InitialDeliveryCount, - a.MaxMessageSize, - a.OfferedCapabilities, - a.DesiredCapabilities, - a.Properties, - ) -} - -func (a *PerformAttach) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeAttach, []encoding.MarshalField{ - {Value: &a.Name, Omit: false}, - {Value: &a.Handle, Omit: false}, - {Value: &a.Role, Omit: false}, - {Value: a.SenderSettleMode, Omit: a.SenderSettleMode == nil}, - {Value: a.ReceiverSettleMode, Omit: a.ReceiverSettleMode == nil}, - {Value: a.Source, Omit: a.Source == nil}, - {Value: a.Target, Omit: a.Target == nil}, - {Value: a.Unsettled, Omit: len(a.Unsettled) == 0}, - {Value: &a.IncompleteUnsettled, Omit: !a.IncompleteUnsettled}, - {Value: &a.InitialDeliveryCount, Omit: a.Role == encoding.RoleReceiver}, - {Value: &a.MaxMessageSize, Omit: a.MaxMessageSize == 0}, - {Value: &a.OfferedCapabilities, Omit: len(a.OfferedCapabilities) == 0}, - {Value: &a.DesiredCapabilities, Omit: len(a.DesiredCapabilities) == 0}, - {Value: a.Properties, Omit: len(a.Properties) == 0}, - }) -} - -func (a *PerformAttach) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeAttach, []encoding.UnmarshalField{ - {Field: &a.Name, HandleNull: func() error { return errors.New("Attach.Name is required") }}, - {Field: &a.Handle, HandleNull: func() error { return errors.New("Attach.Handle is required") }}, - {Field: &a.Role, HandleNull: func() error { return errors.New("Attach.Role is required") }}, - {Field: &a.SenderSettleMode}, - {Field: &a.ReceiverSettleMode}, - {Field: &a.Source}, - {Field: &a.Target}, - {Field: &a.Unsettled}, - {Field: &a.IncompleteUnsettled}, - {Field: &a.InitialDeliveryCount}, - {Field: &a.MaxMessageSize}, - {Field: &a.OfferedCapabilities}, - {Field: &a.DesiredCapabilities}, - {Field: &a.Properties}, - }...) -} - -/* - - - - - - - - - - - - - - - - -*/ -type PerformFlow struct { - // Identifies the expected transfer-id of the next incoming transfer frame. - // This value MUST be set if the peer has received the begin frame for the - // session, and MUST NOT be set if it has not. See subsection 2.5.6 for more details. - NextIncomingID *uint32 // sequence number - - // Defines the maximum number of incoming transfer frames that the endpoint - // can currently receive. See subsection 2.5.6 for more details. - IncomingWindow uint32 // required - - // The transfer-id that will be assigned to the next outgoing transfer frame. - // See subsection 2.5.6 for more details. - NextOutgoingID uint32 // sequence number - - // Defines the maximum number of outgoing transfer frames that the endpoint - // could potentially currently send, if it was not constrained by restrictions - // imposed by its peer's incoming-window. See subsection 2.5.6 for more details. - OutgoingWindow uint32 - - // If set, indicates that the flow frame carries flow state information for the local - // link endpoint associated with the given handle. If not set, the flow frame is - // carrying only information pertaining to the session endpoint. - // - // If set to a handle that is not currently associated with an attached link, - // the recipient MUST respond by ending the session with an unattached-handle - // session error. - Handle *uint32 - - // The delivery-count is initialized by the sender when a link endpoint is created, - // and is incremented whenever a message is sent. Only the sender MAY independently - // modify this field. The receiver's value is calculated based on the last known - // value from the sender and any subsequent messages received on the link. Note that, - // despite its name, the delivery-count is not a count but a sequence number - // initialized at an arbitrary point by the sender. - // - // When the handle field is not set, this field MUST NOT be set. - // - // When the handle identifies that the flow state is being sent from the sender link - // endpoint to receiver link endpoint this field MUST be set to the current - // delivery-count of the link endpoint. - // - // When the flow state is being sent from the receiver endpoint to the sender endpoint - // this field MUST be set to the last known value of the corresponding sending endpoint. - // In the event that the receiving link endpoint has not yet seen the initial attach - // frame from the sender this field MUST NOT be set. - DeliveryCount *uint32 // sequence number - - // the current maximum number of messages that can be received - // - // The current maximum number of messages that can be handled at the receiver endpoint - // of the link. Only the receiver endpoint can independently set this value. The sender - // endpoint sets this to the last known value seen from the receiver. - // See subsection 2.6.7 for more details. - // - // When the handle field is not set, this field MUST NOT be set. - LinkCredit *uint32 - - // the number of available messages - // - // The number of messages awaiting credit at the link sender endpoint. Only the sender - // can independently set this value. The receiver sets this to the last known value seen - // from the sender. See subsection 2.6.7 for more details. - // - // When the handle field is not set, this field MUST NOT be set. - Available *uint32 - - // indicates drain mode - // - // When flow state is sent from the sender to the receiver, this field contains the - // actual drain mode of the sender. When flow state is sent from the receiver to the - // sender, this field contains the desired drain mode of the receiver. - // See subsection 2.6.7 for more details. - // - // When the handle field is not set, this field MUST NOT be set. - Drain bool - - // request state from partner - // - // If set to true then the receiver SHOULD send its state at the earliest convenient - // opportunity. - // - // If set to true, and the handle field is not set, then the sender only requires - // session endpoint state to be echoed, however, the receiver MAY fulfil this requirement - // by sending a flow performative carrying link-specific state (since any such flow also - // carries session state). - // - // If a sender makes multiple requests for the same state before the receiver can reply, - // the receiver MAY send only one flow in return. - // - // Note that if a peer responds to echo requests with flows which themselves have the - // echo field set to true, an infinite loop could result if its partner adopts the same - // policy (therefore such a policy SHOULD be avoided). - Echo bool - - // link state properties - // http://www.amqp.org/specification/1.0/link-state-properties - Properties map[encoding.Symbol]any -} - -func (f *PerformFlow) frameBody() {} - -func (f *PerformFlow) String() string { - return fmt.Sprintf("Flow{NextIncomingID: %s, IncomingWindow: %d, NextOutgoingID: %d, OutgoingWindow: %d, "+ - "Handle: %s, DeliveryCount: %s, LinkCredit: %s, Available: %s, Drain: %t, Echo: %t, Properties: %+v}", - formatUint32Ptr(f.NextIncomingID), - f.IncomingWindow, - f.NextOutgoingID, - f.OutgoingWindow, - formatUint32Ptr(f.Handle), - formatUint32Ptr(f.DeliveryCount), - formatUint32Ptr(f.LinkCredit), - formatUint32Ptr(f.Available), - f.Drain, - f.Echo, - f.Properties, - ) -} - -func formatUint32Ptr(p *uint32) string { - if p == nil { - return "" - } - return strconv.FormatUint(uint64(*p), 10) -} - -func (f *PerformFlow) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeFlow, []encoding.MarshalField{ - {Value: f.NextIncomingID, Omit: f.NextIncomingID == nil}, - {Value: &f.IncomingWindow, Omit: false}, - {Value: &f.NextOutgoingID, Omit: false}, - {Value: &f.OutgoingWindow, Omit: false}, - {Value: f.Handle, Omit: f.Handle == nil}, - {Value: f.DeliveryCount, Omit: f.DeliveryCount == nil}, - {Value: f.LinkCredit, Omit: f.LinkCredit == nil}, - {Value: f.Available, Omit: f.Available == nil}, - {Value: &f.Drain, Omit: !f.Drain}, - {Value: &f.Echo, Omit: !f.Echo}, - {Value: f.Properties, Omit: len(f.Properties) == 0}, - }) -} - -func (f *PerformFlow) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeFlow, []encoding.UnmarshalField{ - {Field: &f.NextIncomingID}, - {Field: &f.IncomingWindow, HandleNull: func() error { return errors.New("Flow.IncomingWindow is required") }}, - {Field: &f.NextOutgoingID, HandleNull: func() error { return errors.New("Flow.NextOutgoingID is required") }}, - {Field: &f.OutgoingWindow, HandleNull: func() error { return errors.New("Flow.OutgoingWindow is required") }}, - {Field: &f.Handle}, - {Field: &f.DeliveryCount}, - {Field: &f.LinkCredit}, - {Field: &f.Available}, - {Field: &f.Drain}, - {Field: &f.Echo}, - {Field: &f.Properties}, - }...) -} - -/* - - - - - - - - - - - - - - - - -*/ -type PerformTransfer struct { - // Specifies the link on which the message is transferred. - Handle uint32 // required - - // The delivery-id MUST be supplied on the first transfer of a multi-transfer - // delivery. On continuation transfers the delivery-id MAY be omitted. It is - // an error if the delivery-id on a continuation transfer differs from the - // delivery-id on the first transfer of a delivery. - DeliveryID *uint32 // sequence number - - // Uniquely identifies the delivery attempt for a given message on this link. - // This field MUST be specified for the first transfer of a multi-transfer - // message and can only be omitted for continuation transfers. It is an error - // if the delivery-tag on a continuation transfer differs from the delivery-tag - // on the first transfer of a delivery. - DeliveryTag []byte // up to 32 bytes - - // This field MUST be specified for the first transfer of a multi-transfer message - // and can only be omitted for continuation transfers. It is an error if the - // message-format on a continuation transfer differs from the message-format on - // the first transfer of a delivery. - // - // The upper three octets of a message format code identify a particular message - // format. The lowest octet indicates the version of said message format. Any given - // version of a format is forwards compatible with all higher versions. - MessageFormat *uint32 - - // If not set on the first (or only) transfer for a (multi-transfer) delivery, - // then the settled flag MUST be interpreted as being false. For subsequent - // transfers in a multi-transfer delivery if the settled flag is left unset then - // it MUST be interpreted as true if and only if the value of the settled flag on - // any of the preceding transfers was true; if no preceding transfer was sent with - // settled being true then the value when unset MUST be taken as false. - // - // If the negotiated value for snd-settle-mode at attachment is settled, then this - // field MUST be true on at least one transfer frame for a delivery (i.e., the - // delivery MUST be settled at the sender at the point the delivery has been - // completely transferred). - // - // If the negotiated value for snd-settle-mode at attachment is unsettled, then this - // field MUST be false (or unset) on every transfer frame for a delivery (unless the - // delivery is aborted). - Settled bool - - // indicates that the message has more content - // - // Note that if both the more and aborted fields are set to true, the aborted flag - // takes precedence. That is, a receiver SHOULD ignore the value of the more field - // if the transfer is marked as aborted. A sender SHOULD NOT set the more flag to - // true if it also sets the aborted flag to true. - More bool - - // If first, this indicates that the receiver MUST settle the delivery once it has - // arrived without waiting for the sender to settle first. - // - // If second, this indicates that the receiver MUST NOT settle until sending its - // disposition to the sender and receiving a settled disposition from the sender. - // - // If not set, this value is defaulted to the value negotiated on link attach. - // - // If the negotiated link value is first, then it is illegal to set this field - // to second. - // - // If the message is being sent settled by the sender, the value of this field - // is ignored. - // - // The (implicit or explicit) value of this field does not form part of the - // transfer state, and is not retained if a link is suspended and subsequently resumed. - // - // 0: first - The receiver will spontaneously settle all incoming transfers. - // 1: second - The receiver will only settle after sending the disposition to - // the sender and receiving a disposition indicating settlement of - // the delivery from the sender. - ReceiverSettleMode *encoding.ReceiverSettleMode - - // the state of the delivery at the sender - // - // When set this informs the receiver of the state of the delivery at the sender. - // This is particularly useful when transfers of unsettled deliveries are resumed - // after resuming a link. Setting the state on the transfer can be thought of as - // being equivalent to sending a disposition immediately before the transfer - // performative, i.e., it is the state of the delivery (not the transfer) that - // existed at the point the frame was sent. - // - // Note that if the transfer performative (or an earlier disposition performative - // referring to the delivery) indicates that the delivery has attained a terminal - // state, then no future transfer or disposition sent by the sender can alter that - // terminal state. - State encoding.DeliveryState - - // indicates a resumed delivery - // - // If true, the resume flag indicates that the transfer is being used to reassociate - // an unsettled delivery from a dissociated link endpoint. See subsection 2.6.13 - // for more details. - // - // The receiver MUST ignore resumed deliveries that are not in its local unsettled map. - // The sender MUST NOT send resumed transfers for deliveries not in its local - // unsettled map. - // - // If a resumed delivery spans more than one transfer performative, then the resume - // flag MUST be set to true on the first transfer of the resumed delivery. For - // subsequent transfers for the same delivery the resume flag MAY be set to true, - // or MAY be omitted. - // - // In the case where the exchange of unsettled maps makes clear that all message - // data has been successfully transferred to the receiver, and that only the final - // state (and potentially settlement) at the sender needs to be conveyed, then a - // resumed delivery MAY carry no payload and instead act solely as a vehicle for - // carrying the terminal state of the delivery at the sender. - Resume bool - - // indicates that the message is aborted - // - // Aborted messages SHOULD be discarded by the recipient (any payload within the - // frame carrying the performative MUST be ignored). An aborted message is - // implicitly settled. - Aborted bool - - // batchable hint - // - // If true, then the issuer is hinting that there is no need for the peer to urgently - // communicate updated delivery state. This hint MAY be used to artificially increase - // the amount of batching an implementation uses when communicating delivery states, - // and thereby save bandwidth. - // - // If the message being delivered is too large to fit within a single frame, then the - // setting of batchable to true on any of the transfer performatives for the delivery - // is equivalent to setting batchable to true for all the transfer performatives for - // the delivery. - // - // The batchable value does not form part of the transfer state, and is not retained - // if a link is suspended and subsequently resumed. - Batchable bool - - Payload []byte - - // optional channel to indicate to sender that transfer has completed - // - // Settled=true: closed when the transferred on network. - // Settled=false: closed when the receiver has confirmed settlement. - Done chan encoding.DeliveryState -} - -func (t *PerformTransfer) frameBody() {} - -func (t PerformTransfer) String() string { - deliveryTag := "" - if t.DeliveryTag != nil { - deliveryTag = fmt.Sprintf("%X", t.DeliveryTag) - } - - return fmt.Sprintf("Transfer{Handle: %d, DeliveryID: %s, DeliveryTag: %s, MessageFormat: %s, "+ - "Settled: %t, More: %t, ReceiverSettleMode: %s, State: %v, Resume: %t, Aborted: %t, "+ - "Batchable: %t, Payload [size]: %d}", - t.Handle, - formatUint32Ptr(t.DeliveryID), - deliveryTag, - formatUint32Ptr(t.MessageFormat), - t.Settled, - t.More, - t.ReceiverSettleMode, - t.State, - t.Resume, - t.Aborted, - t.Batchable, - len(t.Payload), - ) -} - -func (t *PerformTransfer) Marshal(wr *buffer.Buffer) error { - err := encoding.MarshalComposite(wr, encoding.TypeCodeTransfer, []encoding.MarshalField{ - {Value: &t.Handle}, - {Value: t.DeliveryID, Omit: t.DeliveryID == nil}, - {Value: &t.DeliveryTag, Omit: len(t.DeliveryTag) == 0}, - {Value: t.MessageFormat, Omit: t.MessageFormat == nil}, - {Value: &t.Settled, Omit: !t.Settled}, - {Value: &t.More, Omit: !t.More}, - {Value: t.ReceiverSettleMode, Omit: t.ReceiverSettleMode == nil}, - {Value: t.State, Omit: t.State == nil}, - {Value: &t.Resume, Omit: !t.Resume}, - {Value: &t.Aborted, Omit: !t.Aborted}, - {Value: &t.Batchable, Omit: !t.Batchable}, - }) - if err != nil { - return err - } - - wr.Append(t.Payload) - return nil -} - -func (t *PerformTransfer) Unmarshal(r *buffer.Buffer) error { - err := encoding.UnmarshalComposite(r, encoding.TypeCodeTransfer, []encoding.UnmarshalField{ - {Field: &t.Handle, HandleNull: func() error { return errors.New("Transfer.Handle is required") }}, - {Field: &t.DeliveryID}, - {Field: &t.DeliveryTag}, - {Field: &t.MessageFormat}, - {Field: &t.Settled}, - {Field: &t.More}, - {Field: &t.ReceiverSettleMode}, - {Field: &t.State}, - {Field: &t.Resume}, - {Field: &t.Aborted}, - {Field: &t.Batchable}, - }...) - if err != nil { - return err - } - - t.Payload = append([]byte(nil), r.Bytes()...) - - return err -} - -/* - - - - - - - - - - - -*/ -type PerformDisposition struct { - // directionality of disposition - // - // The role identifies whether the disposition frame contains information about - // sending link endpoints or receiving link endpoints. - Role encoding.Role - - // lower bound of deliveries - // - // Identifies the lower bound of delivery-ids for the deliveries in this set. - First uint32 // required, sequence number - - // upper bound of deliveries - // - // Identifies the upper bound of delivery-ids for the deliveries in this set. - // If not set, this is taken to be the same as first. - Last *uint32 // sequence number - - // indicates deliveries are settled - // - // If true, indicates that the referenced deliveries are considered settled by - // the issuing endpoint. - Settled bool - - // indicates state of deliveries - // - // Communicates the state of all the deliveries referenced by this disposition. - State encoding.DeliveryState - - // batchable hint - // - // If true, then the issuer is hinting that there is no need for the peer to - // urgently communicate the impact of the updated delivery states. This hint - // MAY be used to artificially increase the amount of batching an implementation - // uses when communicating delivery states, and thereby save bandwidth. - Batchable bool -} - -func (d *PerformDisposition) frameBody() {} - -func (d PerformDisposition) String() string { - return fmt.Sprintf("Disposition{Role: %s, First: %d, Last: %s, Settled: %t, State: %v, Batchable: %t}", - d.Role, - d.First, - formatUint32Ptr(d.Last), - d.Settled, - d.State, - d.Batchable, - ) -} - -func (d *PerformDisposition) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeDisposition, []encoding.MarshalField{ - {Value: &d.Role, Omit: false}, - {Value: &d.First, Omit: false}, - {Value: d.Last, Omit: d.Last == nil}, - {Value: &d.Settled, Omit: !d.Settled}, - {Value: d.State, Omit: d.State == nil}, - {Value: &d.Batchable, Omit: !d.Batchable}, - }) -} - -func (d *PerformDisposition) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeDisposition, []encoding.UnmarshalField{ - {Field: &d.Role, HandleNull: func() error { return errors.New("Disposition.Role is required") }}, - {Field: &d.First, HandleNull: func() error { return errors.New("Disposition.Handle is required") }}, - {Field: &d.Last}, - {Field: &d.Settled}, - {Field: &d.State}, - {Field: &d.Batchable}, - }...) -} - -/* - - - - - - - - -*/ -type PerformDetach struct { - // the local handle of the link to be detached - Handle uint32 //required - - // if true then the sender has closed the link - Closed bool - - // error causing the detach - // - // If set, this field indicates that the link is being detached due to an error - // condition. The value of the field SHOULD contain details on the cause of the error. - Error *encoding.Error -} - -func (d *PerformDetach) frameBody() {} - -func (d PerformDetach) String() string { - return fmt.Sprintf("Detach{Handle: %d, Closed: %t, Error: %v}", - d.Handle, - d.Closed, - d.Error, - ) -} - -func (d *PerformDetach) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeDetach, []encoding.MarshalField{ - {Value: &d.Handle, Omit: false}, - {Value: &d.Closed, Omit: !d.Closed}, - {Value: d.Error, Omit: d.Error == nil}, - }) -} - -func (d *PerformDetach) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeDetach, []encoding.UnmarshalField{ - {Field: &d.Handle, HandleNull: func() error { return errors.New("Detach.Handle is required") }}, - {Field: &d.Closed}, - {Field: &d.Error}, - }...) -} - -/* - - - - - - -*/ -type PerformEnd struct { - // error causing the end - // - // If set, this field indicates that the session is being ended due to an error - // condition. The value of the field SHOULD contain details on the cause of the error. - Error *encoding.Error -} - -func (e *PerformEnd) frameBody() {} - -func (d PerformEnd) String() string { - return fmt.Sprintf("End{Error: %v}", d.Error) -} - -func (e *PerformEnd) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeEnd, []encoding.MarshalField{ - {Value: e.Error, Omit: e.Error == nil}, - }) -} - -func (e *PerformEnd) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeEnd, - encoding.UnmarshalField{Field: &e.Error}, - ) -} - -/* - - - - - - -*/ -type PerformClose struct { - // error causing the close - // - // If set, this field indicates that the session is being closed due to an error - // condition. The value of the field SHOULD contain details on the cause of the error. - Error *encoding.Error -} - -func (c *PerformClose) frameBody() {} - -func (c *PerformClose) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeClose, []encoding.MarshalField{ - {Value: c.Error, Omit: c.Error == nil}, - }) -} - -func (c *PerformClose) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeClose, - encoding.UnmarshalField{Field: &c.Error}, - ) -} - -func (c *PerformClose) String() string { - return fmt.Sprintf("Close{Error: %s}", c.Error) -} - -/* - - - - - - -*/ - -type SASLInit struct { - Mechanism encoding.Symbol - InitialResponse []byte - Hostname string -} - -func (si *SASLInit) frameBody() {} - -func (si *SASLInit) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeSASLInit, []encoding.MarshalField{ - {Value: &si.Mechanism, Omit: false}, - {Value: &si.InitialResponse, Omit: false}, - {Value: &si.Hostname, Omit: len(si.Hostname) == 0}, - }) -} - -func (si *SASLInit) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeSASLInit, []encoding.UnmarshalField{ - {Field: &si.Mechanism, HandleNull: func() error { return errors.New("saslInit.Mechanism is required") }}, - {Field: &si.InitialResponse}, - {Field: &si.Hostname}, - }...) -} - -func (si *SASLInit) String() string { - // Elide the InitialResponse as it may contain a plain text secret. - return fmt.Sprintf("SaslInit{Mechanism : %s, InitialResponse: ********, Hostname: %s}", - si.Mechanism, - si.Hostname, - ) -} - -/* - - - - -*/ - -type SASLMechanisms struct { - Mechanisms encoding.MultiSymbol -} - -func (sm *SASLMechanisms) frameBody() {} - -func (sm *SASLMechanisms) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeSASLMechanism, []encoding.MarshalField{ - {Value: &sm.Mechanisms, Omit: false}, - }) -} - -func (sm *SASLMechanisms) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeSASLMechanism, - encoding.UnmarshalField{Field: &sm.Mechanisms, HandleNull: func() error { return errors.New("saslMechanisms.Mechanisms is required") }}, - ) -} - -func (sm *SASLMechanisms) String() string { - return fmt.Sprintf("SaslMechanisms{Mechanisms : %v}", - sm.Mechanisms, - ) -} - -/* - - - - -*/ - -type SASLChallenge struct { - Challenge []byte -} - -func (sc *SASLChallenge) String() string { - return "Challenge{Challenge: ********}" -} - -func (sc *SASLChallenge) frameBody() {} - -func (sc *SASLChallenge) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeSASLChallenge, []encoding.MarshalField{ - {Value: &sc.Challenge, Omit: false}, - }) -} - -func (sc *SASLChallenge) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeSASLChallenge, []encoding.UnmarshalField{ - {Field: &sc.Challenge, HandleNull: func() error { return errors.New("saslChallenge.Challenge is required") }}, - }...) -} - -/* - - - - -*/ - -type SASLResponse struct { - Response []byte -} - -func (sr *SASLResponse) String() string { - return "Response{Response: ********}" -} - -func (sr *SASLResponse) frameBody() {} - -func (sr *SASLResponse) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeSASLResponse, []encoding.MarshalField{ - {Value: &sr.Response, Omit: false}, - }) -} - -func (sr *SASLResponse) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeSASLResponse, []encoding.UnmarshalField{ - {Field: &sr.Response, HandleNull: func() error { return errors.New("saslResponse.Response is required") }}, - }...) -} - -/* - - - - - -*/ - -type SASLOutcome struct { - Code encoding.SASLCode - AdditionalData []byte -} - -func (so *SASLOutcome) frameBody() {} - -func (so *SASLOutcome) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeSASLOutcome, []encoding.MarshalField{ - {Value: &so.Code, Omit: false}, - {Value: &so.AdditionalData, Omit: len(so.AdditionalData) == 0}, - }) -} - -func (so *SASLOutcome) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeSASLOutcome, []encoding.UnmarshalField{ - {Field: &so.Code, HandleNull: func() error { return errors.New("saslOutcome.AdditionalData is required") }}, - {Field: &so.AdditionalData}, - }...) -} - -func (so *SASLOutcome) String() string { - return fmt.Sprintf("SaslOutcome{Code : %v, AdditionalData: %v}", - so.Code, - so.AdditionalData, - ) -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/internal/frames/parsing.go b/sdk/messaging/azservicebus/internal/go-amqp/internal/frames/parsing.go deleted file mode 100644 index 2b0b34837774..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/internal/frames/parsing.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package frames - -import ( - "encoding/binary" - "errors" - "fmt" - "math" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/buffer" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding" -) - -const HeaderSize = 8 - -// Frame structure: -// -// header (8 bytes) -// 0-3: SIZE (total size, at least 8 bytes for header, uint32) -// 4: DOFF (data offset,at least 2, count of 4 bytes words, uint8) -// 5: TYPE (frame type) -// 0x0: AMQP -// 0x1: SASL -// 6-7: type dependent (channel for AMQP) -// extended header (opt) -// body (opt) - -// Header in a structure appropriate for use with binary.Read() -type Header struct { - // size: an unsigned 32-bit integer that MUST contain the total frame size of the frame header, - // extended header, and frame body. The frame is malformed if the size is less than the size of - // the frame header (8 bytes). - Size uint32 - // doff: gives the position of the body within the frame. The value of the data offset is an - // unsigned, 8-bit integer specifying a count of 4-byte words. Due to the mandatory 8-byte - // frame header, the frame is malformed if the value is less than 2. - DataOffset uint8 - FrameType uint8 - Channel uint16 -} - -// ParseHeader reads the header from r and returns the result. -// -// No validation is done. -func ParseHeader(r *buffer.Buffer) (Header, error) { - buf, ok := r.Next(8) - if !ok { - return Header{}, errors.New("invalid frameHeader") - } - _ = buf[7] - - fh := Header{ - Size: binary.BigEndian.Uint32(buf[0:4]), - DataOffset: buf[4], - FrameType: buf[5], - Channel: binary.BigEndian.Uint16(buf[6:8]), - } - - if fh.Size < HeaderSize { - return fh, fmt.Errorf("received frame header with invalid size %d", fh.Size) - } - - if fh.DataOffset < 2 { - return fh, fmt.Errorf("received frame header with invalid data offset %d", fh.DataOffset) - } - - return fh, nil -} - -// ParseBody reads and unmarshals an AMQP frame. -func ParseBody(r *buffer.Buffer) (FrameBody, error) { - payload := r.Bytes() - - if r.Len() < 3 || payload[0] != 0 || encoding.AMQPType(payload[1]) != encoding.TypeCodeSmallUlong { - return nil, errors.New("invalid frame body header") - } - - switch pType := encoding.AMQPType(payload[2]); pType { - case encoding.TypeCodeOpen: - t := new(PerformOpen) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeBegin: - t := new(PerformBegin) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeAttach: - t := new(PerformAttach) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeFlow: - t := new(PerformFlow) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeTransfer: - t := new(PerformTransfer) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeDisposition: - t := new(PerformDisposition) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeDetach: - t := new(PerformDetach) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeEnd: - t := new(PerformEnd) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeClose: - t := new(PerformClose) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeSASLMechanism: - t := new(SASLMechanisms) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeSASLChallenge: - t := new(SASLChallenge) - err := t.Unmarshal(r) - return t, err - case encoding.TypeCodeSASLOutcome: - t := new(SASLOutcome) - err := t.Unmarshal(r) - return t, err - default: - return nil, fmt.Errorf("unknown performative type %02x", pType) - } -} - -// Write encodes fr into buf. -// split out from conn.WriteFrame for testing purposes. -func Write(buf *buffer.Buffer, fr Frame) error { - // write header - buf.Append([]byte{ - 0, 0, 0, 0, // size, overwrite later - 2, // doff, see frameHeader.DataOffset comment - uint8(fr.Type), // frame type - }) - buf.AppendUint16(fr.Channel) // channel - - // write AMQP frame body - err := encoding.Marshal(buf, fr.Body) - if err != nil { - return err - } - - // validate size - if uint(buf.Len()) > math.MaxUint32 { - return errors.New("frame too large") - } - - // retrieve raw bytes - bufBytes := buf.Bytes() - - // write correct size - binary.BigEndian.PutUint32(bufBytes, uint32(len(bufBytes))) - return nil -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/internal/queue/queue.go b/sdk/messaging/azservicebus/internal/go-amqp/internal/queue/queue.go deleted file mode 100644 index 45d6f5af9daf..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/internal/queue/queue.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Microsoft Corporation - -package queue - -import ( - "container/ring" -) - -// Holder provides synchronized access to a *Queue[T]. -type Holder[T any] struct { - // these channels work in tandem to provide exclusive access to the underlying *Queue[T]. - // each channel is created with a buffer size of one. - // empty behaves like a mutex when there's one or more messages in the queue. - // populated is like a semaphore when the queue is empty. - // the *Queue[T] is only ever in one channel. which channel depends on if it contains any items. - // the initial state is for empty to contain an empty queue. - empty chan *Queue[T] - populated chan *Queue[T] -} - -// NewHolder creates a new Holder[T] that contains the provided *Queue[T]. -func NewHolder[T any](q *Queue[T]) *Holder[T] { - h := &Holder[T]{ - empty: make(chan *Queue[T], 1), - populated: make(chan *Queue[T], 1), - } - h.Release(q) - return h -} - -// Acquire attempts to acquire the *Queue[T]. If the *Queue[T] has already been acquired the call blocks. -// When the *Queue[T] is no longer required, you MUST call Release() to relinquish acquisition. -func (h *Holder[T]) Acquire() *Queue[T] { - // the queue will be in only one of the channels, it doesn't matter which one - var q *Queue[T] - select { - case q = <-h.empty: - // empty queue - case q = <-h.populated: - // populated queue - } - return q -} - -// Wait returns a channel that's signaled when the *Queue[T] contains at least one item. -// When the *Queue[T] is no longer required, you MUST call Release() to relinquish acquisition. -func (h *Holder[T]) Wait() <-chan *Queue[T] { - return h.populated -} - -// Release returns the *Queue[T] back to the Holder[T]. -// Once the *Queue[T] has been released, it is no longer safe to call its methods. -func (h *Holder[T]) Release(q *Queue[T]) { - if q.Len() == 0 { - h.empty <- q - } else { - h.populated <- q - } -} - -// Len returns the length of the *Queue[T]. -func (h *Holder[T]) Len() int { - msgLen := 0 - select { - case q := <-h.empty: - h.empty <- q - case q := <-h.populated: - msgLen = q.Len() - h.populated <- q - } - return msgLen -} - -// Queue[T] is a segmented FIFO queue of Ts. -type Queue[T any] struct { - head *ring.Ring - tail *ring.Ring - size int -} - -// New creates a new instance of Queue[T]. -// - size is the size of each Queue segment -func New[T any](size int) *Queue[T] { - r := &ring.Ring{ - Value: &segment[T]{ - items: make([]*T, size), - }, - } - return &Queue[T]{ - head: r, - tail: r, - } -} - -// Enqueue adds the specified item to the end of the queue. -// If the current segment is full, a new segment is created. -func (q *Queue[T]) Enqueue(item T) { - for { - r := q.tail - seg := r.Value.(*segment[T]) - - if seg.tail < len(seg.items) { - seg.items[seg.tail] = &item - seg.tail++ - q.size++ - return - } - - // segment is full, can we advance? - if next := r.Next(); next != q.head { - q.tail = next - continue - } - - // no, add a new ring - r.Link(&ring.Ring{ - Value: &segment[T]{ - items: make([]*T, len(seg.items)), - }, - }) - - q.tail = r.Next() - } -} - -// Dequeue removes and returns the item from the front of the queue. -func (q *Queue[T]) Dequeue() *T { - r := q.head - seg := r.Value.(*segment[T]) - - if seg.tail == 0 { - // queue is empty - return nil - } - - // remove first item - item := seg.items[seg.head] - seg.items[seg.head] = nil - seg.head++ - q.size-- - - if seg.head == seg.tail { - // segment is now empty, reset indices - seg.head, seg.tail = 0, 0 - - // if we're not at the last ring, advance head to the next one - if q.head != q.tail { - q.head = r.Next() - } - } - - return item -} - -// Len returns the total count of enqueued items. -func (q *Queue[T]) Len() int { - return q.size -} - -type segment[T any] struct { - items []*T - head int - tail int -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/internal/shared/shared.go b/sdk/messaging/azservicebus/internal/go-amqp/internal/shared/shared.go deleted file mode 100644 index 867c1e932bf5..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/internal/shared/shared.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation - -package shared - -import ( - "encoding/base64" - "math/rand" - "sync" - "time" -) - -// lockedRand provides a rand source that is safe for concurrent use. -type lockedRand struct { - mu sync.Mutex - src *rand.Rand -} - -func (r *lockedRand) Read(p []byte) (int, error) { - r.mu.Lock() - defer r.mu.Unlock() - return r.src.Read(p) -} - -// package scoped rand source to avoid any issues with seeding -// of the global source. -var pkgRand = &lockedRand{ - src: rand.New(rand.NewSource(time.Now().UnixNano())), -} - -// RandString returns a base64 encoded string of n bytes. -func RandString(n int) string { - b := make([]byte, n) - // from math/rand, cannot fail - _, _ = pkgRand.Read(b) - return base64.RawURLEncoding.EncodeToString(b) -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/link.go b/sdk/messaging/azservicebus/internal/go-amqp/link.go deleted file mode 100644 index 1965b75049ed..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/link.go +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "context" - "errors" - "fmt" - "sync" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/debug" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/frames" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/queue" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/shared" -) - -// linkKey uniquely identifies a link on a connection by name and direction. -// -// A link can be identified uniquely by the ordered tuple -// -// (source-container-id, target-container-id, name) -// -// On a single connection the container ID pairs can be abbreviated -// to a boolean flag indicating the direction of the link. -type linkKey struct { - name string - role encoding.Role // Local role: sender/receiver -} - -// link contains the common state and methods for sending and receiving links -type link struct { - key linkKey // Name and direction - handle uint32 // our handle - remoteHandle uint32 // remote's handle - dynamicAddr bool // request a dynamic link address from the server - - // frames destined for this link are added to this queue by Session.muxFrameToLink - rxQ *queue.Holder[frames.FrameBody] - - // used for gracefully closing link - close chan struct{} // signals a link's mux to shut down; DO NOT use this to check if a link has terminated, use done instead - closeOnce *sync.Once // closeOnce protects close from being closed multiple times - - done chan struct{} // closed when the link has terminated (mux exited); DO NOT wait on this from within a link's mux() as it will never trigger! - doneErr error // contains the mux error state; ONLY written to by the mux and MUST only be read from after done is closed! - closeErr error // contains the error state returned from closeLink(); ONLY closeLink() reads/writes this! - - session *Session // parent session - source *frames.Source // used for Receiver links - target *frames.Target // used for Sender links - properties map[encoding.Symbol]any // additional properties sent upon link attach - - // "The delivery-count is initialized by the sender when a link endpoint is created, - // and is incremented whenever a message is sent. Only the sender MAY independently - // modify this field. The receiver's value is calculated based on the last known - // value from the sender and any subsequent messages received on the link. Note that, - // despite its name, the delivery-count is not a count but a sequence number - // initialized at an arbitrary point by the sender." - deliveryCount uint32 - - // The current maximum number of messages that can be handled at the receiver endpoint of the link. Only the receiver endpoint - // can independently set this value. The sender endpoint sets this to the last known value seen from the receiver. - linkCredit uint32 - - senderSettleMode *SenderSettleMode - receiverSettleMode *ReceiverSettleMode - maxMessageSize uint64 - - closeInProgress bool // indicates that the detach performative has been sent -} - -func newLink(s *Session, r encoding.Role) link { - l := link{ - key: linkKey{shared.RandString(40), r}, - session: s, - close: make(chan struct{}), - closeOnce: &sync.Once{}, - done: make(chan struct{}), - } - - // set the segment size relative to respective window - var segmentSize int - if r == encoding.RoleReceiver { - segmentSize = int(s.incomingWindow) - } else { - segmentSize = int(s.outgoingWindow) - } - - l.rxQ = queue.NewHolder(queue.New[frames.FrameBody](segmentSize)) - return l -} - -// waitForFrame waits for an incoming frame to be queued. -// it returns the next frame from the queue, or an error. -// the error is either from the context or session.doneErr. -// not meant for consumption outside of link.go. -func (l *link) waitForFrame(ctx context.Context) (frames.FrameBody, error) { - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-l.session.done: - // session has terminated, no need to deallocate in this case - return nil, l.session.doneErr - case q := <-l.rxQ.Wait(): - // frame received - fr := q.Dequeue() - l.rxQ.Release(q) - return *fr, nil - } -} - -// attach sends the Attach performative to establish the link with its parent session. -// this is automatically called by the new*Link constructors. -func (l *link) attach(ctx context.Context, beforeAttach func(*frames.PerformAttach), afterAttach func(*frames.PerformAttach)) error { - if err := l.session.freeAbandonedLinks(ctx); err != nil { - return err - } - - // once the abandoned links have been cleaned up we can create our link - if err := l.session.allocateHandle(ctx, l); err != nil { - return err - } - - attach := &frames.PerformAttach{ - Name: l.key.name, - Handle: l.handle, - ReceiverSettleMode: l.receiverSettleMode, - SenderSettleMode: l.senderSettleMode, - MaxMessageSize: l.maxMessageSize, - Source: l.source, - Target: l.target, - Properties: l.properties, - } - - // link-specific configuration of the attach frame - beforeAttach(attach) - - if err := l.txFrameAndWait(ctx, attach); err != nil { - return err - } - - // wait for response - fr, err := l.waitForFrame(ctx) - if err != nil { - l.session.abandonLink(l) - return err - } - - resp, ok := fr.(*frames.PerformAttach) - if !ok { - debug.Log(1, "RX (link %p): unexpected attach response frame %T", l, fr) - if err := l.session.conn.Close(); err != nil { - return err - } - return &ConnError{inner: fmt.Errorf("unexpected attach response: %#v", fr)} - } - - // If the remote encounters an error during the attach it returns an Attach - // with no Source or Target. The remote then sends a Detach with an error. - // - // Note that if the application chooses not to create a terminus, the session - // endpoint will still create a link endpoint and issue an attach indicating - // that the link endpoint has no associated local terminus. In this case, the - // session endpoint MUST immediately detach the newly created link endpoint. - // - // http://docs.oasis-open.org/amqp/core/v1.0/csprd01/amqp-core-transport-v1.0-csprd01.html#doc-idp386144 - if resp.Source == nil && resp.Target == nil { - // wait for detach - fr, err := l.waitForFrame(ctx) - if err != nil { - // we timed out waiting for the peer to close the link, this really isn't an abandoned link. - // however, we still need to send the detach performative to ack the peer. - l.session.abandonLink(l) - return err - } - - detach, ok := fr.(*frames.PerformDetach) - if !ok { - if err := l.session.conn.Close(); err != nil { - return err - } - return &ConnError{inner: fmt.Errorf("unexpected frame while waiting for detach: %#v", fr)} - } - - // send return detach - fr = &frames.PerformDetach{ - Handle: l.handle, - Closed: true, - } - if err := l.txFrameAndWait(ctx, fr); err != nil { - return err - } - - if detach.Error == nil { - return fmt.Errorf("received detach with no error specified") - } - return detach.Error - } - - if l.maxMessageSize == 0 || resp.MaxMessageSize < l.maxMessageSize { - l.maxMessageSize = resp.MaxMessageSize - } - - // link-specific configuration post attach - afterAttach(resp) - - if err := l.setSettleModes(resp); err != nil { - // close the link as there's a mismatch on requested/supported settlement modes - dr := &frames.PerformDetach{ - Handle: l.handle, - Closed: true, - } - if err := l.txFrameAndWait(ctx, dr); err != nil { - return err - } - return err - } - - return nil -} - -// setSettleModes sets the settlement modes based on the resp frames.PerformAttach. -// -// If a settlement mode has been explicitly set locally and it was not honored by the -// server an error is returned. -func (l *link) setSettleModes(resp *frames.PerformAttach) error { - var ( - localRecvSettle = receiverSettleModeValue(l.receiverSettleMode) - respRecvSettle = receiverSettleModeValue(resp.ReceiverSettleMode) - ) - if l.receiverSettleMode != nil && localRecvSettle != respRecvSettle { - return fmt.Errorf("amqp: receiver settlement mode %q requested, received %q from server", l.receiverSettleMode, &respRecvSettle) - } - l.receiverSettleMode = &respRecvSettle - - var ( - localSendSettle = senderSettleModeValue(l.senderSettleMode) - respSendSettle = senderSettleModeValue(resp.SenderSettleMode) - ) - if l.senderSettleMode != nil && localSendSettle != respSendSettle { - return fmt.Errorf("amqp: sender settlement mode %q requested, received %q from server", l.senderSettleMode, &respSendSettle) - } - l.senderSettleMode = &respSendSettle - - return nil -} - -// muxHandleFrame processes fr based on type. -func (l *link) muxHandleFrame(fr frames.FrameBody) error { - switch fr := fr.(type) { - case *frames.PerformDetach: - if !fr.Closed { - l.closeWithError(ErrCondNotImplemented, fmt.Sprintf("non-closing detach not supported: %+v", fr)) - return nil - } - - // there are two possibilities: - // - this is the ack to a client-side Close() - // - the peer is closing the link so we must ack - - if l.closeInProgress { - // if the client-side close was initiated due to an error (l.closeWithError) - // then l.doneErr will already be set. in this case, return that error instead - // of an empty LinkError which indicates a clean client-side close. - if l.doneErr != nil { - return l.doneErr - } - return &LinkError{} - } - - dr := &frames.PerformDetach{ - Handle: l.handle, - Closed: true, - } - l.txFrame(context.Background(), dr, nil) - return &LinkError{RemoteErr: fr.Error} - - default: - debug.Log(1, "RX (link %p): unexpected frame: %s", l, fr) - l.closeWithError(ErrCondInternalError, fmt.Sprintf("link received unexpected frame %T", fr)) - return nil - } -} - -// Close closes the Sender and AMQP link. -func (l *link) closeLink(ctx context.Context) error { - var ctxErr error - l.closeOnce.Do(func() { - close(l.close) - - // once the mux has received the ack'ing detach performative, the mux will - // exit which deletes the link and closes l.done. - select { - case <-l.done: - l.closeErr = l.doneErr - case <-ctx.Done(): - // notify the caller that the close timed out/was cancelled. - // the mux will remain running and once the ack is received it will terminate. - ctxErr = ctx.Err() - - // record that the close timed out/was cancelled. - // subsequent calls to closeLink() will return this - debug.Log(1, "TX (link %p) closing %s: %v", l, l.key.name, ctxErr) - l.closeErr = &LinkError{inner: ctxErr} - } - }) - - if ctxErr != nil { - return ctxErr - } - - var linkErr *LinkError - if errors.As(l.closeErr, &linkErr) && linkErr.RemoteErr == nil && linkErr.inner == nil { - // an empty LinkError means the link was cleanly closed by the caller - return nil - } - return l.closeErr -} - -// closeWithError initiates closing the link with the specified AMQP error. -// the mux must continue to run until the ack'ing detach is received. -// l.doneErr is populated with a &LinkError{} containing an inner error constructed from the specified values -// - cnd is the AMQP error condition -// - desc is the error description -func (l *link) closeWithError(cnd ErrCond, desc string) { - amqpErr := &Error{Condition: cnd, Description: desc} - if l.closeInProgress { - debug.Log(3, "TX (link %p) close error already pending, discarding %v", l, amqpErr) - return - } - - dr := &frames.PerformDetach{ - Handle: l.handle, - Closed: true, - Error: amqpErr, - } - l.closeInProgress = true - l.doneErr = &LinkError{inner: fmt.Errorf("%s: %s", cnd, desc)} - l.txFrame(context.Background(), dr, nil) -} - -// txFrame sends the specified frame via the link's session. -// you MUST call this instead of session.txFrame() to ensure -// that frames are not sent during session shutdown. -func (l *link) txFrame(ctx context.Context, fr frames.FrameBody, sent chan error) { - // NOTE: there is no need to select on l.done as this is either - // called from a link's mux or before the mux has even started. - select { - case <-l.session.done: - if sent != nil { - sent <- l.session.doneErr - } - case <-l.session.endSent: - // we swallow this to prevent the link's mux from terminating. - // l.session.done will soon close so this is temporary. - return - case l.session.tx <- frameBodyEnvelope{Ctx: ctx, FrameBody: fr, Sent: sent}: - debug.Log(2, "TX (link %p): mux frame to Session (%p): %s", l, l.session, fr) - } -} - -// txFrame sends the specified frame via the link's session. -// you MUST call this instead of session.txFrame() to ensure -// that frames are not sent during session shutdown. -func (l *link) txFrameAndWait(ctx context.Context, fr frames.FrameBody) error { - // NOTE: there is no need to select on l.done as this is either - // called from a link's mux or before the mux has even started. - sent := make(chan error, 1) - select { - case <-l.session.done: - return l.session.doneErr - case <-l.session.endSent: - // we swallow this to prevent the link's mux from terminating. - // l.session.done will soon close so this is temporary. - return nil - case l.session.tx <- frameBodyEnvelope{Ctx: ctx, FrameBody: fr, Sent: sent}: - debug.Log(2, "TX (link %p): mux frame to Session (%p): %s", l, l.session, fr) - } - - select { - case err := <-sent: - return err - case <-l.done: - return l.doneErr - case <-l.session.done: - return l.session.doneErr - } -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/link_options.go b/sdk/messaging/azservicebus/internal/go-amqp/link_options.go deleted file mode 100644 index 31daa94eb9f6..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/link_options.go +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding" -) - -type SenderOptions struct { - // Capabilities is the list of extension capabilities the sender supports. - Capabilities []string - - // Durability indicates what state of the sender will be retained durably. - // - // Default: DurabilityNone. - Durability Durability - - // DynamicAddress indicates a dynamic address is to be used. - // Any specified address will be ignored. - // - // Default: false. - DynamicAddress bool - - // ExpiryPolicy determines when the expiry timer of the sender starts counting - // down from the timeout value. If the link is subsequently re-attached before - // the timeout is reached, the count down is aborted. - // - // Default: ExpirySessionEnd. - ExpiryPolicy ExpiryPolicy - - // ExpiryTimeout is the duration in seconds that the sender will be retained. - // - // Default: 0. - ExpiryTimeout uint32 - - // Name sets the name of the link. - // - // Link names must be unique per-connection and direction. - // - // Default: randomly generated. - Name string - - // Properties sets an entry in the link properties map sent to the server. - Properties map[string]any - - // RequestedReceiverSettleMode sets the requested receiver settlement mode. - // - // If a settlement mode is explicitly set and the server does not - // honor it an error will be returned during link attachment. - // - // Default: Accept the settlement mode set by the server, commonly ModeFirst. - RequestedReceiverSettleMode *ReceiverSettleMode - - // SettlementMode sets the settlement mode in use by this sender. - // - // Default: ModeMixed. - SettlementMode *SenderSettleMode - - // SourceAddress specifies the source address for this sender. - SourceAddress string - - // TargetCapabilities is the list of extension capabilities the sender desires. - TargetCapabilities []string - - // TargetDurability indicates what state of the peer will be retained durably. - // - // Default: DurabilityNone. - TargetDurability Durability - - // TargetExpiryPolicy determines when the expiry timer of the peer starts counting - // down from the timeout value. If the link is subsequently re-attached before - // the timeout is reached, the count down is aborted. - // - // Default: ExpirySessionEnd. - TargetExpiryPolicy ExpiryPolicy - - // TargetExpiryTimeout is the duration in seconds that the peer will be retained. - // - // Default: 0. - TargetExpiryTimeout uint32 -} - -type ReceiverOptions struct { - // Capabilities is the list of extension capabilities the receiver supports. - Capabilities []string - - // Credit specifies the maximum number of unacknowledged messages - // the sender can transmit. Once this limit is reached, no more messages - // will arrive until messages are acknowledged and settled. - // - // As messages are settled, any available credit will automatically be issued. - // - // Setting this to -1 requires manual management of link credit. - // Credits can be added with IssueCredit(), and links can also be - // drained with DrainCredit(). - // This should only be enabled when complete control of the link's - // flow control is required. - // - // Default: 1. - Credit int32 - - // Durability indicates what state of the receiver will be retained durably. - // - // Default: DurabilityNone. - Durability Durability - - // DynamicAddress indicates a dynamic address is to be used. - // Any specified address will be ignored. - // - // Default: false. - DynamicAddress bool - - // ExpiryPolicy determines when the expiry timer of the sender starts counting - // down from the timeout value. If the link is subsequently re-attached before - // the timeout is reached, the count down is aborted. - // - // Default: ExpirySessionEnd. - ExpiryPolicy ExpiryPolicy - - // ExpiryTimeout is the duration in seconds that the sender will be retained. - // - // Default: 0. - ExpiryTimeout uint32 - - // Filters contains the desired filters for this receiver. - // If the peer cannot fulfill the filters the link will be detached. - Filters []LinkFilter - - // MaxMessageSize sets the maximum message size that can - // be received on the link. - // - // A size of zero indicates no limit. - // - // Default: 0. - MaxMessageSize uint64 - - // Name sets the name of the link. - // - // Link names must be unique per-connection and direction. - // - // Default: randomly generated. - Name string - - // Properties sets an entry in the link properties map sent to the server. - Properties map[string]any - - // RequestedSenderSettleMode sets the requested sender settlement mode. - // - // If a settlement mode is explicitly set and the server does not - // honor it an error will be returned during link attachment. - // - // Default: Accept the settlement mode set by the server, commonly ModeMixed. - RequestedSenderSettleMode *SenderSettleMode - - // SettlementMode sets the settlement mode in use by this receiver. - // - // Default: ModeFirst. - SettlementMode *ReceiverSettleMode - - // TargetAddress specifies the target address for this receiver. - TargetAddress string - - // SourceCapabilities is the list of extension capabilities the receiver desires. - SourceCapabilities []string - - // SourceDurability indicates what state of the peer will be retained durably. - // - // Default: DurabilityNone. - SourceDurability Durability - - // SourceExpiryPolicy determines when the expiry timer of the peer starts counting - // down from the timeout value. If the link is subsequently re-attached before - // the timeout is reached, the count down is aborted. - // - // Default: ExpirySessionEnd. - SourceExpiryPolicy ExpiryPolicy - - // SourceExpiryTimeout is the duration in seconds that the peer will be retained. - // - // Default: 0. - SourceExpiryTimeout uint32 -} - -// LinkFilter is an advanced API for setting non-standard source filters. -// Please file an issue or open a PR if a standard filter is missing from this -// library. -// -// The name is the key for the filter map. It will be encoded as an AMQP symbol type. -// -// The code is the descriptor of the described type value. The domain-id and descriptor-id -// should be concatenated together. If 0 is passed as the code, the name will be used as -// the descriptor. -// -// The value is the value of the descriped types. Acceptable types for value are specific -// to the filter. -// -// Example: -// -// The standard selector-filter is defined as: -// -// -// -// In this case the name is "apache.org:selector-filter:string" and the code is -// 0x0000468C00000004. -// -// LinkSourceFilter("apache.org:selector-filter:string", 0x0000468C00000004, exampleValue) -// -// References: -// -// http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-filter-set -// http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-types-v1.0-os.html#section-descriptor-values -type LinkFilter func(encoding.Filter) - -// NewLinkFilter creates a new LinkFilter with the specified values. -// Any preexisting link filter with the same name will be updated with the new code and value. -func NewLinkFilter(name string, code uint64, value any) LinkFilter { - return func(f encoding.Filter) { - var descriptor any - if code != 0 { - descriptor = code - } else { - descriptor = encoding.Symbol(name) - } - f[encoding.Symbol(name)] = &encoding.DescribedType{ - Descriptor: descriptor, - Value: value, - } - } -} - -// NewSelectorFilter creates a new selector filter (apache.org:selector-filter:string) with the specified filter value. -// Any preexisting selector filter will be updated with the new filter value. -func NewSelectorFilter(filter string) LinkFilter { - return NewLinkFilter(selectorFilter, selectorFilterCode, filter) -} - -const ( - selectorFilter = "apache.org:selector-filter:string" - selectorFilterCode = uint64(0x0000468C00000004) -) diff --git a/sdk/messaging/azservicebus/internal/go-amqp/message.go b/sdk/messaging/azservicebus/internal/go-amqp/message.go deleted file mode 100644 index 404a3cc186da..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/message.go +++ /dev/null @@ -1,492 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "fmt" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/buffer" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding" -) - -// Message is an AMQP message. -type Message struct { - // Message format code. - // - // The upper three octets of a message format code identify a particular message - // format. The lowest octet indicates the version of said message format. Any - // given version of a format is forwards compatible with all higher versions. - Format uint32 - - // The DeliveryTag can be up to 32 octets of binary data. - // Note that when mode one is enabled there will be no delivery tag. - DeliveryTag []byte - - // The header section carries standard delivery details about the transfer - // of a message through the AMQP network. - Header *MessageHeader - // If the header section is omitted the receiver MUST assume the appropriate - // default values (or the meaning implied by no value being set) for the - // fields within the header unless other target or node specific defaults - // have otherwise been set. - - // The delivery-annotations section is used for delivery-specific non-standard - // properties at the head of the message. Delivery annotations convey information - // from the sending peer to the receiving peer. - DeliveryAnnotations Annotations - // If the recipient does not understand the annotation it cannot be acted upon - // and its effects (such as any implied propagation) cannot be acted upon. - // Annotations might be specific to one implementation, or common to multiple - // implementations. The capabilities negotiated on link attach and on the source - // and target SHOULD be used to establish which annotations a peer supports. A - // registry of defined annotations and their meanings is maintained [AMQPDELANN]. - // The symbolic key "rejected" is reserved for the use of communicating error - // information regarding rejected messages. Any values associated with the - // "rejected" key MUST be of type error. - // - // If the delivery-annotations section is omitted, it is equivalent to a - // delivery-annotations section containing an empty map of annotations. - - // The message-annotations section is used for properties of the message which - // are aimed at the infrastructure. - Annotations Annotations - // The message-annotations section is used for properties of the message which - // are aimed at the infrastructure and SHOULD be propagated across every - // delivery step. Message annotations convey information about the message. - // Intermediaries MUST propagate the annotations unless the annotations are - // explicitly augmented or modified (e.g., by the use of the modified outcome). - // - // The capabilities negotiated on link attach and on the source and target can - // be used to establish which annotations a peer understands; however, in a - // network of AMQP intermediaries it might not be possible to know if every - // intermediary will understand the annotation. Note that for some annotations - // it might not be necessary for the intermediary to understand their purpose, - // i.e., they could be used purely as an attribute which can be filtered on. - // - // A registry of defined annotations and their meanings is maintained [AMQPMESSANN]. - // - // If the message-annotations section is omitted, it is equivalent to a - // message-annotations section containing an empty map of annotations. - - // The properties section is used for a defined set of standard properties of - // the message. - Properties *MessageProperties - // The properties section is part of the bare message; therefore, - // if retransmitted by an intermediary, it MUST remain unaltered. - - // The application-properties section is a part of the bare message used for - // structured application data. Intermediaries can use the data within this - // structure for the purposes of filtering or routing. - ApplicationProperties map[string]any - // The keys of this map are restricted to be of type string (which excludes - // the possibility of a null key) and the values are restricted to be of - // simple types only, that is, excluding map, list, and array types. - - // Data payloads. - // A data section contains opaque binary data. - Data [][]byte - - // Value payload. - // An amqp-value section contains a single AMQP value. - Value any - - // Sequence will contain AMQP sequence sections from the body of the message. - // An amqp-sequence section contains an AMQP sequence. - Sequence [][]any - - // The footer section is used for details about the message or delivery which - // can only be calculated or evaluated once the whole bare message has been - // constructed or seen (for example message hashes, HMACs, signatures and - // encryption details). - Footer Annotations - - deliveryID uint32 // used when sending disposition - settled bool // whether transfer was settled by sender -} - -// NewMessage returns a *Message with data as the payload. -// -// This constructor is intended as a helper for basic Messages with a -// single data payload. It is valid to construct a Message directly for -// more complex usages. -func NewMessage(data []byte) *Message { - return &Message{ - Data: [][]byte{data}, - } -} - -// GetData returns the first []byte from the Data field -// or nil if Data is empty. -func (m *Message) GetData() []byte { - if len(m.Data) < 1 { - return nil - } - return m.Data[0] -} - -// MarshalBinary encodes the message into binary form. -func (m *Message) MarshalBinary() ([]byte, error) { - buf := &buffer.Buffer{} - err := m.Marshal(buf) - return buf.Detach(), err -} - -func (m *Message) Marshal(wr *buffer.Buffer) error { - if m.Header != nil { - err := m.Header.Marshal(wr) - if err != nil { - return err - } - } - - if m.DeliveryAnnotations != nil { - encoding.WriteDescriptor(wr, encoding.TypeCodeDeliveryAnnotations) - err := encoding.Marshal(wr, m.DeliveryAnnotations) - if err != nil { - return err - } - } - - if m.Annotations != nil { - encoding.WriteDescriptor(wr, encoding.TypeCodeMessageAnnotations) - err := encoding.Marshal(wr, m.Annotations) - if err != nil { - return err - } - } - - if m.Properties != nil { - err := encoding.Marshal(wr, m.Properties) - if err != nil { - return err - } - } - - if m.ApplicationProperties != nil { - encoding.WriteDescriptor(wr, encoding.TypeCodeApplicationProperties) - err := encoding.Marshal(wr, m.ApplicationProperties) - if err != nil { - return err - } - } - - for _, data := range m.Data { - encoding.WriteDescriptor(wr, encoding.TypeCodeApplicationData) - err := encoding.WriteBinary(wr, data) - if err != nil { - return err - } - } - - if m.Value != nil { - encoding.WriteDescriptor(wr, encoding.TypeCodeAMQPValue) - err := encoding.Marshal(wr, m.Value) - if err != nil { - return err - } - } - - if m.Sequence != nil { - // the body can basically be one of three different types (value, data or sequence). - // When it's sequence it's actually _several_ sequence sections, one for each sub-array. - for _, v := range m.Sequence { - encoding.WriteDescriptor(wr, encoding.TypeCodeAMQPSequence) - err := encoding.Marshal(wr, v) - if err != nil { - return err - } - } - } - - if m.Footer != nil { - encoding.WriteDescriptor(wr, encoding.TypeCodeFooter) - err := encoding.Marshal(wr, m.Footer) - if err != nil { - return err - } - } - - return nil -} - -// UnmarshalBinary decodes the message from binary form. -func (m *Message) UnmarshalBinary(data []byte) error { - buf := buffer.New(data) - return m.Unmarshal(buf) -} - -func (m *Message) Unmarshal(r *buffer.Buffer) error { - // loop, decoding sections until bytes have been consumed - for r.Len() > 0 { - // determine type - type_, headerLength, err := encoding.PeekMessageType(r.Bytes()) - if err != nil { - return err - } - - var ( - section any - // section header is read from r before - // unmarshaling section is set to true - discardHeader = true - ) - switch encoding.AMQPType(type_) { - - case encoding.TypeCodeMessageHeader: - discardHeader = false - section = &m.Header - - case encoding.TypeCodeDeliveryAnnotations: - section = &m.DeliveryAnnotations - - case encoding.TypeCodeMessageAnnotations: - section = &m.Annotations - - case encoding.TypeCodeMessageProperties: - discardHeader = false - section = &m.Properties - - case encoding.TypeCodeApplicationProperties: - section = &m.ApplicationProperties - - case encoding.TypeCodeApplicationData: - r.Skip(int(headerLength)) - - var data []byte - err = encoding.Unmarshal(r, &data) - if err != nil { - return err - } - - m.Data = append(m.Data, data) - continue - - case encoding.TypeCodeAMQPSequence: - r.Skip(int(headerLength)) - - var data []any - err = encoding.Unmarshal(r, &data) - if err != nil { - return err - } - - m.Sequence = append(m.Sequence, data) - continue - - case encoding.TypeCodeFooter: - section = &m.Footer - - case encoding.TypeCodeAMQPValue: - section = &m.Value - - default: - return fmt.Errorf("unknown message section %#02x", type_) - } - - if discardHeader { - r.Skip(int(headerLength)) - } - - err = encoding.Unmarshal(r, section) - if err != nil { - return err - } - } - return nil -} - -/* - - - - - - - - -*/ - -// MessageHeader carries standard delivery details about the transfer -// of a message. -type MessageHeader struct { - Durable bool - Priority uint8 - TTL time.Duration // from milliseconds - FirstAcquirer bool - DeliveryCount uint32 -} - -func (h *MessageHeader) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeMessageHeader, []encoding.MarshalField{ - {Value: &h.Durable, Omit: !h.Durable}, - {Value: &h.Priority, Omit: h.Priority == 4}, - {Value: (*encoding.Milliseconds)(&h.TTL), Omit: h.TTL == 0}, - {Value: &h.FirstAcquirer, Omit: !h.FirstAcquirer}, - {Value: &h.DeliveryCount, Omit: h.DeliveryCount == 0}, - }) -} - -func (h *MessageHeader) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeMessageHeader, []encoding.UnmarshalField{ - {Field: &h.Durable}, - {Field: &h.Priority, HandleNull: func() error { h.Priority = 4; return nil }}, - {Field: (*encoding.Milliseconds)(&h.TTL)}, - {Field: &h.FirstAcquirer}, - {Field: &h.DeliveryCount}, - }...) -} - -/* - - - - - - - - - - - - - - - - -*/ - -// MessageProperties is the defined set of properties for AMQP messages. -type MessageProperties struct { - // Message-id, if set, uniquely identifies a message within the message system. - // The message producer is usually responsible for setting the message-id in - // such a way that it is assured to be globally unique. A broker MAY discard a - // message as a duplicate if the value of the message-id matches that of a - // previously received message sent to the same node. - // - // The value is restricted to the following types - // - uint64, UUID, []byte, or string - MessageID any - - // The identity of the user responsible for producing the message. - // The client sets this value, and it MAY be authenticated by intermediaries. - UserID []byte - - // The to field identifies the node that is the intended destination of the message. - // On any given transfer this might not be the node at the receiving end of the link. - To *string - - // A common field for summary information about the message content and purpose. - Subject *string - - // The address of the node to send replies to. - ReplyTo *string - - // This is a client-specific id that can be used to mark or identify messages - // between clients. - // - // The value is restricted to the following types - // - uint64, UUID, []byte, or string - CorrelationID any - - // The RFC-2046 [RFC2046] MIME type for the message's application-data section - // (body). As per RFC-2046 [RFC2046] this can contain a charset parameter defining - // the character encoding used: e.g., 'text/plain; charset="utf-8"'. - // - // For clarity, as per section 7.2.1 of RFC-2616 [RFC2616], where the content type - // is unknown the content-type SHOULD NOT be set. This allows the recipient the - // opportunity to determine the actual type. Where the section is known to be truly - // opaque binary data, the content-type SHOULD be set to application/octet-stream. - // - // When using an application-data section with a section code other than data, - // content-type SHOULD NOT be set. - ContentType *string - - // The content-encoding property is used as a modifier to the content-type. - // When present, its value indicates what additional content encodings have been - // applied to the application-data, and thus what decoding mechanisms need to be - // applied in order to obtain the media-type referenced by the content-type header - // field. - // - // Content-encoding is primarily used to allow a document to be compressed without - // losing the identity of its underlying content type. - // - // Content-encodings are to be interpreted as per section 3.5 of RFC 2616 [RFC2616]. - // Valid content-encodings are registered at IANA [IANAHTTPPARAMS]. - // - // The content-encoding MUST NOT be set when the application-data section is other - // than data. The binary representation of all other application-data section types - // is defined completely in terms of the AMQP type system. - // - // Implementations MUST NOT use the identity encoding. Instead, implementations - // SHOULD NOT set this property. Implementations SHOULD NOT use the compress encoding, - // except as to remain compatible with messages originally sent with other protocols, - // e.g. HTTP or SMTP. - // - // Implementations SHOULD NOT specify multiple content-encoding values except as to - // be compatible with messages originally sent with other protocols, e.g. HTTP or SMTP. - ContentEncoding *string - - // An absolute time when this message is considered to be expired. - AbsoluteExpiryTime *time.Time - - // An absolute time when this message was created. - CreationTime *time.Time - - // Identifies the group the message belongs to. - GroupID *string - - // The relative position of this message within its group. - // - // The value is defined as a RFC-1982 sequence number - GroupSequence *uint32 - - // This is a client-specific id that is used so that client can send replies to this - // message to a specific group. - ReplyToGroupID *string -} - -func (p *MessageProperties) Marshal(wr *buffer.Buffer) error { - return encoding.MarshalComposite(wr, encoding.TypeCodeMessageProperties, []encoding.MarshalField{ - {Value: p.MessageID, Omit: p.MessageID == nil}, - {Value: &p.UserID, Omit: len(p.UserID) == 0}, - {Value: p.To, Omit: p.To == nil}, - {Value: p.Subject, Omit: p.Subject == nil}, - {Value: p.ReplyTo, Omit: p.ReplyTo == nil}, - {Value: p.CorrelationID, Omit: p.CorrelationID == nil}, - {Value: (*encoding.Symbol)(p.ContentType), Omit: p.ContentType == nil}, - {Value: (*encoding.Symbol)(p.ContentEncoding), Omit: p.ContentEncoding == nil}, - {Value: p.AbsoluteExpiryTime, Omit: p.AbsoluteExpiryTime == nil}, - {Value: p.CreationTime, Omit: p.CreationTime == nil}, - {Value: p.GroupID, Omit: p.GroupID == nil}, - {Value: p.GroupSequence, Omit: p.GroupSequence == nil}, - {Value: p.ReplyToGroupID, Omit: p.ReplyToGroupID == nil}, - }) -} - -func (p *MessageProperties) Unmarshal(r *buffer.Buffer) error { - return encoding.UnmarshalComposite(r, encoding.TypeCodeMessageProperties, []encoding.UnmarshalField{ - {Field: &p.MessageID}, - {Field: &p.UserID}, - {Field: &p.To}, - {Field: &p.Subject}, - {Field: &p.ReplyTo}, - {Field: &p.CorrelationID}, - {Field: &p.ContentType}, - {Field: &p.ContentEncoding}, - {Field: &p.AbsoluteExpiryTime}, - {Field: &p.CreationTime}, - {Field: &p.GroupID}, - {Field: &p.GroupSequence}, - {Field: &p.ReplyToGroupID}, - }...) -} - -// Annotations keys must be of type string, int, or int64. -// -// String keys are encoded as AMQP Symbols. -type Annotations = encoding.Annotations - -// UUID is a 128 bit identifier as defined in RFC 4122. -type UUID = encoding.UUID diff --git a/sdk/messaging/azservicebus/internal/go-amqp/receiver.go b/sdk/messaging/azservicebus/internal/go-amqp/receiver.go deleted file mode 100644 index 8bcd7b293ca4..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/receiver.go +++ /dev/null @@ -1,897 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "bytes" - "context" - "errors" - "fmt" - "sync" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/buffer" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/debug" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/frames" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/queue" -) - -// Default link options -const ( - defaultLinkCredit = 1 -) - -// Receiver receives messages on a single AMQP link. -type Receiver struct { - l link - - // message receiving - receiverReady chan struct{} // receiver sends on this when mux is paused to indicate it can handle more messages - messagesQ *queue.Holder[Message] // used to send completed messages to receiver - txDisposition chan frameBodyEnvelope // used to funnel disposition frames through the mux - - unsettledMessages map[string]struct{} // used to keep track of messages being handled downstream - unsettledMessagesLock sync.RWMutex // lock to protect concurrent access to unsettledMessages - msgBuf buffer.Buffer // buffered bytes for current message - more bool // if true, buf contains a partial message - msg Message // current message being decoded - - settlementCount uint32 // the count of settled messages - settlementCountMu sync.Mutex // must be held when accessing settlementCount - - autoSendFlow bool // automatically send flow frames as credit becomes available - inFlight inFlight // used to track message disposition when rcv-settle-mode == second - creditor creditor // manages credits via calls to IssueCredit/DrainCredit -} - -// IssueCredit adds credits to be requested in the next flow request. -// Attempting to issue more credit than the receiver's max credit as -// specified in ReceiverOptions.MaxCredit will result in an error. -func (r *Receiver) IssueCredit(credit uint32) error { - if r.autoSendFlow { - return errors.New("issueCredit can only be used with receiver links using manual credit management") - } - - if err := r.creditor.IssueCredit(credit); err != nil { - return err - } - - // cause mux() to check our flow conditions. - select { - case r.receiverReady <- struct{}{}: - default: - } - - return nil -} - -// Prefetched returns the next message that is stored in the Receiver's -// prefetch cache. It does NOT wait for the remote sender to send messages -// and returns immediately if the prefetch cache is empty. To receive from the -// prefetch and wait for messages from the remote Sender use `Receive`. -// -// Once a message is received, and if the sender is configured in any mode other -// than SenderSettleModeSettled, you *must* take an action on the message by calling -// one of the following: AcceptMessage, RejectMessage, ReleaseMessage, ModifyMessage. -func (r *Receiver) Prefetched() *Message { - select { - case r.receiverReady <- struct{}{}: - default: - } - - // non-blocking receive to ensure buffered messages are - // delivered regardless of whether the link has been closed. - q := r.messagesQ.Acquire() - msg := q.Dequeue() - r.messagesQ.Release(q) - - if msg == nil { - return nil - } - - debug.Log(3, "RX (Receiver %p): prefetched delivery ID %d", r, msg.deliveryID) - - if msg.settled { - r.onSettlement(1) - } - - return msg -} - -// ReceiveOptions contains any optional values for the Receiver.Receive method. -type ReceiveOptions struct { - // for future expansion -} - -// Receive returns the next message from the sender. -// Blocks until a message is received, ctx completes, or an error occurs. -// -// Once a message is received, and if the sender is configured in any mode other -// than SenderSettleModeSettled, you *must* take an action on the message by calling -// one of the following: AcceptMessage, RejectMessage, ReleaseMessage, ModifyMessage. -func (r *Receiver) Receive(ctx context.Context, opts *ReceiveOptions) (*Message, error) { - if msg := r.Prefetched(); msg != nil { - return msg, nil - } - - // wait for the next message - select { - case q := <-r.messagesQ.Wait(): - msg := q.Dequeue() - debug.Assert(msg != nil) - debug.Log(3, "RX (Receiver %p): received delivery ID %d", r, msg.deliveryID) - r.messagesQ.Release(q) - if msg.settled { - r.onSettlement(1) - } - return msg, nil - case <-r.l.done: - // if the link receives messages and is then closed between the above call to r.Prefetched() - // and this select statement, the order of selecting r.messages and r.l.done is undefined. - // however, once r.l.done is closed the link cannot receive any more messages. so be sure to - // drain any that might have trickled in within this window. - if msg := r.Prefetched(); msg != nil { - return msg, nil - } - return nil, r.l.doneErr - case <-ctx.Done(): - return nil, ctx.Err() - } -} - -// Accept notifies the server that the message has been accepted and does not require redelivery. -// - ctx controls waiting for the peer to acknowledge the disposition -// - msg is the message to accept -// -// If the context's deadline expires or is cancelled before the operation -// completes, the message's disposition is in an unknown state. -func (r *Receiver) AcceptMessage(ctx context.Context, msg *Message) error { - return r.messageDisposition(ctx, msg, &encoding.StateAccepted{}) -} - -// Reject notifies the server that the message is invalid. -// - ctx controls waiting for the peer to acknowledge the disposition -// - msg is the message to reject -// - e is an optional rejection error -// -// If the context's deadline expires or is cancelled before the operation -// completes, the message's disposition is in an unknown state. -func (r *Receiver) RejectMessage(ctx context.Context, msg *Message, e *Error) error { - return r.messageDisposition(ctx, msg, &encoding.StateRejected{Error: e}) -} - -// Release releases the message back to the server. The message may be redelivered to this or another consumer. -// - ctx controls waiting for the peer to acknowledge the disposition -// - msg is the message to release -// -// If the context's deadline expires or is cancelled before the operation -// completes, the message's disposition is in an unknown state. -func (r *Receiver) ReleaseMessage(ctx context.Context, msg *Message) error { - return r.messageDisposition(ctx, msg, &encoding.StateReleased{}) -} - -// Modify notifies the server that the message was not acted upon and should be modifed. -// - ctx controls waiting for the peer to acknowledge the disposition -// - msg is the message to modify -// - options contains the optional settings to modify -// -// If the context's deadline expires or is cancelled before the operation -// completes, the message's disposition is in an unknown state. -func (r *Receiver) ModifyMessage(ctx context.Context, msg *Message, options *ModifyMessageOptions) error { - if options == nil { - options = &ModifyMessageOptions{} - } - return r.messageDisposition(ctx, - msg, &encoding.StateModified{ - DeliveryFailed: options.DeliveryFailed, - UndeliverableHere: options.UndeliverableHere, - MessageAnnotations: options.Annotations, - }) -} - -// ModifyMessageOptions contains the optional parameters to ModifyMessage. -type ModifyMessageOptions struct { - // DeliveryFailed indicates that the server must consider this an - // unsuccessful delivery attempt and increment the delivery count. - DeliveryFailed bool - - // UndeliverableHere indicates that the server must not redeliver - // the message to this link. - UndeliverableHere bool - - // Annotations is an optional annotation map to be merged - // with the existing message annotations, overwriting existing keys - // if necessary. - Annotations Annotations -} - -// Address returns the link's address. -func (r *Receiver) Address() string { - if r.l.source == nil { - return "" - } - return r.l.source.Address -} - -// LinkName returns associated link name or an empty string if link is not defined. -func (r *Receiver) LinkName() string { - return r.l.key.name -} - -// LinkSourceFilterValue retrieves the specified link source filter value or nil if it doesn't exist. -func (r *Receiver) LinkSourceFilterValue(name string) any { - if r.l.source == nil { - return nil - } - filter, ok := r.l.source.Filter[encoding.Symbol(name)] - if !ok { - return nil - } - return filter.Value -} - -// Close closes the Receiver and AMQP link. -// - ctx controls waiting for the peer to acknowledge the close -// -// If the context's deadline expires or is cancelled before the operation -// completes, an error is returned. However, the operation will continue to -// execute in the background. Subsequent calls will return a *LinkError -// that contains the context's error message. -func (r *Receiver) Close(ctx context.Context) error { - return r.l.closeLink(ctx) -} - -// sendDisposition sends a disposition frame to the peer -func (r *Receiver) sendDisposition(ctx context.Context, first uint32, last *uint32, state encoding.DeliveryState) error { - fr := &frames.PerformDisposition{ - Role: encoding.RoleReceiver, - First: first, - Last: last, - Settled: r.l.receiverSettleMode == nil || *r.l.receiverSettleMode == ReceiverSettleModeFirst, - State: state, - } - - sent := make(chan error, 1) - select { - case r.txDisposition <- frameBodyEnvelope{Ctx: ctx, FrameBody: fr, Sent: sent}: - debug.Log(2, "TX (Receiver %p): mux txDisposition %s", r, fr) - case <-r.l.done: - return r.l.doneErr - } - - select { - case err := <-sent: - return err - case <-r.l.done: - return r.l.doneErr - } -} - -func (r *Receiver) messageDisposition(ctx context.Context, msg *Message, state encoding.DeliveryState) error { - if msg.settled { - return nil - } - - // NOTE: we MUST add to the in-flight map before sending the disposition. if not, it's possible - // to receive the ack'ing disposition frame *before* the in-flight map has been updated which - // will cause the below <-wait to never trigger. - - var wait chan error - if r.l.receiverSettleMode != nil && *r.l.receiverSettleMode == ReceiverSettleModeSecond { - debug.Log(3, "TX (Receiver %p): delivery ID %d is in flight", r, msg.deliveryID) - wait = r.inFlight.add(msg) - } - - if err := r.sendDisposition(ctx, msg.deliveryID, nil, state); err != nil { - return err - } - - if wait == nil { - // mode first, there will be no settlement ack - r.deleteUnsettled(msg) - r.onSettlement(1) - return nil - } - - select { - case err := <-wait: - // err has three possibilities - // - nil, meaning the peer acknowledged the settlement - // - an *Error, meaning the peer rejected the message with a provided error - // - a non-AMQP error. this comes from calls to inFlight.clear() during mux unwind. - // only for the first two cases is the message considered settled - - if amqpErr := (&Error{}); err == nil || errors.As(err, &amqpErr) { - debug.Log(3, "RX (Receiver %p): delivery ID %d has been settled", r, msg.deliveryID) - // we've received confirmation of disposition - return err - } - - debug.Log(3, "RX (Receiver %p): error settling delivery ID %d: %v", r, msg.deliveryID, err) - return err - - case <-ctx.Done(): - // didn't receive the ack in the time allotted, leave message as unsettled - // TODO: if the ack arrives later, we need to remove the message from the unsettled map and reclaim the credit - return ctx.Err() - } -} - -// onSettlement is to be called after message settlement. -// - count is the number of messages that were settled -func (r *Receiver) onSettlement(count uint32) { - if !r.autoSendFlow { - return - } - - r.settlementCountMu.Lock() - r.settlementCount += count - r.settlementCountMu.Unlock() - - select { - case r.receiverReady <- struct{}{}: - // woke up - default: - // wake pending - } -} - -func (r *Receiver) addUnsettled(msg *Message) { - r.unsettledMessagesLock.Lock() - r.unsettledMessages[string(msg.DeliveryTag)] = struct{}{} - r.unsettledMessagesLock.Unlock() -} - -func (r *Receiver) deleteUnsettled(msg *Message) { - r.unsettledMessagesLock.Lock() - delete(r.unsettledMessages, string(msg.DeliveryTag)) - r.unsettledMessagesLock.Unlock() -} - -func (r *Receiver) countUnsettled() int { - r.unsettledMessagesLock.RLock() - count := len(r.unsettledMessages) - r.unsettledMessagesLock.RUnlock() - return count -} - -func newReceiver(source string, session *Session, opts *ReceiverOptions) (*Receiver, error) { - l := newLink(session, encoding.RoleReceiver) - l.source = &frames.Source{Address: source} - l.target = new(frames.Target) - l.linkCredit = defaultLinkCredit - r := &Receiver{ - l: l, - autoSendFlow: true, - receiverReady: make(chan struct{}, 1), - txDisposition: make(chan frameBodyEnvelope), - } - - r.messagesQ = queue.NewHolder(queue.New[Message](int(session.incomingWindow))) - - if opts == nil { - return r, nil - } - - for _, v := range opts.Capabilities { - r.l.target.Capabilities = append(r.l.target.Capabilities, encoding.Symbol(v)) - } - if opts.Credit > 0 { - r.l.linkCredit = uint32(opts.Credit) - } else if opts.Credit < 0 { - r.l.linkCredit = 0 - r.autoSendFlow = false - } - if opts.Durability > DurabilityUnsettledState { - return nil, fmt.Errorf("invalid Durability %d", opts.Durability) - } - r.l.target.Durable = opts.Durability - if opts.DynamicAddress { - r.l.source.Address = "" - r.l.dynamicAddr = opts.DynamicAddress - } - if opts.ExpiryPolicy != "" { - if err := encoding.ValidateExpiryPolicy(opts.ExpiryPolicy); err != nil { - return nil, err - } - r.l.target.ExpiryPolicy = opts.ExpiryPolicy - } - r.l.target.Timeout = opts.ExpiryTimeout - if opts.Filters != nil { - r.l.source.Filter = make(encoding.Filter) - for _, f := range opts.Filters { - f(r.l.source.Filter) - } - } - if opts.MaxMessageSize > 0 { - r.l.maxMessageSize = opts.MaxMessageSize - } - if opts.Name != "" { - r.l.key.name = opts.Name - } - if opts.Properties != nil { - r.l.properties = make(map[encoding.Symbol]any) - for k, v := range opts.Properties { - if k == "" { - return nil, errors.New("link property key must not be empty") - } - r.l.properties[encoding.Symbol(k)] = v - } - } - if opts.RequestedSenderSettleMode != nil { - if rsm := *opts.RequestedSenderSettleMode; rsm > SenderSettleModeMixed { - return nil, fmt.Errorf("invalid RequestedSenderSettleMode %d", rsm) - } - r.l.senderSettleMode = opts.RequestedSenderSettleMode - } - if opts.SettlementMode != nil { - if rsm := *opts.SettlementMode; rsm > ReceiverSettleModeSecond { - return nil, fmt.Errorf("invalid SettlementMode %d", rsm) - } - r.l.receiverSettleMode = opts.SettlementMode - } - r.l.target.Address = opts.TargetAddress - for _, v := range opts.SourceCapabilities { - r.l.source.Capabilities = append(r.l.source.Capabilities, encoding.Symbol(v)) - } - if opts.SourceDurability != DurabilityNone { - r.l.source.Durable = opts.SourceDurability - } - if opts.SourceExpiryPolicy != ExpiryPolicySessionEnd { - r.l.source.ExpiryPolicy = opts.SourceExpiryPolicy - } - if opts.SourceExpiryTimeout != 0 { - r.l.source.Timeout = opts.SourceExpiryTimeout - } - return r, nil -} - -// attach sends the Attach performative to establish the link with its parent session. -// this is automatically called by the new*Link constructors. -func (r *Receiver) attach(ctx context.Context) error { - if err := r.l.attach(ctx, func(pa *frames.PerformAttach) { - pa.Role = encoding.RoleReceiver - if pa.Source == nil { - pa.Source = new(frames.Source) - } - pa.Source.Dynamic = r.l.dynamicAddr - }, func(pa *frames.PerformAttach) { - if r.l.source == nil { - r.l.source = new(frames.Source) - } - // if dynamic address requested, copy assigned name to address - if r.l.dynamicAddr && pa.Source != nil { - r.l.source.Address = pa.Source.Address - } - // deliveryCount is a sequence number, must initialize to sender's initial sequence number - r.l.deliveryCount = pa.InitialDeliveryCount - r.unsettledMessages = map[string]struct{}{} - // copy the received filter values - if pa.Source != nil { - r.l.source.Filter = pa.Source.Filter - } - }); err != nil { - return err - } - - return nil -} - -func nop() {} - -type receiverTestHooks struct { - MuxStart func() - MuxSelect func() -} - -func (r *Receiver) mux(hooks receiverTestHooks) { - if hooks.MuxSelect == nil { - hooks.MuxSelect = nop - } - if hooks.MuxStart == nil { - hooks.MuxStart = nop - } - - defer func() { - // unblock any in flight message dispositions - r.inFlight.clear(r.l.doneErr) - - if !r.autoSendFlow { - // unblock any pending drain requests - r.creditor.EndDrain() - } - - close(r.l.done) - }() - - hooks.MuxStart() - - if r.autoSendFlow { - r.l.doneErr = r.muxFlow(r.l.linkCredit, false) - } - - for { - msgLen := r.messagesQ.Len() - - r.settlementCountMu.Lock() - // counter that accumulates the settled delivery count. - // once the threshold has been reached, the counter is - // reset and a flow frame is sent. - previousSettlementCount := r.settlementCount - if previousSettlementCount >= r.l.linkCredit { - r.settlementCount = 0 - } - r.settlementCountMu.Unlock() - - // once we have pending credit equal to or greater than our available credit, reclaim it. - // we do this instead of settlementCount > 0 to prevent flow frames from being too chatty. - // NOTE: we compare the settlementCount against the current link credit instead of some - // fixed threshold to ensure credit is reclaimed in cases where the number of unsettled - // messages remains high for whatever reason. - if r.autoSendFlow && previousSettlementCount > 0 && previousSettlementCount >= r.l.linkCredit { - debug.Log(1, "RX (Receiver %p) (auto): source: %q, inflight: %d, linkCredit: %d, deliveryCount: %d, messages: %d, unsettled: %d, settlementCount: %d, settleMode: %s", - r, r.l.source.Address, r.inFlight.len(), r.l.linkCredit, r.l.deliveryCount, msgLen, r.countUnsettled(), previousSettlementCount, r.l.receiverSettleMode.String()) - r.l.doneErr = r.creditor.IssueCredit(previousSettlementCount) - } else if r.l.linkCredit == 0 { - debug.Log(1, "RX (Receiver %p) (pause): source: %q, inflight: %d, linkCredit: %d, deliveryCount: %d, messages: %d, unsettled: %d, settlementCount: %d, settleMode: %s", - r, r.l.source.Address, r.inFlight.len(), r.l.linkCredit, r.l.deliveryCount, msgLen, r.countUnsettled(), previousSettlementCount, r.l.receiverSettleMode.String()) - } - - if r.l.doneErr != nil { - return - } - - drain, credits := r.creditor.FlowBits(r.l.linkCredit) - if drain || credits > 0 { - debug.Log(1, "RX (Receiver %p) (flow): source: %q, inflight: %d, curLinkCredit: %d, newLinkCredit: %d, drain: %v, deliveryCount: %d, messages: %d, unsettled: %d, settlementCount: %d, settleMode: %s", - r, r.l.source.Address, r.inFlight.len(), r.l.linkCredit, credits, drain, r.l.deliveryCount, msgLen, r.countUnsettled(), previousSettlementCount, r.l.receiverSettleMode.String()) - - // send a flow frame. - r.l.doneErr = r.muxFlow(credits, drain) - } - - if r.l.doneErr != nil { - return - } - - txDisposition := r.txDisposition - closed := r.l.close - if r.l.closeInProgress { - // swap out channel so it no longer triggers - closed = nil - - // disable sending of disposition frames once closing is in progress. - // this is to prevent races between mux shutdown and clearing of - // any in-flight dispositions. - txDisposition = nil - } - - hooks.MuxSelect() - - select { - case q := <-r.l.rxQ.Wait(): - // populated queue - fr := *q.Dequeue() - r.l.rxQ.Release(q) - - // if muxHandleFrame returns an error it means the mux must terminate. - // note that in the case of a client-side close due to an error, nil - // is returned in order to keep the mux running to ack the detach frame. - if err := r.muxHandleFrame(fr); err != nil { - r.l.doneErr = err - return - } - - case env := <-txDisposition: - r.l.txFrame(env.Ctx, env.FrameBody, env.Sent) - - case <-r.receiverReady: - continue - - case <-closed: - if r.l.closeInProgress { - // a client-side close due to protocol error is in progress - continue - } - - // receiver is being closed by the client - r.l.closeInProgress = true - fr := &frames.PerformDetach{ - Handle: r.l.handle, - Closed: true, - } - r.l.txFrame(context.Background(), fr, nil) - - case <-r.l.session.done: - r.l.doneErr = r.l.session.doneErr - return - } - } -} - -// muxFlow sends tr to the session mux. -// l.linkCredit will also be updated to `linkCredit` -func (r *Receiver) muxFlow(linkCredit uint32, drain bool) error { - var ( - deliveryCount = r.l.deliveryCount - ) - - fr := &frames.PerformFlow{ - Handle: &r.l.handle, - DeliveryCount: &deliveryCount, - LinkCredit: &linkCredit, // max number of messages, - Drain: drain, - } - - // Update credit. This must happen before entering loop below - // because incoming messages handled while waiting to transmit - // flow increment deliveryCount. This causes the credit to become - // out of sync with the server. - - if !drain { - // if we're draining we don't want to touch our internal credit - we're not changing it so any issued credits - // are still valid until drain completes, at which point they will be naturally zeroed. - r.l.linkCredit = linkCredit - } - - select { - case r.l.session.tx <- frameBodyEnvelope{Ctx: context.Background(), FrameBody: fr}: - debug.Log(2, "TX (Receiver %p): mux frame to Session (%p): %d, %s", r, r.l.session, r.l.session.channel, fr) - return nil - case <-r.l.close: - return nil - case <-r.l.session.done: - return r.l.session.doneErr - } -} - -// muxHandleFrame processes fr based on type. -func (r *Receiver) muxHandleFrame(fr frames.FrameBody) error { - debug.Log(2, "RX (Receiver %p): %s", r, fr) - switch fr := fr.(type) { - // message frame - case *frames.PerformTransfer: - r.muxReceive(*fr) - - // flow control frame - case *frames.PerformFlow: - if !fr.Echo { - // if the 'drain' flag has been set in the frame sent to the _receiver_ then - // we signal whomever is waiting (the service has seen and acknowledged our drain) - if fr.Drain && !r.autoSendFlow { - r.l.linkCredit = 0 // we have no active credits at this point. - r.creditor.EndDrain() - } - return nil - } - - var ( - // copy because sent by pointer below; prevent race - linkCredit = r.l.linkCredit - deliveryCount = r.l.deliveryCount - ) - - // send flow - resp := &frames.PerformFlow{ - Handle: &r.l.handle, - DeliveryCount: &deliveryCount, - LinkCredit: &linkCredit, // max number of messages - } - - select { - case r.l.session.tx <- frameBodyEnvelope{Ctx: context.Background(), FrameBody: resp}: - debug.Log(2, "TX (Receiver %p): mux frame to Session (%p): %d, %s", r, r.l.session, r.l.session.channel, resp) - case <-r.l.close: - return nil - case <-r.l.session.done: - return r.l.session.doneErr - } - - case *frames.PerformDisposition: - // Unblock receivers waiting for message disposition - // bubble disposition error up to the receiver - var dispositionError error - if state, ok := fr.State.(*encoding.StateRejected); ok { - // state.Error isn't required to be filled out. For instance if you dead letter a message - // you will get a rejected response that doesn't contain an error. - if state.Error != nil { - dispositionError = state.Error - } - } - // removal from the in-flight map will also remove the message from the unsettled map - count := r.inFlight.remove(fr.First, fr.Last, dispositionError, func(msg *Message) { - r.deleteUnsettled(msg) - msg.settled = true - }) - r.onSettlement(count) - - default: - return r.l.muxHandleFrame(fr) - } - - return nil -} - -func (r *Receiver) muxReceive(fr frames.PerformTransfer) { - if !r.more { - // this is the first transfer of a message, - // record the delivery ID, message format, - // and delivery Tag - if fr.DeliveryID != nil { - r.msg.deliveryID = *fr.DeliveryID - } - if fr.MessageFormat != nil { - r.msg.Format = *fr.MessageFormat - } - r.msg.DeliveryTag = fr.DeliveryTag - - // these fields are required on first transfer of a message - if fr.DeliveryID == nil { - r.l.closeWithError(ErrCondNotAllowed, "received message without a delivery-id") - return - } - if fr.MessageFormat == nil { - r.l.closeWithError(ErrCondNotAllowed, "received message without a message-format") - return - } - if fr.DeliveryTag == nil { - r.l.closeWithError(ErrCondNotAllowed, "received message without a delivery-tag") - return - } - } else { - // this is a continuation of a multipart message - // some fields may be omitted on continuation transfers, - // but if they are included they must be consistent - // with the first. - - if fr.DeliveryID != nil && *fr.DeliveryID != r.msg.deliveryID { - msg := fmt.Sprintf( - "received continuation transfer with inconsistent delivery-id: %d != %d", - *fr.DeliveryID, r.msg.deliveryID, - ) - r.l.closeWithError(ErrCondNotAllowed, msg) - return - } - if fr.MessageFormat != nil && *fr.MessageFormat != r.msg.Format { - msg := fmt.Sprintf( - "received continuation transfer with inconsistent message-format: %d != %d", - *fr.MessageFormat, r.msg.Format, - ) - r.l.closeWithError(ErrCondNotAllowed, msg) - return - } - if fr.DeliveryTag != nil && !bytes.Equal(fr.DeliveryTag, r.msg.DeliveryTag) { - msg := fmt.Sprintf( - "received continuation transfer with inconsistent delivery-tag: %q != %q", - fr.DeliveryTag, r.msg.DeliveryTag, - ) - r.l.closeWithError(ErrCondNotAllowed, msg) - return - } - } - - // discard message if it's been aborted - if fr.Aborted { - r.msgBuf.Reset() - r.msg = Message{} - r.more = false - return - } - - // ensure maxMessageSize will not be exceeded - if r.l.maxMessageSize != 0 && uint64(r.msgBuf.Len())+uint64(len(fr.Payload)) > r.l.maxMessageSize { - r.l.closeWithError(ErrCondMessageSizeExceeded, fmt.Sprintf("received message larger than max size of %d", r.l.maxMessageSize)) - return - } - - // add the payload the the buffer - r.msgBuf.Append(fr.Payload) - - // mark as settled if at least one frame is settled - r.msg.settled = r.msg.settled || fr.Settled - - // save in-progress status - r.more = fr.More - - if fr.More { - return - } - - // last frame in message - err := r.msg.Unmarshal(&r.msgBuf) - if err != nil { - r.l.closeWithError(ErrCondInternalError, err.Error()) - return - } - - // send to receiver - if !r.msg.settled { - r.addUnsettled(&r.msg) - debug.Log(3, "RX (Receiver %p): add unsettled delivery ID %d", r, r.msg.deliveryID) - } - - q := r.messagesQ.Acquire() - q.Enqueue(r.msg) - msgLen := q.Len() - r.messagesQ.Release(q) - - // reset progress - r.msgBuf.Reset() - r.msg = Message{} - - // decrement link-credit after entire message received - r.l.deliveryCount++ - r.l.linkCredit-- - debug.Log(3, "RX (Receiver %p) link %s - deliveryCount: %d, linkCredit: %d, len(messages): %d", r, r.l.key.name, r.l.deliveryCount, r.l.linkCredit, msgLen) -} - -// inFlight tracks in-flight message dispositions allowing receivers -// to block waiting for the server to respond when an appropriate -// settlement mode is configured. -type inFlight struct { - mu sync.RWMutex - m map[uint32]inFlightInfo -} - -type inFlightInfo struct { - wait chan error - msg *Message -} - -func (f *inFlight) add(msg *Message) chan error { - wait := make(chan error, 1) - - f.mu.Lock() - if f.m == nil { - f.m = make(map[uint32]inFlightInfo) - } - - f.m[msg.deliveryID] = inFlightInfo{wait: wait, msg: msg} - f.mu.Unlock() - - return wait -} - -func (f *inFlight) remove(first uint32, last *uint32, err error, handler func(*Message)) uint32 { - f.mu.Lock() - - if f.m == nil { - f.mu.Unlock() - return 0 - } - - ll := first - if last != nil { - ll = *last - } - - count := uint32(0) - for i := first; i <= ll; i++ { - info, ok := f.m[i] - if ok { - handler(info.msg) - info.wait <- err - delete(f.m, i) - count++ - } - } - - f.mu.Unlock() - return count -} - -func (f *inFlight) clear(err error) { - f.mu.Lock() - for id, info := range f.m { - info.wait <- err - delete(f.m, id) - } - f.mu.Unlock() -} - -func (f *inFlight) len() int { - f.mu.RLock() - defer f.mu.RUnlock() - return len(f.m) -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/sasl.go b/sdk/messaging/azservicebus/internal/go-amqp/sasl.go deleted file mode 100644 index c50ecdcbd83e..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/sasl.go +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "context" - "fmt" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/debug" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/frames" -) - -// SASL Mechanisms -const ( - saslMechanismPLAIN encoding.Symbol = "PLAIN" - saslMechanismANONYMOUS encoding.Symbol = "ANONYMOUS" - saslMechanismEXTERNAL encoding.Symbol = "EXTERNAL" - saslMechanismXOAUTH2 encoding.Symbol = "XOAUTH2" -) - -// SASLType represents a SASL configuration to use during authentication. -type SASLType func(c *Conn) error - -// ConnSASLPlain enables SASL PLAIN authentication for the connection. -// -// SASL PLAIN transmits credentials in plain text and should only be used -// on TLS/SSL enabled connection. -func SASLTypePlain(username, password string) SASLType { - // TODO: how widely used is hostname? should it be supported - return func(c *Conn) error { - // make handlers map if no other mechanism has - if c.saslHandlers == nil { - c.saslHandlers = make(map[encoding.Symbol]stateFunc) - } - - // add the handler the the map - c.saslHandlers[saslMechanismPLAIN] = func(ctx context.Context) (stateFunc, error) { - // send saslInit with PLAIN payload - init := &frames.SASLInit{ - Mechanism: "PLAIN", - InitialResponse: []byte("\x00" + username + "\x00" + password), - Hostname: "", - } - fr := frames.Frame{ - Type: frames.TypeSASL, - Body: init, - } - debug.Log(1, "TX (ConnSASLPlain %p): %s", c, fr) - timeout, err := c.getWriteTimeout(ctx) - if err != nil { - return nil, err - } - if err = c.writeFrame(timeout, fr); err != nil { - return nil, err - } - - // go to c.saslOutcome to handle the server response - return c.saslOutcome, nil - } - return nil - } -} - -// ConnSASLAnonymous enables SASL ANONYMOUS authentication for the connection. -func SASLTypeAnonymous() SASLType { - return func(c *Conn) error { - // make handlers map if no other mechanism has - if c.saslHandlers == nil { - c.saslHandlers = make(map[encoding.Symbol]stateFunc) - } - - // add the handler the the map - c.saslHandlers[saslMechanismANONYMOUS] = func(ctx context.Context) (stateFunc, error) { - init := &frames.SASLInit{ - Mechanism: saslMechanismANONYMOUS, - InitialResponse: []byte("anonymous"), - } - fr := frames.Frame{ - Type: frames.TypeSASL, - Body: init, - } - debug.Log(1, "TX (ConnSASLAnonymous %p): %s", c, fr) - timeout, err := c.getWriteTimeout(ctx) - if err != nil { - return nil, err - } - if err = c.writeFrame(timeout, fr); err != nil { - return nil, err - } - - // go to c.saslOutcome to handle the server response - return c.saslOutcome, nil - } - return nil - } -} - -// ConnSASLExternal enables SASL EXTERNAL authentication for the connection. -// The value for resp is dependent on the type of authentication (empty string is common for TLS). -// See https://datatracker.ietf.org/doc/html/rfc4422#appendix-A for additional info. -func SASLTypeExternal(resp string) SASLType { - return func(c *Conn) error { - // make handlers map if no other mechanism has - if c.saslHandlers == nil { - c.saslHandlers = make(map[encoding.Symbol]stateFunc) - } - - // add the handler the the map - c.saslHandlers[saslMechanismEXTERNAL] = func(ctx context.Context) (stateFunc, error) { - init := &frames.SASLInit{ - Mechanism: saslMechanismEXTERNAL, - InitialResponse: []byte(resp), - } - fr := frames.Frame{ - Type: frames.TypeSASL, - Body: init, - } - debug.Log(1, "TX (ConnSASLExternal %p): %s", c, fr) - timeout, err := c.getWriteTimeout(ctx) - if err != nil { - return nil, err - } - if err = c.writeFrame(timeout, fr); err != nil { - return nil, err - } - - // go to c.saslOutcome to handle the server response - return c.saslOutcome, nil - } - return nil - } -} - -// ConnSASLXOAUTH2 enables SASL XOAUTH2 authentication for the connection. -// -// The saslMaxFrameSizeOverride parameter allows the limit that governs the maximum frame size this client will allow -// itself to generate to be raised for the sasl-init frame only. Set this when the size of the size of the SASL XOAUTH2 -// initial client response (which contains the username and bearer token) would otherwise breach the 512 byte min-max-frame-size -// (http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#definition-MIN-MAX-FRAME-SIZE). Pass -1 -// to keep the default. -// -// SASL XOAUTH2 transmits the bearer in plain text and should only be used -// on TLS/SSL enabled connection. -func SASLTypeXOAUTH2(username, bearer string, saslMaxFrameSizeOverride uint32) SASLType { - return func(c *Conn) error { - // make handlers map if no other mechanism has - if c.saslHandlers == nil { - c.saslHandlers = make(map[encoding.Symbol]stateFunc) - } - - response, err := saslXOAUTH2InitialResponse(username, bearer) - if err != nil { - return err - } - - handler := saslXOAUTH2Handler{ - conn: c, - maxFrameSizeOverride: saslMaxFrameSizeOverride, - response: response, - } - // add the handler the the map - c.saslHandlers[saslMechanismXOAUTH2] = handler.init - return nil - } -} - -type saslXOAUTH2Handler struct { - conn *Conn - maxFrameSizeOverride uint32 - response []byte - errorResponse []byte // https://developers.google.com/gmail/imap/xoauth2-protocol#error_response -} - -func (s saslXOAUTH2Handler) init(ctx context.Context) (stateFunc, error) { - originalPeerMaxFrameSize := s.conn.peerMaxFrameSize - if s.maxFrameSizeOverride > s.conn.peerMaxFrameSize { - s.conn.peerMaxFrameSize = s.maxFrameSizeOverride - } - timeout, err := s.conn.getWriteTimeout(ctx) - if err != nil { - return nil, err - } - err = s.conn.writeFrame(timeout, frames.Frame{ - Type: frames.TypeSASL, - Body: &frames.SASLInit{ - Mechanism: saslMechanismXOAUTH2, - InitialResponse: s.response, - }, - }) - s.conn.peerMaxFrameSize = originalPeerMaxFrameSize - if err != nil { - return nil, err - } - - return s.step, nil -} - -func (s saslXOAUTH2Handler) step(ctx context.Context) (stateFunc, error) { - // read challenge or outcome frame - fr, err := s.conn.readFrame() - if err != nil { - return nil, err - } - - switch v := fr.Body.(type) { - case *frames.SASLOutcome: - // check if auth succeeded - if v.Code != encoding.CodeSASLOK { - return nil, fmt.Errorf("SASL XOAUTH2 auth failed with code %#00x: %s : %s", - v.Code, v.AdditionalData, s.errorResponse) - } - - // return to c.negotiateProto - s.conn.saslComplete = true - return s.conn.negotiateProto, nil - case *frames.SASLChallenge: - if s.errorResponse == nil { - s.errorResponse = v.Challenge - - timeout, err := s.conn.getWriteTimeout(ctx) - if err != nil { - return nil, err - } - - // The SASL protocol requires clients to send an empty response to this challenge. - err = s.conn.writeFrame(timeout, frames.Frame{ - Type: frames.TypeSASL, - Body: &frames.SASLResponse{ - Response: []byte{}, - }, - }) - if err != nil { - return nil, err - } - return s.step, nil - } else { - return nil, fmt.Errorf("SASL XOAUTH2 unexpected additional error response received during "+ - "exchange. Initial error response: %s, additional response: %s", s.errorResponse, v.Challenge) - } - default: - return nil, fmt.Errorf("sasl: unexpected frame type %T", fr.Body) - } -} - -func saslXOAUTH2InitialResponse(username string, bearer string) ([]byte, error) { - if len(bearer) == 0 { - return []byte{}, fmt.Errorf("unacceptable bearer token") - } - for _, char := range bearer { - if char < '\x20' || char > '\x7E' { - return []byte{}, fmt.Errorf("unacceptable bearer token") - } - } - for _, char := range username { - if char == '\x01' { - return []byte{}, fmt.Errorf("unacceptable username") - } - } - return []byte("user=" + username + "\x01auth=Bearer " + bearer + "\x01\x01"), nil -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/sender.go b/sdk/messaging/azservicebus/internal/go-amqp/sender.go deleted file mode 100644 index afe17e7f8e1c..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/sender.go +++ /dev/null @@ -1,482 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "context" - "encoding/binary" - "errors" - "fmt" - "sync" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/buffer" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/debug" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/frames" -) - -// Sender sends messages on a single AMQP link. -type Sender struct { - l link - transfers chan transferEnvelope // sender uses to send transfer frames - - mu sync.Mutex // protects buf and nextDeliveryTag - buf buffer.Buffer - nextDeliveryTag uint64 -} - -// LinkName() is the name of the link used for this Sender. -func (s *Sender) LinkName() string { - return s.l.key.name -} - -// MaxMessageSize is the maximum size of a single message. -func (s *Sender) MaxMessageSize() uint64 { - return s.l.maxMessageSize -} - -// SendOptions contains any optional values for the Sender.Send method. -type SendOptions struct { - // Indicates the message is to be sent as settled when settlement mode is SenderSettleModeMixed. - // If the settlement mode is SenderSettleModeUnsettled and Settled is true, an error is returned. - Settled bool -} - -// Send sends a Message. -// -// Blocks until the message is sent or an error occurs. If the peer is -// configured for receiver settlement mode second, the call also blocks -// until the peer confirms message settlement. -// -// - ctx controls waiting for the message to be sent and possibly confirmed -// - msg is the message to send -// - opts contains optional values, pass nil to accept the defaults -// -// If the context's deadline expires or is cancelled before the operation -// completes, the message is in an unknown state of transmission. -// -// Send is safe for concurrent use. Since only a single message can be -// sent on a link at a time, this is most useful when settlement confirmation -// has been requested (receiver settle mode is second). In this case, -// additional messages can be sent while the current goroutine is waiting -// for the confirmation. -func (s *Sender) Send(ctx context.Context, msg *Message, opts *SendOptions) error { - // check if the link is dead. while it's safe to call s.send - // in this case, this will avoid some allocations etc. - select { - case <-s.l.done: - return s.l.doneErr - default: - // link is still active - } - done, err := s.send(ctx, msg, opts) - if err != nil { - return err - } - - // wait for transfer to be confirmed - select { - case state := <-done: - if state, ok := state.(*encoding.StateRejected); ok { - if state.Error != nil { - return state.Error - } - return errors.New("the peer rejected the message without specifying an error") - } - return nil - case <-s.l.done: - return s.l.doneErr - case <-ctx.Done(): - // TODO: if the message is not settled and we never received a disposition, how can we consider the message as sent? - return ctx.Err() - } -} - -// send is separated from Send so that the mutex unlock can be deferred without -// locking the transfer confirmation that happens in Send. -func (s *Sender) send(ctx context.Context, msg *Message, opts *SendOptions) (chan encoding.DeliveryState, error) { - const ( - maxDeliveryTagLength = 32 - maxTransferFrameHeader = 66 // determined by calcMaxTransferFrameHeader - ) - if len(msg.DeliveryTag) > maxDeliveryTagLength { - return nil, &Error{ - Condition: ErrCondMessageSizeExceeded, - Description: fmt.Sprintf("delivery tag is over the allowed %v bytes, len: %v", maxDeliveryTagLength, len(msg.DeliveryTag)), - } - } - - s.mu.Lock() - defer s.mu.Unlock() - - s.buf.Reset() - err := msg.Marshal(&s.buf) - if err != nil { - return nil, err - } - - if s.l.maxMessageSize != 0 && uint64(s.buf.Len()) > s.l.maxMessageSize { - return nil, &Error{ - Condition: ErrCondMessageSizeExceeded, - Description: fmt.Sprintf("encoded message size exceeds max of %d", s.l.maxMessageSize), - } - } - - senderSettled := senderSettleModeValue(s.l.senderSettleMode) == SenderSettleModeSettled - if opts != nil { - if opts.Settled && senderSettleModeValue(s.l.senderSettleMode) == SenderSettleModeUnsettled { - return nil, errors.New("can't send message as settled when sender settlement mode is unsettled") - } else if opts.Settled { - senderSettled = true - } - } - - var ( - maxPayloadSize = int64(s.l.session.conn.peerMaxFrameSize) - maxTransferFrameHeader - ) - - deliveryTag := msg.DeliveryTag - if len(deliveryTag) == 0 { - // use uint64 encoded as []byte as deliveryTag - deliveryTag = make([]byte, 8) - binary.BigEndian.PutUint64(deliveryTag, s.nextDeliveryTag) - s.nextDeliveryTag++ - } - - fr := frames.PerformTransfer{ - Handle: s.l.handle, - DeliveryID: &needsDeliveryID, - DeliveryTag: deliveryTag, - MessageFormat: &msg.Format, - More: s.buf.Len() > 0, - } - - for fr.More { - buf, _ := s.buf.Next(maxPayloadSize) - fr.Payload = append([]byte(nil), buf...) - fr.More = s.buf.Len() > 0 - if !fr.More { - // SSM=settled: overrides RSM; no acks. - // SSM=unsettled: sender should wait for receiver to ack - // RSM=first: receiver considers it settled immediately, but must still send ack (SSM=unsettled only) - // RSM=second: receiver sends ack and waits for return ack from sender (SSM=unsettled only) - - // mark final transfer as settled when sender mode is settled - fr.Settled = senderSettled - - // set done on last frame - fr.Done = make(chan encoding.DeliveryState, 1) - } - - // NOTE: we MUST send a copy of fr here since we modify it post send - - sent := make(chan error, 1) - select { - case s.transfers <- transferEnvelope{Ctx: ctx, Frame: fr, Sent: sent}: - // frame was sent to our mux - case <-s.l.done: - return nil, s.l.doneErr - case <-ctx.Done(): - return nil, &Error{Condition: ErrCondTransferLimitExceeded, Description: fmt.Sprintf("credit limit exceeded for sending link %s", s.l.key.name)} - } - - select { - case err := <-sent: - if err != nil { - return nil, err - } - case <-s.l.done: - return nil, s.l.doneErr - } - - // clear values that are only required on first message - fr.DeliveryID = nil - fr.DeliveryTag = nil - fr.MessageFormat = nil - } - - return fr.Done, nil -} - -// Address returns the link's address. -func (s *Sender) Address() string { - if s.l.target == nil { - return "" - } - return s.l.target.Address -} - -// Close closes the Sender and AMQP link. -// - ctx controls waiting for the peer to acknowledge the close -// -// If the context's deadline expires or is cancelled before the operation -// completes, an error is returned. However, the operation will continue to -// execute in the background. Subsequent calls will return a *LinkError -// that contains the context's error message. -func (s *Sender) Close(ctx context.Context) error { - return s.l.closeLink(ctx) -} - -// newSendingLink creates a new sending link and attaches it to the session -func newSender(target string, session *Session, opts *SenderOptions) (*Sender, error) { - l := newLink(session, encoding.RoleSender) - l.target = &frames.Target{Address: target} - l.source = new(frames.Source) - s := &Sender{ - l: l, - } - - if opts == nil { - return s, nil - } - - for _, v := range opts.Capabilities { - s.l.source.Capabilities = append(s.l.source.Capabilities, encoding.Symbol(v)) - } - if opts.Durability > DurabilityUnsettledState { - return nil, fmt.Errorf("invalid Durability %d", opts.Durability) - } - s.l.source.Durable = opts.Durability - if opts.DynamicAddress { - s.l.target.Address = "" - s.l.dynamicAddr = opts.DynamicAddress - } - if opts.ExpiryPolicy != "" { - if err := encoding.ValidateExpiryPolicy(opts.ExpiryPolicy); err != nil { - return nil, err - } - s.l.source.ExpiryPolicy = opts.ExpiryPolicy - } - s.l.source.Timeout = opts.ExpiryTimeout - if opts.Name != "" { - s.l.key.name = opts.Name - } - if opts.Properties != nil { - s.l.properties = make(map[encoding.Symbol]any) - for k, v := range opts.Properties { - if k == "" { - return nil, errors.New("link property key must not be empty") - } - s.l.properties[encoding.Symbol(k)] = v - } - } - if opts.RequestedReceiverSettleMode != nil { - if rsm := *opts.RequestedReceiverSettleMode; rsm > ReceiverSettleModeSecond { - return nil, fmt.Errorf("invalid RequestedReceiverSettleMode %d", rsm) - } - s.l.receiverSettleMode = opts.RequestedReceiverSettleMode - } - if opts.SettlementMode != nil { - if ssm := *opts.SettlementMode; ssm > SenderSettleModeMixed { - return nil, fmt.Errorf("invalid SettlementMode %d", ssm) - } - s.l.senderSettleMode = opts.SettlementMode - } - s.l.source.Address = opts.SourceAddress - for _, v := range opts.TargetCapabilities { - s.l.target.Capabilities = append(s.l.target.Capabilities, encoding.Symbol(v)) - } - if opts.TargetDurability != DurabilityNone { - s.l.target.Durable = opts.TargetDurability - } - if opts.TargetExpiryPolicy != ExpiryPolicySessionEnd { - s.l.target.ExpiryPolicy = opts.TargetExpiryPolicy - } - if opts.TargetExpiryTimeout != 0 { - s.l.target.Timeout = opts.TargetExpiryTimeout - } - return s, nil -} - -func (s *Sender) attach(ctx context.Context) error { - if err := s.l.attach(ctx, func(pa *frames.PerformAttach) { - pa.Role = encoding.RoleSender - if pa.Target == nil { - pa.Target = new(frames.Target) - } - pa.Target.Dynamic = s.l.dynamicAddr - }, func(pa *frames.PerformAttach) { - if s.l.target == nil { - s.l.target = new(frames.Target) - } - - // if dynamic address requested, copy assigned name to address - if s.l.dynamicAddr && pa.Target != nil { - s.l.target.Address = pa.Target.Address - } - }); err != nil { - return err - } - - s.transfers = make(chan transferEnvelope) - - return nil -} - -type senderTestHooks struct { - MuxTransfer func() -} - -func (s *Sender) mux(hooks senderTestHooks) { - if hooks.MuxTransfer == nil { - hooks.MuxTransfer = nop - } - - defer func() { - close(s.l.done) - }() - -Loop: - for { - var outgoingTransfers chan transferEnvelope - if s.l.linkCredit > 0 { - debug.Log(1, "TX (Sender %p) (enable): target: %q, link credit: %d, deliveryCount: %d", s, s.l.target.Address, s.l.linkCredit, s.l.deliveryCount) - outgoingTransfers = s.transfers - } else { - debug.Log(1, "TX (Sender %p) (pause): target: %q, link credit: %d, deliveryCount: %d", s, s.l.target.Address, s.l.linkCredit, s.l.deliveryCount) - } - - closed := s.l.close - if s.l.closeInProgress { - // swap out channel so it no longer triggers - closed = nil - - // disable sending once closing is in progress. - // this prevents races with mux shutdown and - // the peer sending disposition frames. - outgoingTransfers = nil - } - - select { - // received frame - case q := <-s.l.rxQ.Wait(): - // populated queue - fr := *q.Dequeue() - s.l.rxQ.Release(q) - - // if muxHandleFrame returns an error it means the mux must terminate. - // note that in the case of a client-side close due to an error, nil - // is returned in order to keep the mux running to ack the detach frame. - if err := s.muxHandleFrame(fr); err != nil { - s.l.doneErr = err - return - } - - // send data - case env := <-outgoingTransfers: - hooks.MuxTransfer() - select { - case s.l.session.txTransfer <- env: - debug.Log(2, "TX (Sender %p): mux transfer to Session: %d, %s", s, s.l.session.channel, env.Frame) - // decrement link-credit after entire message transferred - if !env.Frame.More { - s.l.deliveryCount++ - s.l.linkCredit-- - // we are the sender and we keep track of the peer's link credit - debug.Log(3, "TX (Sender %p): link: %s, link credit: %d", s, s.l.key.name, s.l.linkCredit) - } - continue Loop - case <-s.l.close: - continue Loop - case <-s.l.session.done: - continue Loop - } - - case <-closed: - if s.l.closeInProgress { - // a client-side close due to protocol error is in progress - continue - } - - // sender is being closed by the client - s.l.closeInProgress = true - fr := &frames.PerformDetach{ - Handle: s.l.handle, - Closed: true, - } - s.l.txFrame(context.Background(), fr, nil) - - case <-s.l.session.done: - s.l.doneErr = s.l.session.doneErr - return - } - } -} - -// muxHandleFrame processes fr based on type. -// depending on the peer's RSM, it might return a disposition frame for sending -func (s *Sender) muxHandleFrame(fr frames.FrameBody) error { - debug.Log(2, "RX (Sender %p): %s", s, fr) - switch fr := fr.(type) { - // flow control frame - case *frames.PerformFlow: - // the sender's link-credit variable MUST be set according to this formula when flow information is given by the receiver: - // link-credit(snd) := delivery-count(rcv) + link-credit(rcv) - delivery-count(snd) - linkCredit := *fr.LinkCredit - s.l.deliveryCount - if fr.DeliveryCount != nil { - // DeliveryCount can be nil if the receiver hasn't processed - // the attach. That shouldn't be the case here, but it's - // what ActiveMQ does. - linkCredit += *fr.DeliveryCount - } - - s.l.linkCredit = linkCredit - - if !fr.Echo { - return nil - } - - var ( - // copy because sent by pointer below; prevent race - deliveryCount = s.l.deliveryCount - ) - - // send flow - resp := &frames.PerformFlow{ - Handle: &s.l.handle, - DeliveryCount: &deliveryCount, - LinkCredit: &linkCredit, // max number of messages - } - - select { - case s.l.session.tx <- frameBodyEnvelope{Ctx: context.Background(), FrameBody: resp}: - debug.Log(2, "TX (Sender %p): mux frame to Session (%p): %d, %s", s, s.l.session, s.l.session.channel, resp) - case <-s.l.close: - return nil - case <-s.l.session.done: - return s.l.session.doneErr - } - - case *frames.PerformDisposition: - if fr.Settled { - return nil - } - - // peer is in mode second, so we must send confirmation of disposition. - // NOTE: the ack must be sent through the session so it can close out - // the in-flight disposition. - dr := &frames.PerformDisposition{ - Role: encoding.RoleSender, - First: fr.First, - Last: fr.Last, - Settled: true, - } - - select { - case s.l.session.tx <- frameBodyEnvelope{Ctx: context.Background(), FrameBody: dr}: - debug.Log(2, "TX (Sender %p): mux frame to Session (%p): %d, %s", s, s.l.session, s.l.session.channel, dr) - case <-s.l.close: - return nil - case <-s.l.session.done: - return s.l.session.doneErr - } - - return nil - - default: - return s.l.muxHandleFrame(fr) - } - - return nil -} diff --git a/sdk/messaging/azservicebus/internal/go-amqp/session.go b/sdk/messaging/azservicebus/internal/go-amqp/session.go deleted file mode 100644 index d9087be92e80..000000000000 --- a/sdk/messaging/azservicebus/internal/go-amqp/session.go +++ /dev/null @@ -1,792 +0,0 @@ -// Copyright (C) 2017 Kale Blankenship -// Portions Copyright (c) Microsoft Corporation - -package amqp - -import ( - "context" - "errors" - "fmt" - "math" - "sync" - - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/bitmap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/debug" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/encoding" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/frames" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/internal/queue" -) - -// Default session options -const ( - defaultWindow = 5000 -) - -// SessionOptions contains the optional settings for configuring an AMQP session. -type SessionOptions struct { - // MaxLinks sets the maximum number of links (Senders/Receivers) - // allowed on the session. - // - // Minimum: 1. - // Default: 4294967295. - MaxLinks uint32 -} - -// Session is an AMQP session. -// -// A session multiplexes Receivers. -type Session struct { - channel uint16 // session's local channel - remoteChannel uint16 // session's remote channel, owned by conn.connReader - conn *Conn // underlying conn - tx chan frameBodyEnvelope // non-transfer frames to be sent; session must track disposition - txTransfer chan transferEnvelope // transfer frames to be sent; session must track disposition - - // frames destined for this session are added to this queue by conn.connReader - rxQ *queue.Holder[frames.FrameBody] - - // flow control - incomingWindow uint32 - outgoingWindow uint32 - needFlowCount uint32 - - handleMax uint32 - - // link management - linksMu sync.RWMutex // used to synchronize link handle allocation - linksByKey map[linkKey]*link // mapping of name+role link - handles *bitmap.Bitmap // allocated handles - - abandonedLinksMu sync.Mutex - abandonedLinks []*link - - // used for gracefully closing session - close chan struct{} // closed by calling Close(). it signals that the end performative should be sent - closeOnce sync.Once - - // part of internal public surface area - done chan struct{} // closed when the session has terminated (mux exited); DO NOT wait on this from within Session.mux() as it will never trigger! - endSent chan struct{} // closed when the end performative has been sent; once this is closed, links MUST NOT send any frames! - doneErr error // contains the mux error state; ONLY written to by the mux and MUST only be read from after done is closed! - closeErr error // contains the error state returned from Close(); ONLY Close() reads/writes this! -} - -func newSession(c *Conn, channel uint16, opts *SessionOptions) *Session { - s := &Session{ - conn: c, - channel: channel, - tx: make(chan frameBodyEnvelope), - txTransfer: make(chan transferEnvelope), - incomingWindow: defaultWindow, - outgoingWindow: defaultWindow, - handleMax: math.MaxUint32 - 1, - linksMu: sync.RWMutex{}, - linksByKey: make(map[linkKey]*link), - close: make(chan struct{}), - done: make(chan struct{}), - endSent: make(chan struct{}), - } - - if opts != nil { - if opts.MaxLinks != 0 { - // MaxLinks is the number of total links. - // handleMax is the max handle ID which starts - // at zero. so we decrement by one - s.handleMax = opts.MaxLinks - 1 - } - } - - // create handle map after options have been applied - s.handles = bitmap.New(s.handleMax) - - s.rxQ = queue.NewHolder(queue.New[frames.FrameBody](int(s.incomingWindow))) - - return s -} - -// waitForFrame waits for an incoming frame to be queued. -// it returns the next frame from the queue, or an error. -// the error is either from the context or conn.doneErr. -// not meant for consumption outside of session.go. -func (s *Session) waitForFrame(ctx context.Context) (frames.FrameBody, error) { - var q *queue.Queue[frames.FrameBody] - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-s.conn.done: - return nil, s.conn.doneErr - case q = <-s.rxQ.Wait(): - // populated queue - } - - fr := q.Dequeue() - s.rxQ.Release(q) - - return *fr, nil -} - -func (s *Session) begin(ctx context.Context) error { - // send Begin to server - begin := &frames.PerformBegin{ - NextOutgoingID: 0, - IncomingWindow: s.incomingWindow, - OutgoingWindow: s.outgoingWindow, - HandleMax: s.handleMax, - } - - if err := s.txFrameAndWait(ctx, begin); err != nil { - return err - } - - // wait for response - fr, err := s.waitForFrame(ctx) - if err != nil { - // if we exit before receiving the ack, our caller will clean up the channel. - // however, it does mean that the peer will now have assigned an outgoing - // channel ID that's not in use. - return err - } - - begin, ok := fr.(*frames.PerformBegin) - if !ok { - // this codepath is hard to hit (impossible?). if the response isn't a PerformBegin and we've not - // yet seen the remote channel number, the default clause in conn.connReader will protect us from that. - // if we have seen the remote channel number then it's likely the session.mux for that channel will - // either swallow the frame or blow up in some other way, both causing this call to hang. - // deallocate session on error. we can't call - // s.Close() as the session mux hasn't started yet. - debug.Log(1, "RX (Session %p): unexpected begin response frame %T", s, fr) - s.conn.deleteSession(s) - if err := s.conn.Close(); err != nil { - return err - } - return &ConnError{inner: fmt.Errorf("unexpected begin response: %#v", fr)} - } - - // start Session multiplexor - go s.mux(begin) - - return nil -} - -// Close closes the session. -// - ctx controls waiting for the peer to acknowledge the session is closed -// -// If the context's deadline expires or is cancelled before the operation -// completes, an error is returned. However, the operation will continue to -// execute in the background. Subsequent calls will return a *SessionError -// that contains the context's error message. -func (s *Session) Close(ctx context.Context) error { - var ctxErr error - s.closeOnce.Do(func() { - close(s.close) - - // once the mux has received the ack'ing end performative, the mux will - // exit which deletes the session and closes s.done. - select { - case <-s.done: - s.closeErr = s.doneErr - - case <-ctx.Done(): - // notify the caller that the close timed out/was cancelled. - // the mux will remain running and once the ack is received it will terminate. - ctxErr = ctx.Err() - - // record that the close timed out/was cancelled. - // subsequent calls to Close() will return this - debug.Log(1, "TX (Session %p) channel %d: %v", s, s.channel, ctxErr) - s.closeErr = &SessionError{inner: ctxErr} - } - }) - - if ctxErr != nil { - return ctxErr - } - - var sessionErr *SessionError - if errors.As(s.closeErr, &sessionErr) && sessionErr.RemoteErr == nil && sessionErr.inner == nil { - // an empty SessionError means the session was cleanly closed by the caller - return nil - } - return s.closeErr -} - -// txFrame sends a frame to the connWriter. -// - ctx is used to provide the write deadline -// - fr is the frame to write to net.Conn -// - sent is the optional channel that will contain the error if the write fails -func (s *Session) txFrame(ctx context.Context, fr frames.FrameBody, sent chan error) { - debug.Log(2, "TX (Session %p) mux frame to Conn (%p): %s", s, s.conn, fr) - s.conn.sendFrame(ctx, frames.Frame{ - Type: frames.TypeAMQP, - Channel: s.channel, - Body: fr, - }, sent) -} - -// txFrameAndWait sends a frame to the connWriter and waits for the write to complete -// - ctx is used to provide the write deadline -// - fr is the frame to write to net.Conn -func (s *Session) txFrameAndWait(ctx context.Context, fr frames.FrameBody) error { - sent := make(chan error, 1) - s.txFrame(ctx, fr, sent) - - select { - case err := <-sent: - return err - case <-s.conn.done: - return s.conn.doneErr - case <-s.done: - return s.doneErr - } -} - -// NewReceiver opens a new receiver link on the session. -// - ctx controls waiting for the peer to create a sending terminus -// - source is the name of the peer's sending terminus -// - opts contains optional values, pass nil to accept the defaults -// -// If the context's deadline expires or is cancelled before the operation -// completes, an error is returned. If the Receiver was successfully -// created, it will be cleaned up in future calls to NewReceiver. -func (s *Session) NewReceiver(ctx context.Context, source string, opts *ReceiverOptions) (*Receiver, error) { - r, err := newReceiver(source, s, opts) - if err != nil { - return nil, err - } - if err = r.attach(ctx); err != nil { - return nil, err - } - - go r.mux(receiverTestHooks{}) - - return r, nil -} - -// NewSender opens a new sender link on the session. -// - ctx controls waiting for the peer to create a receiver terminus -// - target is the name of the peer's receiver terminus -// - opts contains optional values, pass nil to accept the defaults -// -// If the context's deadline expires or is cancelled before the operation -// completes, an error is returned. If the Sender was successfully -// created, it will be cleaned up in future calls to NewSender. -func (s *Session) NewSender(ctx context.Context, target string, opts *SenderOptions) (*Sender, error) { - l, err := newSender(target, s, opts) - if err != nil { - return nil, err - } - if err = l.attach(ctx); err != nil { - return nil, err - } - - go l.mux(senderTestHooks{}) - - return l, nil -} - -func (s *Session) mux(remoteBegin *frames.PerformBegin) { - defer func() { - if s.doneErr == nil { - s.doneErr = &SessionError{} - } else if connErr := (&ConnError{}); !errors.As(s.doneErr, &connErr) { - // only wrap non-ConnError error types - var amqpErr *Error - if errors.As(s.doneErr, &amqpErr) { - s.doneErr = &SessionError{RemoteErr: amqpErr} - } else { - s.doneErr = &SessionError{inner: s.doneErr} - } - } - // Signal goroutines waiting on the session. - close(s.done) - }() - - var ( - links = make(map[uint32]*link) // mapping of remote handles to links - handlesByDeliveryID = make(map[uint32]uint32) // mapping of deliveryIDs to handles - deliveryIDByHandle = make(map[uint32]uint32) // mapping of handles to latest deliveryID - handlesByRemoteDeliveryID = make(map[uint32]uint32) // mapping of remote deliveryID to handles - - settlementByDeliveryID = make(map[uint32]chan encoding.DeliveryState) - - nextDeliveryID uint32 // tracks the next delivery ID for outgoing transfers - - // flow control values - nextOutgoingID uint32 - nextIncomingID = remoteBegin.NextOutgoingID - remoteIncomingWindow = remoteBegin.IncomingWindow - remoteOutgoingWindow = remoteBegin.OutgoingWindow - - closeInProgress bool // indicates the end performative has been sent - ) - - closeWithError := func(e1 *Error, e2 error) { - if closeInProgress { - debug.Log(3, "TX (Session %p): close already pending, discarding %v", s, e1) - return - } - - closeInProgress = true - s.doneErr = e2 - s.txFrame(context.Background(), &frames.PerformEnd{Error: e1}, nil) - close(s.endSent) - } - - for { - txTransfer := s.txTransfer - // disable txTransfer if flow control windows have been exceeded - if remoteIncomingWindow == 0 || s.outgoingWindow == 0 { - debug.Log(1, "TX (Session %p): disabling txTransfer - window exceeded. remoteIncomingWindow: %d outgoingWindow: %d", - s, remoteIncomingWindow, s.outgoingWindow) - txTransfer = nil - } - - tx := s.tx - closed := s.close - if closeInProgress { - // swap out channel so it no longer triggers - closed = nil - - // once the end performative is sent, we're not allowed to send any frames - tx = nil - txTransfer = nil - } - - // notes on client-side closing session - // when session is closed, we must keep the mux running until the ack'ing end performative - // has been received. during this window, the session is allowed to receive frames but cannot - // send them. - // client-side close happens either by user calling Session.Close() or due to mux initiated - // close due to a violation of some invariant (see sending &Error{} to s.close). in the case - // that both code paths have been triggered, we must be careful to preserve the error that - // triggered the mux initiated close so it can be surfaced to the caller. - - select { - // conn has completed, exit - case <-s.conn.done: - s.doneErr = s.conn.doneErr - return - - case <-closed: - if closeInProgress { - // a client-side close due to protocol error is in progress - continue - } - // session is being closed by the client - closeInProgress = true - s.txFrame(context.Background(), &frames.PerformEnd{}, nil) - close(s.endSent) - - // incoming frame - case q := <-s.rxQ.Wait(): - fr := *q.Dequeue() - s.rxQ.Release(q) - debug.Log(2, "RX (Session %p): %s", s, fr) - - switch body := fr.(type) { - // Disposition frames can reference transfers from more than one - // link. Send this frame to all of them. - case *frames.PerformDisposition: - start := body.First - end := start - if body.Last != nil { - end = *body.Last - } - for deliveryID := start; deliveryID <= end; deliveryID++ { - handles := handlesByDeliveryID - if body.Role == encoding.RoleSender { - handles = handlesByRemoteDeliveryID - } - - handle, ok := handles[deliveryID] - if !ok { - debug.Log(2, "RX (Session %p): role %s: didn't find deliveryID %d in handles map", s, body.Role, deliveryID) - continue - } - delete(handles, deliveryID) - - if body.Settled && body.Role == encoding.RoleReceiver { - // check if settlement confirmation was requested, if so - // confirm by closing channel - if done, ok := settlementByDeliveryID[deliveryID]; ok { - delete(settlementByDeliveryID, deliveryID) - select { - case done <- body.State: - default: - } - close(done) - } - } - - link, ok := links[handle] - if !ok { - closeWithError(&Error{ - Condition: ErrCondUnattachedHandle, - Description: "received disposition frame referencing a handle that's not in use", - }, fmt.Errorf("received disposition frame with unknown link handle %d", handle)) - continue - } - - s.muxFrameToLink(link, fr) - } - continue - case *frames.PerformFlow: - if body.NextIncomingID == nil { - // This is a protocol error: - // "[...] MUST be set if the peer has received - // the begin frame for the session" - closeWithError(&Error{ - Condition: ErrCondNotAllowed, - Description: "next-incoming-id not set after session established", - }, errors.New("protocol error: received flow without next-incoming-id after session established")) - continue - } - - // "When the endpoint receives a flow frame from its peer, - // it MUST update the next-incoming-id directly from the - // next-outgoing-id of the frame, and it MUST update the - // remote-outgoing-window directly from the outgoing-window - // of the frame." - nextIncomingID = body.NextOutgoingID - remoteOutgoingWindow = body.OutgoingWindow - - // "The remote-incoming-window is computed as follows: - // - // next-incoming-id(flow) + incoming-window(flow) - next-outgoing-id(endpoint) - // - // If the next-incoming-id field of the flow frame is not set, then remote-incoming-window is computed as follows: - // - // initial-outgoing-id(endpoint) + incoming-window(flow) - next-outgoing-id(endpoint)" - remoteIncomingWindow = body.IncomingWindow - nextOutgoingID - remoteIncomingWindow += *body.NextIncomingID - debug.Log(3, "RX (Session %p): flow - remoteOutgoingWindow: %d remoteIncomingWindow: %d nextOutgoingID: %d", s, remoteOutgoingWindow, remoteIncomingWindow, nextOutgoingID) - - // Send to link if handle is set - if body.Handle != nil { - link, ok := links[*body.Handle] - if !ok { - closeWithError(&Error{ - Condition: ErrCondUnattachedHandle, - Description: "received flow frame referencing a handle that's not in use", - }, fmt.Errorf("received flow frame with unknown link handle %d", body.Handle)) - continue - } - - s.muxFrameToLink(link, fr) - continue - } - - if body.Echo && !closeInProgress { - niID := nextIncomingID - resp := &frames.PerformFlow{ - NextIncomingID: &niID, - IncomingWindow: s.incomingWindow, - NextOutgoingID: nextOutgoingID, - OutgoingWindow: s.outgoingWindow, - } - s.txFrame(context.Background(), resp, nil) - } - - case *frames.PerformAttach: - // On Attach response link should be looked up by name, then added - // to the links map with the remote's handle contained in this - // attach frame. - // - // Note body.Role is the remote peer's role, we reverse for the local key. - s.linksMu.RLock() - link, linkOk := s.linksByKey[linkKey{name: body.Name, role: !body.Role}] - s.linksMu.RUnlock() - if !linkOk { - closeWithError(&Error{ - Condition: ErrCondNotAllowed, - Description: "received mismatched attach frame", - }, fmt.Errorf("protocol error: received mismatched attach frame %+v", body)) - continue - } - - link.remoteHandle = body.Handle - links[link.remoteHandle] = link - - s.muxFrameToLink(link, fr) - - case *frames.PerformTransfer: - s.needFlowCount++ - // "Upon receiving a transfer, the receiving endpoint will - // increment the next-incoming-id to match the implicit - // transfer-id of the incoming transfer plus one, as well - // as decrementing the remote-outgoing-window, and MAY - // (depending on policy) decrement its incoming-window." - nextIncomingID++ - // don't loop to intmax - if remoteOutgoingWindow > 0 { - remoteOutgoingWindow-- - } - link, ok := links[body.Handle] - if !ok { - closeWithError(&Error{ - Condition: ErrCondUnattachedHandle, - Description: "received transfer frame referencing a handle that's not in use", - }, fmt.Errorf("received transfer frame with unknown link handle %d", body.Handle)) - continue - } - - s.muxFrameToLink(link, fr) - - // if this message is received unsettled and link rcv-settle-mode == second, add to handlesByRemoteDeliveryID - if !body.Settled && body.DeliveryID != nil && link.receiverSettleMode != nil && *link.receiverSettleMode == ReceiverSettleModeSecond { - debug.Log(1, "RX (Session %p): adding handle to handlesByRemoteDeliveryID. delivery ID: %d", s, *body.DeliveryID) - handlesByRemoteDeliveryID[*body.DeliveryID] = body.Handle - } - - // Update peer's outgoing window if half has been consumed. - if s.needFlowCount >= s.incomingWindow/2 && !closeInProgress { - debug.Log(3, "RX (Session %p): channel %d: flow - s.needFlowCount(%d) >= s.incomingWindow(%d)/2\n", s, s.channel, s.needFlowCount, s.incomingWindow) - s.needFlowCount = 0 - nID := nextIncomingID - flow := &frames.PerformFlow{ - NextIncomingID: &nID, - IncomingWindow: s.incomingWindow, - NextOutgoingID: nextOutgoingID, - OutgoingWindow: s.outgoingWindow, - } - s.txFrame(context.Background(), flow, nil) - } - - case *frames.PerformDetach: - link, ok := links[body.Handle] - if !ok { - closeWithError(&Error{ - Condition: ErrCondUnattachedHandle, - Description: "received detach frame referencing a handle that's not in use", - }, fmt.Errorf("received detach frame with unknown link handle %d", body.Handle)) - continue - } - s.muxFrameToLink(link, fr) - - // we received a detach frame and sent it to the link. - // this was either the response to a client-side initiated - // detach or our peer detached us. either way, now that - // the link has processed the frame it's detached so we - // are safe to clean up its state. - delete(links, link.remoteHandle) - delete(deliveryIDByHandle, link.handle) - s.deallocateHandle(link) - - case *frames.PerformEnd: - // there are two possibilities: - // - this is the ack to a client-side Close() - // - the peer is ending the session so we must ack - - if closeInProgress { - return - } - - // peer detached us with an error, save it and send the ack - if body.Error != nil { - s.doneErr = body.Error - } - - fr := frames.PerformEnd{} - s.txFrame(context.Background(), &fr, nil) - - // per spec, when end is received, we're no longer allowed to receive frames - return - - default: - debug.Log(1, "RX (Session %p): unexpected frame: %s\n", s, body) - closeWithError(&Error{ - Condition: ErrCondInternalError, - Description: "session received unexpected frame", - }, fmt.Errorf("internal error: unexpected frame %T", body)) - } - - case env := <-txTransfer: - fr := &env.Frame - // record current delivery ID - var deliveryID uint32 - if fr.DeliveryID == &needsDeliveryID { - deliveryID = nextDeliveryID - fr.DeliveryID = &deliveryID - nextDeliveryID++ - deliveryIDByHandle[fr.Handle] = deliveryID - - // add to handleByDeliveryID if not sender-settled - if !fr.Settled { - handlesByDeliveryID[deliveryID] = fr.Handle - } - } else { - // if fr.DeliveryID is nil it must have been added - // to deliveryIDByHandle already - deliveryID = deliveryIDByHandle[fr.Handle] - } - - // log after the delivery ID has been assigned - debug.Log(2, "TX (Session %p): %d, %s", s, s.channel, fr) - - // frame has been sender-settled, remove from map - if fr.Settled { - delete(handlesByDeliveryID, deliveryID) - } - - s.txFrame(env.Ctx, fr, env.Sent) - if sendErr := <-env.Sent; sendErr != nil { - s.doneErr = sendErr - - // put the error back as our sender will read from this channel - env.Sent <- sendErr - return - } - - // if not settled, add done chan to map - if !fr.Settled && fr.Done != nil { - settlementByDeliveryID[deliveryID] = fr.Done - } else if fr.Done != nil { - // sender-settled, close done now that the transfer has been sent - close(fr.Done) - } - - // "Upon sending a transfer, the sending endpoint will increment - // its next-outgoing-id, decrement its remote-incoming-window, - // and MAY (depending on policy) decrement its outgoing-window." - nextOutgoingID++ - // don't decrement if we're at 0 or we could loop to int max - if remoteIncomingWindow != 0 { - remoteIncomingWindow-- - } - - case env := <-tx: - fr := env.FrameBody - debug.Log(2, "TX (Session %p): %d, %s", s, s.channel, fr) - switch fr := env.FrameBody.(type) { - case *frames.PerformDisposition: - if fr.Settled && fr.Role == encoding.RoleSender { - // sender with a peer that's in mode second; sending confirmation of disposition. - // disposition frames can reference a range of delivery IDs, although it's highly - // likely in this case there will only be one. - start := fr.First - end := start - if fr.Last != nil { - end = *fr.Last - } - for deliveryID := start; deliveryID <= end; deliveryID++ { - // send delivery state to the channel and close it to signal - // that the delivery has completed. - if done, ok := settlementByDeliveryID[deliveryID]; ok { - delete(settlementByDeliveryID, deliveryID) - select { - case done <- fr.State: - default: - } - close(done) - } - } - } - s.txFrame(env.Ctx, fr, env.Sent) - case *frames.PerformFlow: - niID := nextIncomingID - fr.NextIncomingID = &niID - fr.IncomingWindow = s.incomingWindow - fr.NextOutgoingID = nextOutgoingID - fr.OutgoingWindow = s.outgoingWindow - s.txFrame(context.Background(), fr, env.Sent) - case *frames.PerformTransfer: - panic("transfer frames must use txTransfer") - default: - s.txFrame(context.Background(), fr, env.Sent) - } - } - } -} - -func (s *Session) allocateHandle(ctx context.Context, l *link) error { - s.linksMu.Lock() - defer s.linksMu.Unlock() - - // Check if link name already exists, if so then an error should be returned - existing := s.linksByKey[l.key] - if existing != nil { - return fmt.Errorf("link with name '%v' already exists", l.key.name) - } - - next, ok := s.handles.Next() - if !ok { - if err := s.Close(ctx); err != nil { - return err - } - // handle numbers are zero-based, report the actual count - return &SessionError{inner: fmt.Errorf("reached session handle max (%d)", s.handleMax+1)} - } - - l.handle = next // allocate handle to the link - s.linksByKey[l.key] = l // add to mapping - - return nil -} - -func (s *Session) deallocateHandle(l *link) { - s.linksMu.Lock() - defer s.linksMu.Unlock() - - delete(s.linksByKey, l.key) - s.handles.Remove(l.handle) -} - -func (s *Session) abandonLink(l *link) { - s.abandonedLinksMu.Lock() - defer s.abandonedLinksMu.Unlock() - s.abandonedLinks = append(s.abandonedLinks, l) -} - -func (s *Session) freeAbandonedLinks(ctx context.Context) error { - s.abandonedLinksMu.Lock() - defer s.abandonedLinksMu.Unlock() - - debug.Log(3, "TX (Session %p): cleaning up %d abandoned links", s, len(s.abandonedLinks)) - - for _, l := range s.abandonedLinks { - dr := &frames.PerformDetach{ - Handle: l.handle, - Closed: true, - } - if err := s.txFrameAndWait(ctx, dr); err != nil { - return err - } - } - - s.abandonedLinks = nil - return nil -} - -func (s *Session) muxFrameToLink(l *link, fr frames.FrameBody) { - q := l.rxQ.Acquire() - q.Enqueue(fr) - l.rxQ.Release(q) - debug.Log(2, "RX (Session %p): mux frame to link (%p): %s, %s", s, l, l.key.name, fr) -} - -// transferEnvelope is used by senders to send transfer frames -type transferEnvelope struct { - Ctx context.Context - Frame frames.PerformTransfer - - // Sent is *never* nil as we use this for confirmation of sending - // NOTE: use a buffered channel of size 1 when populating - Sent chan error -} - -// frameBodyEnvelope is used by senders and receivers to send frames. -type frameBodyEnvelope struct { - Ctx context.Context - FrameBody frames.FrameBody - - // Sent *can* be nil depending on what frame is being sent. - // e.g. sending a disposition frame frame a receiver's settlement - // APIs will have a non-nil channel vs sending a flow frame - // NOTE: use a buffered channel of size 1 when populating - Sent chan error -} - -// the address of this var is a sentinel value indicating -// that a transfer frame is in need of a delivery ID -var needsDeliveryID uint32 diff --git a/sdk/messaging/azservicebus/internal/mgmt.go b/sdk/messaging/azservicebus/internal/mgmt.go index 74f42d117b0e..cf57ba06e004 100644 --- a/sdk/messaging/azservicebus/internal/mgmt.go +++ b/sdk/messaging/azservicebus/internal/mgmt.go @@ -12,7 +12,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/internal/uuid" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" ) type Disposition struct { diff --git a/sdk/messaging/azservicebus/internal/mock/emulation/events.go b/sdk/messaging/azservicebus/internal/mock/emulation/events.go index e2e19e32e628..d34791841e05 100644 --- a/sdk/messaging/azservicebus/internal/mock/emulation/events.go +++ b/sdk/messaging/azservicebus/internal/mock/emulation/events.go @@ -7,7 +7,7 @@ import ( "fmt" "sync" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" ) type EventType string diff --git a/sdk/messaging/azservicebus/internal/mock/emulation/mock_data.go b/sdk/messaging/azservicebus/internal/mock/emulation/mock_data.go index cb2fd0ded2d7..6e49410a9191 100644 --- a/sdk/messaging/azservicebus/internal/mock/emulation/mock_data.go +++ b/sdk/messaging/azservicebus/internal/mock/emulation/mock_data.go @@ -17,9 +17,9 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/auth" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/mock" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/sbauth" + "github.com/Azure/go-amqp" "github.com/golang/mock/gomock" ) diff --git a/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_receiver.go b/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_receiver.go index 73dc7c15720c..bcef3023fcc3 100644 --- a/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_receiver.go +++ b/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_receiver.go @@ -10,8 +10,8 @@ import ( "sync/atomic" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/mock" + "github.com/Azure/go-amqp" "github.com/golang/mock/gomock" ) diff --git a/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_sender.go b/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_sender.go index aa02398a85c4..7ee49c436bc3 100644 --- a/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_sender.go +++ b/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_sender.go @@ -9,8 +9,8 @@ import ( "sync/atomic" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/mock" + "github.com/Azure/go-amqp" "github.com/golang/mock/gomock" ) diff --git a/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_session.go b/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_session.go index 5bc7529d25da..0b3f34e8deef 100644 --- a/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_session.go +++ b/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_session.go @@ -7,8 +7,8 @@ import ( "context" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/mock" + "github.com/Azure/go-amqp" "github.com/golang/mock/gomock" ) diff --git a/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_test.go b/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_test.go index 8d6ea6c2e2a1..7cca0c64d372 100644 --- a/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_test.go +++ b/sdk/messaging/azservicebus/internal/mock/emulation/mock_data_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/mock/emulation" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/internal/mock/emulation/queue.go b/sdk/messaging/azservicebus/internal/mock/emulation/queue.go index faf80a446e82..f336d67c6cc0 100644 --- a/sdk/messaging/azservicebus/internal/mock/emulation/queue.go +++ b/sdk/messaging/azservicebus/internal/mock/emulation/queue.go @@ -9,8 +9,8 @@ import ( "sync" azlog "github.com/Azure/azure-sdk-for-go/sdk/internal/log" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/test" + "github.com/Azure/go-amqp" ) type Operation struct { diff --git a/sdk/messaging/azservicebus/internal/mock/emulation/queue_test.go b/sdk/messaging/azservicebus/internal/mock/emulation/queue_test.go index 0a9b822ef539..636d71e93010 100644 --- a/sdk/messaging/azservicebus/internal/mock/emulation/queue_test.go +++ b/sdk/messaging/azservicebus/internal/mock/emulation/queue_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/mock/emulation" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/internal/mock/mock_amqp.go b/sdk/messaging/azservicebus/internal/mock/mock_amqp.go index 5ee2c0e5ea6a..9bd0b22f579d 100644 --- a/sdk/messaging/azservicebus/internal/mock/mock_amqp.go +++ b/sdk/messaging/azservicebus/internal/mock/mock_amqp.go @@ -13,7 +13,7 @@ import ( reflect "reflect" amqpwrap "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" - amqp "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + amqp "github.com/Azure/go-amqp" gomock "github.com/golang/mock/gomock" ) diff --git a/sdk/messaging/azservicebus/internal/mock/mock_helpers.go b/sdk/messaging/azservicebus/internal/mock/mock_helpers.go index 1695aaf5c3d6..fa4480069435 100644 --- a/sdk/messaging/azservicebus/internal/mock/mock_helpers.go +++ b/sdk/messaging/azservicebus/internal/mock/mock_helpers.go @@ -8,7 +8,7 @@ import ( "fmt" "time" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" gomock "github.com/golang/mock/gomock" ) diff --git a/sdk/messaging/azservicebus/internal/mock/mock_rpc.go b/sdk/messaging/azservicebus/internal/mock/mock_rpc.go index 94fb00fcf2cb..193c3653f18d 100644 --- a/sdk/messaging/azservicebus/internal/mock/mock_rpc.go +++ b/sdk/messaging/azservicebus/internal/mock/mock_rpc.go @@ -13,7 +13,7 @@ import ( reflect "reflect" amqpwrap "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" - amqp "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + amqp "github.com/Azure/go-amqp" gomock "github.com/golang/mock/gomock" ) diff --git a/sdk/messaging/azservicebus/internal/namespace.go b/sdk/messaging/azservicebus/internal/namespace.go index 8d40a7ab6f36..c56a1e083067 100644 --- a/sdk/messaging/azservicebus/internal/namespace.go +++ b/sdk/messaging/azservicebus/internal/namespace.go @@ -20,9 +20,9 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/auth" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/conn" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/sbauth" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/utils" + "github.com/Azure/go-amqp" ) var rootUserAgent = telemetry.Format("azservicebus", Version) diff --git a/sdk/messaging/azservicebus/internal/namespace_test.go b/sdk/messaging/azservicebus/internal/namespace_test.go index 910eb1eeedd2..82d7ef9aed27 100644 --- a/sdk/messaging/azservicebus/internal/namespace_test.go +++ b/sdk/messaging/azservicebus/internal/namespace_test.go @@ -15,9 +15,9 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/internal/telemetry" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/auth" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/sbauth" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/test" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/internal/rpc.go b/sdk/messaging/azservicebus/internal/rpc.go index 2dbb180b54a1..a23fede1964a 100644 --- a/sdk/messaging/azservicebus/internal/rpc.go +++ b/sdk/messaging/azservicebus/internal/rpc.go @@ -14,7 +14,7 @@ import ( azlog "github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/internal/uuid" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" ) const ( diff --git a/sdk/messaging/azservicebus/internal/rpc_test.go b/sdk/messaging/azservicebus/internal/rpc_test.go index 6ca286c85d9a..704e4137b1f7 100644 --- a/sdk/messaging/azservicebus/internal/rpc_test.go +++ b/sdk/messaging/azservicebus/internal/rpc_test.go @@ -10,9 +10,9 @@ import ( "testing" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/mock" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/test" + "github.com/Azure/go-amqp" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/internal/test/test_helpers.go b/sdk/messaging/azservicebus/internal/test/test_helpers.go index 49636ec457ed..655c94bc4d64 100644 --- a/sdk/messaging/azservicebus/internal/test/test_helpers.go +++ b/sdk/messaging/azservicebus/internal/test/test_helpers.go @@ -18,7 +18,7 @@ import ( azlog "github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/internal/uuid" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/atom" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/internal/utils/retrier_test.go b/sdk/messaging/azservicebus/internal/utils/retrier_test.go index 4c7282a5e897..cc0551c61033 100644 --- a/sdk/messaging/azservicebus/internal/utils/retrier_test.go +++ b/sdk/messaging/azservicebus/internal/utils/retrier_test.go @@ -14,8 +14,8 @@ import ( azlog "github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/test" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/message.go b/sdk/messaging/azservicebus/message.go index b3247fd5d355..40b690b0f548 100644 --- a/sdk/messaging/azservicebus/message.go +++ b/sdk/messaging/azservicebus/message.go @@ -9,7 +9,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/internal/log" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" ) // ReceivedMessage is a received message from a Client.NewReceiver(). diff --git a/sdk/messaging/azservicebus/messageSettler.go b/sdk/messaging/azservicebus/messageSettler.go index 0b787efadb67..915cc020d905 100644 --- a/sdk/messaging/azservicebus/messageSettler.go +++ b/sdk/messaging/azservicebus/messageSettler.go @@ -8,8 +8,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/utils" + "github.com/Azure/go-amqp" ) type settler interface { diff --git a/sdk/messaging/azservicebus/message_batch.go b/sdk/messaging/azservicebus/message_batch.go index 1fde3d4bd761..8b1e10a8c5d8 100644 --- a/sdk/messaging/azservicebus/message_batch.go +++ b/sdk/messaging/azservicebus/message_batch.go @@ -8,7 +8,7 @@ import ( "sync" "github.com/Azure/azure-sdk-for-go/sdk/internal/uuid" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" ) // ErrMessageTooLarge is returned when a message cannot fit into a batch when using MessageBatch.Add() diff --git a/sdk/messaging/azservicebus/message_batch_test.go b/sdk/messaging/azservicebus/message_batch_test.go index fee360a7bbd8..5ab3e9dbf35b 100644 --- a/sdk/messaging/azservicebus/message_batch_test.go +++ b/sdk/messaging/azservicebus/message_batch_test.go @@ -7,7 +7,7 @@ import ( "sync" "testing" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/message_test.go b/sdk/messaging/azservicebus/message_test.go index 2db45826d68c..3704b7591f35 100644 --- a/sdk/messaging/azservicebus/message_test.go +++ b/sdk/messaging/azservicebus/message_test.go @@ -9,7 +9,7 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/receiver.go b/sdk/messaging/azservicebus/receiver.go index 240fd4b2924b..3646a9c1c8e0 100644 --- a/sdk/messaging/azservicebus/receiver.go +++ b/sdk/messaging/azservicebus/receiver.go @@ -14,8 +14,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/utils" + "github.com/Azure/go-amqp" ) // ReceiveMode represents the lock style to use for a receiver - either diff --git a/sdk/messaging/azservicebus/receiver_helpers_test.go b/sdk/messaging/azservicebus/receiver_helpers_test.go index 1a56ae304e9e..bb2fb6698ea4 100644 --- a/sdk/messaging/azservicebus/receiver_helpers_test.go +++ b/sdk/messaging/azservicebus/receiver_helpers_test.go @@ -7,7 +7,7 @@ import ( "context" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" + "github.com/Azure/go-amqp" ) type StubAMQPReceiver struct { diff --git a/sdk/messaging/azservicebus/receiver_simulated_test.go b/sdk/messaging/azservicebus/receiver_simulated_test.go index 18f7210aacda..b2444e3cb22a 100644 --- a/sdk/messaging/azservicebus/receiver_simulated_test.go +++ b/sdk/messaging/azservicebus/receiver_simulated_test.go @@ -14,10 +14,10 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/mock" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/mock/emulation" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/test" + "github.com/Azure/go-amqp" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/receiver_test.go b/sdk/messaging/azservicebus/receiver_test.go index 0d691447c9cc..d1617f77526a 100644 --- a/sdk/messaging/azservicebus/receiver_test.go +++ b/sdk/messaging/azservicebus/receiver_test.go @@ -15,9 +15,9 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/admin" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/sas" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/test" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) @@ -610,7 +610,7 @@ func TestReceiverAMQPDataTypes(t *testing.T) { // - TypeCodeDecimal64 // - TypeCodeDecimal128 // - TypeCodeChar (although note below that a 'character' does work, although it's not a TypecodeChar value) - // https://github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp/blob/e0c6c63fb01e6642686ee4f8e7412da042bf35dd/internal/encoding/decode.go#L568 + // https://github.com/Azure/go-amqp/blob/e0c6c63fb01e6642686ee4f8e7412da042bf35dd/internal/encoding/decode.go#L568 "timestamp": expectedTime, "byte": byte(128), diff --git a/sdk/messaging/azservicebus/receiver_unit_test.go b/sdk/messaging/azservicebus/receiver_unit_test.go index 50bf141d70f0..0b131f4ee9ca 100644 --- a/sdk/messaging/azservicebus/receiver_unit_test.go +++ b/sdk/messaging/azservicebus/receiver_unit_test.go @@ -13,8 +13,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/exported" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/test" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/sender.go b/sdk/messaging/azservicebus/sender.go index 24e18ab23f58..7e4c31d90773 100644 --- a/sdk/messaging/azservicebus/sender.go +++ b/sdk/messaging/azservicebus/sender.go @@ -10,8 +10,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/utils" + "github.com/Azure/go-amqp" ) type ( diff --git a/sdk/messaging/azservicebus/sender_unit_test.go b/sdk/messaging/azservicebus/sender_unit_test.go index 5ab1781b9eba..b676ffc75150 100644 --- a/sdk/messaging/azservicebus/sender_unit_test.go +++ b/sdk/messaging/azservicebus/sender_unit_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/mock/emulation" + "github.com/Azure/go-amqp" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) diff --git a/sdk/messaging/azservicebus/session_receiver.go b/sdk/messaging/azservicebus/session_receiver.go index 07ee1ba90276..6b9a0f9af450 100644 --- a/sdk/messaging/azservicebus/session_receiver.go +++ b/sdk/messaging/azservicebus/session_receiver.go @@ -10,8 +10,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/amqpwrap" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/utils" + "github.com/Azure/go-amqp" ) // SessionReceiver is a Receiver that handles sessions. diff --git a/sdk/messaging/azservicebus/session_receiver_test.go b/sdk/messaging/azservicebus/session_receiver_test.go index c58e4b6b3797..5a0c89ff877f 100644 --- a/sdk/messaging/azservicebus/session_receiver_test.go +++ b/sdk/messaging/azservicebus/session_receiver_test.go @@ -14,8 +14,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/admin" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal" - "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/go-amqp" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/internal/test" + "github.com/Azure/go-amqp" "github.com/stretchr/testify/require" ) From 1cfa5fdbc2db856488a45039d2e454c66c9588fb Mon Sep 17 00:00:00 2001 From: Richard Park Date: Mon, 8 May 2023 11:56:27 -0700 Subject: [PATCH 9/9] Reverting the change to convert CheckpointStore into a struct with pointers. --- sdk/messaging/azeventhubs/checkpoint_store.go | 12 ++--- .../azeventhubs/checkpoints/blob_store.go | 27 +++++------- ...example_consuming_with_checkpoints_test.go | 2 +- .../inmemory_checkpoint_store_test.go | 13 +----- .../stress/tests/processor_stress_tester.go | 2 +- sdk/messaging/azeventhubs/processor.go | 6 +-- .../azeventhubs/processor_load_balancer.go | 4 +- .../processor_load_balancers_test.go | 44 +++++++++---------- .../azeventhubs/processor_partition_client.go | 2 +- sdk/messaging/azeventhubs/processor_test.go | 2 +- .../azeventhubs/processor_unit_test.go | 14 +++--- 11 files changed, 55 insertions(+), 73 deletions(-) diff --git a/sdk/messaging/azeventhubs/checkpoint_store.go b/sdk/messaging/azeventhubs/checkpoint_store.go index d72f2242858b..83c1c3e54fa7 100644 --- a/sdk/messaging/azeventhubs/checkpoint_store.go +++ b/sdk/messaging/azeventhubs/checkpoint_store.go @@ -11,19 +11,19 @@ import ( ) // CheckpointStore is used by multiple consumers to coordinate progress and ownership for partitions. -type CheckpointStore struct { +type CheckpointStore interface { // ClaimOwnership attempts to claim ownership of the partitions in partitionOwnership and returns // the actual partitions that were claimed. - ClaimOwnership func(ctx context.Context, partitionOwnership []Ownership, options *ClaimOwnershipOptions) ([]Ownership, error) + ClaimOwnership(ctx context.Context, partitionOwnership []Ownership, options *ClaimOwnershipOptions) ([]Ownership, error) // ListCheckpoints lists all the available checkpoints. - ListCheckpoints func(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *ListCheckpointsOptions) ([]Checkpoint, error) + ListCheckpoints(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *ListCheckpointsOptions) ([]Checkpoint, error) // ListOwnership lists all ownerships. - ListOwnership func(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *ListOwnershipOptions) ([]Ownership, error) + ListOwnership(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *ListOwnershipOptions) ([]Ownership, error) - // SetCheckpoint creates or updates a specific checkpoint with a sequence and offset. - SetCheckpoint func(ctx context.Context, checkpoint Checkpoint, options *SetCheckpointOptions) error + // SetCheckpoint updates a specific checkpoint with a sequence and offset. + SetCheckpoint(ctx context.Context, checkpoint Checkpoint, options *SetCheckpointOptions) error } // Ownership tracks which consumer owns a particular partition. diff --git a/sdk/messaging/azeventhubs/checkpoints/blob_store.go b/sdk/messaging/azeventhubs/checkpoints/blob_store.go index 4957d7ff17de..5bb0fe17068b 100644 --- a/sdk/messaging/azeventhubs/checkpoints/blob_store.go +++ b/sdk/messaging/azeventhubs/checkpoints/blob_store.go @@ -23,8 +23,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" ) -// blobStore is a CheckpointStore implementation that uses Azure Blob storage. -type blobStore struct { +// BlobStore is a CheckpointStore implementation that uses Azure Blob storage. +type BlobStore struct { cc *container.Client } @@ -37,16 +37,9 @@ type BlobStoreOptions struct { // NewBlobStore creates a checkpoint store that stores ownership and checkpoints in // Azure Blob storage. // NOTE: the container must exist before the checkpoint store can be used. -func NewBlobStore(containerClient *container.Client, options *BlobStoreOptions) (*azeventhubs.CheckpointStore, error) { - bs := &blobStore{ +func NewBlobStore(containerClient *container.Client, options *BlobStoreOptions) (*BlobStore, error) { + return &BlobStore{ cc: containerClient, - } - - return &azeventhubs.CheckpointStore{ - ClaimOwnership: bs.ClaimOwnership, - ListCheckpoints: bs.ListCheckpoints, - ListOwnership: bs.ListOwnership, - SetCheckpoint: bs.SetCheckpoint, }, nil } @@ -55,7 +48,7 @@ func NewBlobStore(containerClient *container.Client, options *BlobStoreOptions) // // If we fail to claim ownership because of another update then it will be omitted from the // returned slice of [Ownership]'s. It is not considered an error. -func (b *blobStore) ClaimOwnership(ctx context.Context, partitionOwnership []azeventhubs.Ownership, options *azeventhubs.ClaimOwnershipOptions) ([]azeventhubs.Ownership, error) { +func (b *BlobStore) ClaimOwnership(ctx context.Context, partitionOwnership []azeventhubs.Ownership, options *azeventhubs.ClaimOwnershipOptions) ([]azeventhubs.Ownership, error) { var ownerships []azeventhubs.Ownership // TODO: in parallel? @@ -90,7 +83,7 @@ func (b *blobStore) ClaimOwnership(ctx context.Context, partitionOwnership []aze } // ListCheckpoints lists all the available checkpoints. -func (b *blobStore) ListCheckpoints(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *azeventhubs.ListCheckpointsOptions) ([]azeventhubs.Checkpoint, error) { +func (b *BlobStore) ListCheckpoints(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *azeventhubs.ListCheckpointsOptions) ([]azeventhubs.Checkpoint, error) { prefix, err := prefixForCheckpointBlobs(azeventhubs.Checkpoint{ FullyQualifiedNamespace: fullyQualifiedNamespace, EventHubName: eventHubName, @@ -141,7 +134,7 @@ func (b *blobStore) ListCheckpoints(ctx context.Context, fullyQualifiedNamespace var partitionIDRegexp = regexp.MustCompile("[^/]+?$") // ListOwnership lists all ownerships. -func (b *blobStore) ListOwnership(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *azeventhubs.ListOwnershipOptions) ([]azeventhubs.Ownership, error) { +func (b *BlobStore) ListOwnership(ctx context.Context, fullyQualifiedNamespace string, eventHubName string, consumerGroup string, options *azeventhubs.ListOwnershipOptions) ([]azeventhubs.Ownership, error) { prefix, err := prefixForOwnershipBlobs(azeventhubs.Ownership{ FullyQualifiedNamespace: fullyQualifiedNamespace, EventHubName: eventHubName, @@ -193,7 +186,7 @@ func (b *blobStore) ListOwnership(ctx context.Context, fullyQualifiedNamespace s // SetCheckpoint updates a specific checkpoint with a sequence and offset. // // NOTE: This function doesn't attempt to prevent simultaneous checkpoint updates - ownership is assumed. -func (b *blobStore) SetCheckpoint(ctx context.Context, checkpoint azeventhubs.Checkpoint, options *azeventhubs.SetCheckpointOptions) error { +func (b *BlobStore) SetCheckpoint(ctx context.Context, checkpoint azeventhubs.Checkpoint, options *azeventhubs.SetCheckpointOptions) error { blobName, err := nameForCheckpointBlob(checkpoint) if err != nil { @@ -204,7 +197,7 @@ func (b *blobStore) SetCheckpoint(ctx context.Context, checkpoint azeventhubs.Ch return err } -func (b *blobStore) setOwnershipMetadata(ctx context.Context, blobName string, ownership azeventhubs.Ownership) (*time.Time, azcore.ETag, error) { +func (b *BlobStore) setOwnershipMetadata(ctx context.Context, blobName string, ownership azeventhubs.Ownership) (*time.Time, azcore.ETag, error) { blobMetadata := newOwnershipBlobMetadata(ownership) blobClient := b.cc.NewBlockBlobClient(blobName) @@ -247,7 +240,7 @@ func (b *blobStore) setOwnershipMetadata(ctx context.Context, blobName string, o // // NOTE: unlike [setOwnershipMetadata] this function doesn't attempt to prevent simultaneous // checkpoint updates - ownership is assumed. -func (b *blobStore) setCheckpointMetadata(ctx context.Context, blobName string, checkpoint azeventhubs.Checkpoint) (*time.Time, azcore.ETag, error) { +func (b *BlobStore) setCheckpointMetadata(ctx context.Context, blobName string, checkpoint azeventhubs.Checkpoint) (*time.Time, azcore.ETag, error) { blobMetadata := newCheckpointBlobMetadata(checkpoint) blobClient := b.cc.NewBlockBlobClient(blobName) diff --git a/sdk/messaging/azeventhubs/example_consuming_with_checkpoints_test.go b/sdk/messaging/azeventhubs/example_consuming_with_checkpoints_test.go index 70b37eeea2d0..c02ad5804ddd 100644 --- a/sdk/messaging/azeventhubs/example_consuming_with_checkpoints_test.go +++ b/sdk/messaging/azeventhubs/example_consuming_with_checkpoints_test.go @@ -154,7 +154,7 @@ func shutdownPartitionResources(partitionClient *azeventhubs.ProcessorPartitionC defer partitionClient.Close(context.TODO()) } -func createClientsForExample(eventHubConnectionString, eventHubName, storageConnectionString, storageContainerName string) (*azeventhubs.ConsumerClient, *azeventhubs.CheckpointStore, error) { +func createClientsForExample(eventHubConnectionString, eventHubName, storageConnectionString, storageContainerName string) (*azeventhubs.ConsumerClient, azeventhubs.CheckpointStore, error) { // NOTE: the storageContainerName must exist before the checkpoint store can be used. azBlobContainerClient, err := container.NewClientFromConnectionString(storageConnectionString, storageContainerName, nil) diff --git a/sdk/messaging/azeventhubs/inmemory_checkpoint_store_test.go b/sdk/messaging/azeventhubs/inmemory_checkpoint_store_test.go index 28a29ddd7de4..44aacfa626d0 100644 --- a/sdk/messaging/azeventhubs/inmemory_checkpoint_store_test.go +++ b/sdk/messaging/azeventhubs/inmemory_checkpoint_store_test.go @@ -167,24 +167,13 @@ type testCheckpointStore struct { ownershipMu sync.RWMutex ownerships map[string]Ownership - - store *CheckpointStore } func newCheckpointStoreForTest() *testCheckpointStore { - cps := &testCheckpointStore{ + return &testCheckpointStore{ checkpoints: map[string]Checkpoint{}, ownerships: map[string]Ownership{}, } - - cps.store = &CheckpointStore{ - ClaimOwnership: cps.ClaimOwnership, - ListCheckpoints: cps.ListCheckpoints, - ListOwnership: cps.ListOwnership, - SetCheckpoint: cps.SetCheckpoint, - } - - return cps } func (cps *testCheckpointStore) ExpireOwnership(o Ownership) { diff --git a/sdk/messaging/azeventhubs/internal/eh/stress/tests/processor_stress_tester.go b/sdk/messaging/azeventhubs/internal/eh/stress/tests/processor_stress_tester.go index 3c46310561dd..9e9a48032059 100644 --- a/sdk/messaging/azeventhubs/internal/eh/stress/tests/processor_stress_tester.go +++ b/sdk/messaging/azeventhubs/internal/eh/stress/tests/processor_stress_tester.go @@ -43,7 +43,7 @@ type processorStressTest struct { prefetch int32 sleepAfterFn func() - checkpointStore *azeventhubs.CheckpointStore + checkpointStore azeventhubs.CheckpointStore } func newProcessorStressTest(args []string) (*processorStressTest, error) { diff --git a/sdk/messaging/azeventhubs/processor.go b/sdk/messaging/azeventhubs/processor.go index c34e469b4258..5eae14183d4c 100644 --- a/sdk/messaging/azeventhubs/processor.go +++ b/sdk/messaging/azeventhubs/processor.go @@ -88,7 +88,7 @@ type StartPositions struct { type Processor struct { ownershipUpdateInterval time.Duration defaultStartPositions StartPositions - checkpointStore *CheckpointStore + checkpointStore CheckpointStore prefetch int32 // consumerClient is actually a *azeventhubs.ConsumerClient @@ -114,11 +114,11 @@ type consumerClientForProcessor interface { // type or the [example_consuming_with_checkpoints_test.go] for an example. // // [example_consuming_with_checkpoints_test.go]: https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/messaging/azeventhubs/example_consuming_with_checkpoints_test.go -func NewProcessor(consumerClient *ConsumerClient, checkpointStore *CheckpointStore, options *ProcessorOptions) (*Processor, error) { +func NewProcessor(consumerClient *ConsumerClient, checkpointStore CheckpointStore, options *ProcessorOptions) (*Processor, error) { return newProcessorImpl(consumerClient, checkpointStore, options) } -func newProcessorImpl(consumerClient consumerClientForProcessor, checkpointStore *CheckpointStore, options *ProcessorOptions) (*Processor, error) { +func newProcessorImpl(consumerClient consumerClientForProcessor, checkpointStore CheckpointStore, options *ProcessorOptions) (*Processor, error) { if options == nil { options = &ProcessorOptions{} } diff --git a/sdk/messaging/azeventhubs/processor_load_balancer.go b/sdk/messaging/azeventhubs/processor_load_balancer.go index 63788c830d5f..e708c6dd514b 100644 --- a/sdk/messaging/azeventhubs/processor_load_balancer.go +++ b/sdk/messaging/azeventhubs/processor_load_balancer.go @@ -15,7 +15,7 @@ import ( ) type processorLoadBalancer struct { - checkpointStore *CheckpointStore + checkpointStore CheckpointStore details consumerClientDetails strategy ProcessorStrategy partitionExpirationDuration time.Duration @@ -24,7 +24,7 @@ type processorLoadBalancer struct { rnd *rand.Rand } -func newProcessorLoadBalancer(checkpointStore *CheckpointStore, details consumerClientDetails, strategy ProcessorStrategy, partitionExpiration time.Duration) *processorLoadBalancer { +func newProcessorLoadBalancer(checkpointStore CheckpointStore, details consumerClientDetails, strategy ProcessorStrategy, partitionExpiration time.Duration) *processorLoadBalancer { return &processorLoadBalancer{ checkpointStore: checkpointStore, details: details, diff --git a/sdk/messaging/azeventhubs/processor_load_balancers_test.go b/sdk/messaging/azeventhubs/processor_load_balancers_test.go index 3cc7e286fe3f..ee9b56a86ded 100644 --- a/sdk/messaging/azeventhubs/processor_load_balancers_test.go +++ b/sdk/messaging/azeventhubs/processor_load_balancers_test.go @@ -20,7 +20,7 @@ func TestProcessorLoadBalancers_Greedy_EnoughUnownedPartitions(t *testing.T) { }, nil) require.NoError(t, err) - lb := newProcessorLoadBalancer(cps.store, newTestConsumerDetails("new-client"), ProcessorStrategyGreedy, time.Hour) + lb := newProcessorLoadBalancer(cps, newTestConsumerDetails("new-client"), ProcessorStrategyGreedy, time.Hour) // "0" and "3" are already claimed, so we'll pick up the 2 free partitions. ownerships, err := lb.LoadBalance(context.Background(), []string{"0", "1", "2", "3"}) @@ -42,7 +42,7 @@ func TestProcessorLoadBalancers_Balanced_UnownedPartitions(t *testing.T) { }, nil) require.NoError(t, err) - lb := newProcessorLoadBalancer(cps.store, newTestConsumerDetails("new-client"), ProcessorStrategyBalanced, time.Hour) + lb := newProcessorLoadBalancer(cps, newTestConsumerDetails("new-client"), ProcessorStrategyBalanced, time.Hour) // "0" and "3" are already claimed, so we'll pick up one partition each time we load balance ownerships, err := lb.LoadBalance(context.Background(), []string{"0", "1", "2", "3"}) @@ -53,7 +53,7 @@ func TestProcessorLoadBalancers_Balanced_UnownedPartitions(t *testing.T) { require.NoError(t, err) require.Equal(t, 2, len(ownerships)) - requireBalanced(t, cps.store, 4, 2) + requireBalanced(t, cps, 4, 2) } func TestProcessorLoadBalancers_Greedy_ForcedToSteal(t *testing.T) { @@ -79,7 +79,7 @@ func TestProcessorLoadBalancers_Greedy_ForcedToSteal(t *testing.T) { }, nil) require.NoError(t, err) - lb := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(stealingClientID), ProcessorStrategyGreedy, time.Hour) + lb := newProcessorLoadBalancer(cps, newTestConsumerDetails(stealingClientID), ProcessorStrategyGreedy, time.Hour) ownerships, err := lb.LoadBalance(context.Background(), []string{"0", "1", "2", "3", "4"}) require.NoError(t, err) @@ -122,13 +122,13 @@ func TestProcessorLoadBalancers_AnyStrategy_GrabExpiredPartition(t *testing.T) { // expire the middle partition (simulating that ClientC died, so nobody's updated it's ownership in awhile) cps.ExpireOwnership(middleOwnership) - lb := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(clientB), strategy, time.Hour) + lb := newProcessorLoadBalancer(cps, newTestConsumerDetails(clientB), strategy, time.Hour) ownerships, err := lb.LoadBalance(context.Background(), []string{"0", "1", "2", "3", "4"}) require.NoError(t, err) require.NotEmpty(t, mapToStrings(ownerships, extractPartitionID)) - requireBalanced(t, cps.store, 5, 2) + requireBalanced(t, cps, 5, 2) }) } } @@ -151,21 +151,21 @@ func TestProcessorLoadBalancers_AnyStrategy_FullyBalancedOdd(t *testing.T) { require.NoError(t, err) { - lbB := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(clientB), strategy, time.Hour) + lbB := newProcessorLoadBalancer(cps, newTestConsumerDetails(clientB), strategy, time.Hour) ownerships, err := lbB.LoadBalance(context.Background(), []string{"0", "1", "2", "3", "4"}) require.NoError(t, err) require.Equal(t, []string{"3", "4"}, mapToStrings(ownerships, extractPartitionID)) - requireBalanced(t, cps.store, 5, 2) + requireBalanced(t, cps, 5, 2) } { - lbA := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(clientA), strategy, time.Hour) + lbA := newProcessorLoadBalancer(cps, newTestConsumerDetails(clientA), strategy, time.Hour) ownerships, err := lbA.LoadBalance(context.Background(), []string{"0", "1", "2", "3", "4"}) require.NoError(t, err) require.Equal(t, []string{"0", "1", "2"}, mapToStrings(ownerships, extractPartitionID)) - requireBalanced(t, cps.store, 5, 2) + requireBalanced(t, cps, 5, 2) } }) } @@ -188,21 +188,21 @@ func TestProcessorLoadBalancers_AnyStrategy_FullyBalancedEven(t *testing.T) { require.NoError(t, err) { - lbB := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(clientB), strategy, time.Hour) + lbB := newProcessorLoadBalancer(cps, newTestConsumerDetails(clientB), strategy, time.Hour) ownerships, err := lbB.LoadBalance(context.Background(), []string{"0", "1", "2", "3"}) require.NoError(t, err) require.Equal(t, []string{"2", "3"}, mapToStrings(ownerships, extractPartitionID)) - requireBalanced(t, cps.store, 4, 2) + requireBalanced(t, cps, 4, 2) } { - lbA := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(clientA), strategy, time.Hour) + lbA := newProcessorLoadBalancer(cps, newTestConsumerDetails(clientA), strategy, time.Hour) ownerships, err := lbA.LoadBalance(context.Background(), []string{"0", "1", "2", "3"}) require.NoError(t, err) require.Equal(t, []string{"0", "1"}, mapToStrings(ownerships, extractPartitionID)) - requireBalanced(t, cps.store, 4, 2) + requireBalanced(t, cps, 4, 2) } }) } @@ -225,13 +225,13 @@ func TestProcessorLoadBalancers_Any_GrabExtraPartitionBecauseAboveMax(t *testing }, nil) require.NoError(t, err) - lb := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(clientB), strategy, time.Hour) + lb := newProcessorLoadBalancer(cps, newTestConsumerDetails(clientB), strategy, time.Hour) ownerships, err := lb.LoadBalance(context.Background(), []string{"0", "1", "2", "3", "4"}) require.NoError(t, err) require.NotEmpty(t, mapToStrings(ownerships, extractPartitionID)) - requireBalanced(t, cps.store, 5, 2) + requireBalanced(t, cps, 5, 2) }) } } @@ -254,7 +254,7 @@ func TestProcessorLoadBalancers_AnyStrategy_StealsToBalance(t *testing.T) { require.NoError(t, err) { - tooManyLB := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(lotsClientID), strategy, time.Hour) + tooManyLB := newProcessorLoadBalancer(cps, newTestConsumerDetails(lotsClientID), strategy, time.Hour) require.NoError(t, err) ownerships, err := tooManyLB.LoadBalance(context.Background(), allPartitionIDs) @@ -266,7 +266,7 @@ func TestProcessorLoadBalancers_AnyStrategy_StealsToBalance(t *testing.T) { } { - tooFewLB := newProcessorLoadBalancer(cps.store, newTestConsumerDetails(littleClientID), strategy, time.Hour) + tooFewLB := newProcessorLoadBalancer(cps, newTestConsumerDetails(littleClientID), strategy, time.Hour) require.NoError(t, err) // either strategy will balance here by stealing. @@ -275,7 +275,7 @@ func TestProcessorLoadBalancers_AnyStrategy_StealsToBalance(t *testing.T) { require.Equal(t, 2, len(ownerships)) } - requireBalanced(t, cps.store, len(allPartitionIDs), 2) + requireBalanced(t, cps, len(allPartitionIDs), 2) }) } } @@ -283,12 +283,12 @@ func TestProcessorLoadBalancers_AnyStrategy_StealsToBalance(t *testing.T) { func TestProcessorLoadBalancers_InvalidStrategy(t *testing.T) { cps := newCheckpointStoreForTest() - lb := newProcessorLoadBalancer(cps.store, newTestConsumerDetails("does not matter"), "", time.Hour) + lb := newProcessorLoadBalancer(cps, newTestConsumerDetails("does not matter"), "", time.Hour) ownerships, err := lb.LoadBalance(context.Background(), []string{"0"}) require.Nil(t, ownerships) require.EqualError(t, err, "invalid load balancing strategy ''") - lb = newProcessorLoadBalancer(cps.store, newTestConsumerDetails("does not matter"), "super-greedy", time.Hour) + lb = newProcessorLoadBalancer(cps, newTestConsumerDetails("does not matter"), "super-greedy", time.Hour) ownerships, err = lb.LoadBalance(context.Background(), []string{"0"}) require.Nil(t, ownerships) require.EqualError(t, err, "invalid load balancing strategy 'super-greedy'") @@ -328,7 +328,7 @@ func newTestConsumerDetails(clientID string) consumerClientDetails { } } -func requireBalanced(t *testing.T, cps *CheckpointStore, totalPartitions int, numConsumers int) { +func requireBalanced(t *testing.T, cps CheckpointStore, totalPartitions int, numConsumers int) { ownerships, err := cps.ListOwnership(context.Background(), testEventHubFQDN, testEventHubName, testConsumerGroup, nil) require.NoError(t, err) diff --git a/sdk/messaging/azeventhubs/processor_partition_client.go b/sdk/messaging/azeventhubs/processor_partition_client.go index 35360032c93e..cc52c533da5a 100644 --- a/sdk/messaging/azeventhubs/processor_partition_client.go +++ b/sdk/messaging/azeventhubs/processor_partition_client.go @@ -20,7 +20,7 @@ import "context" type ProcessorPartitionClient struct { partitionID string innerClient *PartitionClient - checkpointStore *CheckpointStore + checkpointStore CheckpointStore cleanupFn func() consumerClientDetails consumerClientDetails } diff --git a/sdk/messaging/azeventhubs/processor_test.go b/sdk/messaging/azeventhubs/processor_test.go index f6bef27beae1..e08771110439 100644 --- a/sdk/messaging/azeventhubs/processor_test.go +++ b/sdk/messaging/azeventhubs/processor_test.go @@ -413,7 +413,7 @@ func processEventsForTest(t *testing.T, producerClient *azeventhubs.ProducerClie } } -func printOwnerships(ctx context.Context, t *testing.T, cps *azeventhubs.CheckpointStore, testParams test.ConnectionParamsForTest, partitionIDs []string, expectedConsumers int) { +func printOwnerships(ctx context.Context, t *testing.T, cps azeventhubs.CheckpointStore, testParams test.ConnectionParamsForTest, partitionIDs []string, expectedConsumers int) { max := len(partitionIDs) / expectedConsumers if len(partitionIDs)%expectedConsumers > 0 { diff --git a/sdk/messaging/azeventhubs/processor_unit_test.go b/sdk/messaging/azeventhubs/processor_unit_test.go index cb2142b6ce44..950ec342d18e 100644 --- a/sdk/messaging/azeventhubs/processor_unit_test.go +++ b/sdk/messaging/azeventhubs/processor_unit_test.go @@ -20,7 +20,7 @@ import ( func TestUnit_Processor_loadBalancing(t *testing.T) { cps := newCheckpointStoreForTest() - firstProcessor := newProcessorForTest(t, "first-processor", cps.store) + firstProcessor := newProcessorForTest(t, "first-processor", cps) newTestOwnership := func(base Ownership) Ownership { base.ConsumerGroup = "consumer-group" base.EventHubName = "event-hub" @@ -80,7 +80,7 @@ func TestUnit_Processor_loadBalancing(t *testing.T) { // 1 of those partitions is owned by our client ("first-processor") // 2 are still available. - secondProcessor := newProcessorForTest(t, "second-processor", cps.store) + secondProcessor := newProcessorForTest(t, "second-processor", cps) // when we ask for available partitions we take into account the owners that are // present in the checkpoint store and ourselves, since we're about to try to claim @@ -133,7 +133,7 @@ func TestUnit_Processor_loadBalancing(t *testing.T) { func TestUnit_Processor_Run(t *testing.T) { cps := newCheckpointStoreForTest() - processor, err := newProcessorImpl(simpleFakeConsumerClient(), cps.store, &ProcessorOptions{ + processor, err := newProcessorImpl(simpleFakeConsumerClient(), cps, &ProcessorOptions{ PartitionExpirationDuration: time.Hour, }) @@ -180,7 +180,7 @@ func TestUnit_Processor_Run_singleConsumerPerPartition(t *testing.T) { }, } - processor, err := newProcessorImpl(cc, cps.store, &ProcessorOptions{ + processor, err := newProcessorImpl(cc, cps, &ProcessorOptions{ PartitionExpirationDuration: time.Hour, }) require.NoError(t, err) @@ -242,7 +242,7 @@ func TestUnit_Processor_Run_startPosition(t *testing.T) { return newFakePartitionClient(partitionID, offsetExpr), nil } - processor, err := newProcessorImpl(fakeConsumerClient, cps.store, &ProcessorOptions{ + processor, err := newProcessorImpl(fakeConsumerClient, cps, &ProcessorOptions{ PartitionExpirationDuration: time.Hour, }) require.NoError(t, err) @@ -298,7 +298,7 @@ func TestUnit_Processor_Run_cancellation(t *testing.T) { FullyQualifiedNamespace: "fqdn", ClientID: "my-client-id", }, - }, cps.store, &ProcessorOptions{ + }, cps, &ProcessorOptions{ PartitionExpirationDuration: time.Hour, }) @@ -329,7 +329,7 @@ func updateDynamicData(t *testing.T, src Ownership, expected Ownership, allParti return expected } -func newProcessorForTest(t *testing.T, clientID string, cps *CheckpointStore) *Processor { +func newProcessorForTest(t *testing.T, clientID string, cps CheckpointStore) *Processor { processor, err := newProcessorImpl(&fakeConsumerClient{ details: consumerClientDetails{ ConsumerGroup: "consumer-group",