/// <summary>
 /// Rotates the Key Encryption Key (KEK) for a client-side encrypted blob without
 /// needing to reupload the entire blob.
 /// </summary>
 /// <param name="client">
 /// Client to the blob.
 /// </param>
 /// <param name="options">
 /// Optional parameters for the operation.
 /// </param>
 /// <param name="cancellationToken">
 /// Cancellation token for the operation.
 /// </param>
 public static async Task UpdateClientSideKeyEncryptionKeyAsync(
     this BlobClient client,
     UpdateClientSideKeyEncryptionKeyOptions options = default,
     CancellationToken cancellationToken             = default)
 => await UpdateClientsideKeyEncryptionKeyInternal(
     client,
     options,
     async : true,
     cancellationToken).ConfigureAwait(false);
 /// <summary>
 /// Rotates the Key Encryption Key (KEK) for a client-side encrypted blob without
 /// needing to reupload the entire blob.
 /// </summary>
 /// <param name="client">
 /// Client to the blob.
 /// </param>
 /// <param name="options">
 /// Optional parameters for the operation.
 /// </param>
 /// <param name="cancellationToken">
 /// Cancellation token for the operation.
 /// </param>
 public static void UpdateClientSideKeyEncryptionKey(
     this BlobClient client,
     UpdateClientSideKeyEncryptionKeyOptions options = default,
     CancellationToken cancellationToken             = default)
 => UpdateClientsideKeyEncryptionKeyInternal(
     client,
     options,
     async: false,
     cancellationToken).EnsureCompleted();
        private static async Task UpdateClientsideKeyEncryptionKeyInternal(
            BlobClient client,
            UpdateClientSideKeyEncryptionKeyOptions options,
            bool async,
            CancellationToken cancellationToken)
        {
            // argument validation
            Argument.AssertNotNull(client, nameof(client));
            ClientSideEncryptionOptions operationEncryptionOptions = options?.EncryptionOptionsOverride
                                                                     ?? client.ClientSideEncryption
                                                                     ?? throw new ArgumentException($"{nameof(ClientSideEncryptionOptions)} are not configured on this client and none were provided for the operation.");

            Argument.AssertNotNull(operationEncryptionOptions.KeyEncryptionKey, nameof(ClientSideEncryptionOptions.KeyEncryptionKey));
            Argument.AssertNotNull(operationEncryptionOptions.KeyResolver, nameof(ClientSideEncryptionOptions.KeyResolver));
            Argument.AssertNotNull(operationEncryptionOptions.KeyWrapAlgorithm, nameof(ClientSideEncryptionOptions.KeyWrapAlgorithm));

            using (client.ClientConfiguration.Pipeline.BeginLoggingScope(nameof(BlobClient)))
            {
                client.ClientConfiguration.Pipeline.LogMethodEnter(
                    nameof(BlobBaseClient),
                    message:
                    $"{nameof(Uri)}: {client.Uri}\n" +
                    $"{nameof(options.Conditions)}: {options?.Conditions}");

                DiagnosticScope scope = client.ClientConfiguration.ClientDiagnostics.CreateScope($"{nameof(BlobClient)}.{nameof(UpdateClientSideKeyEncryptionKey)}");

                try
                {
                    // hold onto etag, metadata, encryptiondata
                    BlobProperties getPropertiesResponse = await client.GetPropertiesInternal(options?.Conditions, async, cancellationToken).ConfigureAwait(false);

                    ETag etag = getPropertiesResponse.ETag;
                    IDictionary <string, string> metadata = getPropertiesResponse.Metadata;
                    EncryptionData encryptionData         = BlobClientSideDecryptor.GetAndValidateEncryptionDataOrDefault(metadata)
                                                            ?? throw new InvalidOperationException("Resource has no client-side encryption key to rotate.");

                    // rotate keywrapping
                    byte[] newWrappedKey = await WrapKeyInternal(
                        await UnwrapKeyInternal(
                            encryptionData,
                            operationEncryptionOptions.KeyResolver,
                            async,
                            cancellationToken).ConfigureAwait(false),
                        operationEncryptionOptions.KeyWrapAlgorithm,
                        operationEncryptionOptions.KeyEncryptionKey,
                        async,
                        cancellationToken).ConfigureAwait(false);

                    // set new wrapped key info and reinsert into metadata
                    encryptionData.WrappedContentKey = new KeyEnvelope
                    {
                        EncryptedKey = newWrappedKey,
                        Algorithm    = operationEncryptionOptions.KeyWrapAlgorithm,
                        KeyId        = operationEncryptionOptions.KeyEncryptionKey.KeyId
                    };
                    metadata[Constants.ClientSideEncryption.EncryptionDataKey] = EncryptionDataSerializer.Serialize(encryptionData);

                    // update blob ONLY IF ETAG MATCHES (do not take chances encryption info is now out of sync)
                    BlobRequestConditions modifiedRequestConditions = BlobRequestConditions.CloneOrDefault(options?.Conditions) ?? new BlobRequestConditions();
                    modifiedRequestConditions.IfMatch = etag;
                    await client.SetMetadataInternal(metadata, modifiedRequestConditions, async, cancellationToken).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    client.ClientConfiguration.Pipeline.LogException(ex);
                    scope.Failed(ex);
                    throw;
                }
                finally
                {
                    client.ClientConfiguration.Pipeline.LogMethodExit(nameof(BlobBaseClient));
                    scope.Dispose();
                }
            }
        }