Rajnish Noonia

Tag: C#

  • .NET 8 Cryptography: AES-GCM, PBKDF2, and Secure Key Management

    Data encryption in financial systems is a regulatory and architectural baseline. With .NET 8 LTS (November 2023), the System.Security.Cryptography namespace has changed significantly: several APIs widely used in earlier .NET versions are now obsolete or removed, and the recommended patterns for symmetric encryption, key derivation, and asymmetric key transport have all been updated. This post covers the current .NET 8 approach to AES encryption (both CBC and GCM modes), PBKDF2 key derivation, RSA key exchange, and practical hybrid encryption — with guidance on what changed and why it matters for enterprise financial applications.

    What Changed from Earlier .NET Versions

    If you worked with .NET cryptography before .NET 6, several classes you may have relied on are now obsolete or removed in .NET 8:

    • RijndaelManaged — removed in .NET 8. Use Aes.Create() instead. RijndaelManaged throws PlatformNotSupportedException on .NET 8.
    • MD5CryptoServiceProvider, SHA1CryptoServiceProvider — deprecated. Use the static MD5.HashData() and SHA256.HashData() methods introduced in .NET 7.
    • PasswordDeriveBytes — obsolete. Use Rfc2898DeriveBytes (PBKDF2) with SHA-256 or SHA-512, or the new static Rfc2898DeriveBytes.Pbkdf2() method in .NET 6+.
    • RSACryptoServiceProvider — works but is a legacy Windows CAPI wrapper. Prefer RSA.Create() for cross-platform behaviour and modern padding support.

    Symmetric Encryption — AES in .NET 8

    AES is the standard for symmetric encryption. .NET 8 supports two modes that matter architecturally: AES-CBC (cipher-block chaining — confidentiality only) and AES-GCM (Galois/Counter Mode — authenticated encryption providing both confidentiality and integrity). For any new system, prefer AES-GCM: it detects ciphertext tampering before decryption, which AES-CBC cannot. AES-CBC without a separate MAC is vulnerable to padding oracle attacks.

    AES-GCM — Authenticated Encryption (Recommended)

    using System.Security.Cryptography;
    
    public static class AesGcmEncryption
    {
        public static (byte[] ciphertext, byte[] nonce, byte[] tag) Encrypt(
            byte[] plaintext, byte[] key)
        {
            var nonce = RandomNumberGenerator.GetBytes(AesGcm.NonceByteSizes.MaxSize);
            var ciphertext = new byte[plaintext.Length];
            var tag = new byte[AesGcm.TagByteSizes.MaxSize];
    
            using var aes = new AesGcm(key, AesGcm.TagByteSizes.MaxSize);
            aes.Encrypt(nonce, plaintext, ciphertext, tag);
    
            return (ciphertext, nonce, tag);
        }
    
        public static byte[] Decrypt(byte[] ciphertext, byte[] key, byte[] nonce, byte[] tag)
        {
            var plaintext = new byte[ciphertext.Length];
    
            using var aes = new AesGcm(key, AesGcm.TagByteSizes.MaxSize);
            aes.Decrypt(nonce, ciphertext, tag, plaintext);
    
            return plaintext;
        }
    }

    AES-CBC (where backward compatibility requires it)

    using System.Security.Cryptography;
    
    public static class AesCbcEncryption
    {
        public static (byte[] ciphertext, byte[] iv) Encrypt(byte[] plaintext, byte[] key)
        {
            using var aes = Aes.Create();
            aes.Key = key;
            aes.GenerateIV();
    
            using var ms = new MemoryStream();
            using var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write);
            cs.Write(plaintext);
            cs.FlushFinalBlock();
    
            return (ms.ToArray(), aes.IV);
        }
    
        public static byte[] Decrypt(byte[] ciphertext, byte[] key, byte[] iv)
        {
            using var aes = Aes.Create();
            aes.Key = key;
            aes.IV = iv;
    
            using var ms = new MemoryStream(ciphertext);
            using var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read);
            using var output = new MemoryStream();
            cs.CopyTo(output);
            return output.ToArray();
        }
    }

    Key Derivation — PBKDF2 with SHA-256

    Never use a raw password as an encryption key. Password-based keys must be derived using a key derivation function that applies a computationally expensive hash to slow down brute-force attacks. PBKDF2 (Password-Based Key Derivation Function 2) is the NIST-recommended approach. In .NET 8, use the static Rfc2898DeriveBytes.Pbkdf2() method with SHA-256 and a minimum of 600,000 iterations — the 2023 OWASP recommendation for PBKDF2-HMAC-SHA256.

    using System.Security.Cryptography;
    
    public static class KeyDerivation
    {
        public static byte[] DeriveKey(string password, byte[] salt, int keyLengthBytes = 32)
        {
            return Rfc2898DeriveBytes.Pbkdf2(
                password: password,
                salt: salt,
                iterations: 600_000,
                hashAlgorithm: HashAlgorithmName.SHA256,
                outputLength: keyLengthBytes);
        }
    
        public static byte[] GenerateSalt(int length = 16)
            => RandomNumberGenerator.GetBytes(length);
    }

    For machine-to-machine encryption in production financial services, avoid password-derived keys entirely. Generate cryptographically random AES keys and store them in a managed key store — Azure Key Vault, AWS Secrets Manager, or an HSM. Key rotation, access control, and audit logs come for free from the platform rather than being built from scratch.

    Asymmetric Encryption — RSA in .NET 8

    RSA is used for key transport and digital signatures, not for bulk data encryption — it is orders of magnitude slower than AES and limited by key size in the amount of data it can encrypt directly. The standard architectural pattern is hybrid encryption: encrypt the data payload with AES-GCM using a freshly generated key, then encrypt that AES key with the recipient’s RSA public key.

    using System.Security.Cryptography;
    
    public static class RsaEncryption
    {
        public static RSA CreateKeyPair() => RSA.Create(4096);
    
        public static byte[] Encrypt(byte[] data, RSA publicKey)
            => publicKey.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
    
        public static byte[] Decrypt(byte[] ciphertext, RSA privateKey)
            => privateKey.Decrypt(ciphertext, RSAEncryptionPadding.OaepSHA256);
    
        public static byte[] Sign(byte[] data, RSA privateKey)
            => privateKey.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
    
        public static bool Verify(byte[] data, byte[] signature, RSA publicKey)
            => publicKey.VerifyData(data, signature,
                   HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
    }

    Hybrid Encryption Pattern

    The practical pattern for encrypting arbitrarily large payloads in financial systems combines both approaches — RSA for secure key transport, AES-GCM for the actual data — and uses CryptographicOperations.ZeroMemory to scrub key material from managed memory after use.

    using System.Security.Cryptography;
    
    public static class HybridEncryption
    {
        public static (byte[] encryptedKey, byte[] nonce, byte[] tag, byte[] ciphertext)
            Encrypt(byte[] plaintext, RSA recipientPublicKey)
        {
            var aesKey = RandomNumberGenerator.GetBytes(32);
            try
            {
                var (ciphertext, nonce, tag) = AesGcmEncryption.Encrypt(plaintext, aesKey);
                var encryptedKey = RsaEncryption.Encrypt(aesKey, recipientPublicKey);
                return (encryptedKey, nonce, tag, ciphertext);
            }
            finally
            {
                CryptographicOperations.ZeroMemory(aesKey);
            }
        }
    
        public static byte[] Decrypt(
            byte[] encryptedKey, byte[] nonce, byte[] tag,
            byte[] ciphertext, RSA recipientPrivateKey)
        {
            var aesKey = RsaEncryption.Decrypt(encryptedKey, recipientPrivateKey);
            try
            {
                return AesGcmEncryption.Decrypt(ciphertext, aesKey, nonce, tag);
            }
            finally
            {
                CryptographicOperations.ZeroMemory(aesKey);
            }
        }
    }

    Architectural Guidance

    • Prefer AES-GCM over AES-CBC for new systems. Authenticated encryption prevents bit-flipping and padding oracle attacks that AES-CBC alone cannot stop.
    • Never reuse a nonce with the same AES-GCM key. Nonce reuse in GCM completely breaks confidentiality — both the keystream and the plaintext become recoverable. Generate a fresh random nonce per message.
    • Use RSA-OAEP-SHA256 padding. PKCS#1 v1.5 padding is vulnerable to the Bleichenbacher adaptive chosen-ciphertext attack. OAEP is the current standard.
    • Use PBKDF2 with SHA-256 and ≥ 600,000 iterations for any password-derived key. For non-interactive M2M keys, use RandomNumberGenerator.GetBytes(32) and store in a managed secrets store.
    • Zero key material after use with CryptographicOperations.ZeroMemory. In long-running services, uncleared key bytes can persist in managed heap memory until the next GC cycle — and potentially be readable via memory dumps.
    • Store private keys outside the application. In production financial systems, RSA private keys belong in HSMs or platform key vaults (Azure Key Vault, AWS KMS). Never commit key material to configuration files or source control.
    • Use certificate-backed RSA where available. X.509 certificates provide key identity, expiry, and chain-of-trust that raw key bytes do not — essential for regulated environments such as capital markets platforms.
  • Reactive Extensions (Rx) Data Streaming

    You have probably heard about Reactive Extensions, a library from Microsoft that greatly simplifies working with asynchronous data streams and allows to query them with LINQ operators.In my previous post I briefly discussed about loading data via entity framework asynchronously however it lacks getting chunks of data. This post demonstrates how to use Reactive Extensions for loading data from database asynchronously in chunks covering brief around Reactive extension library.

    Reactive extensions (Rx)
    The Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs using observable sequences and LINQ-style query operators. Using Rx, developers represent asynchronous data streams with Observables, query asynchronous data streams using LINQ operators, and parameterize the concurrency in the asynchronous data streams using Schedulers. Simply put, Rx = Observables + LINQ + Schedulers.Data sequences can take many forms, such as a stream of data from a file or web service, web services requests, system notifications, or a series of events such as user input. Reactive Extensions represents all these data sequences as observable sequences. An application can subscribe to these observable sequences to receive asynchronous notifications as new data arrive. The Rx library is available for desktop application development in .NET. It is also released for Silverlight, Windows Phone 7 and JavaScript.

    Reactive programming allows you to turn those aspects of your code that are currently imperative into something much more event-driven and flexible.Reactive programming can be applied to a range of situations—from WPF applications to Windows Phone apps—to improve coding efficiency and boost performance.

    Code snippets to asynchronous load data via entity framework in batches of 200 records.

    public void LoadPostCodes()
    {
     btnStatus.Content = "Started";
     listBox1.Items.Clear();
     (from p in cx.MAS_PostCode select p)
     .ToObservable(Scheduler.NewThread)
     .Buffer(200)
     .ObserveOn(SynchronizationContext.Current)
     .Subscribe(ld =>
     {
     foreach (var item in ld)
     {
     ListBoxItem litem = new ListBoxItem();
     litem.Content = string.Format("{0} {1}", item.PC_PostCode, item.PC_Address1);
     listBox1.Items.Add(litem);
     listBox1.ScrollIntoView(litem);
     }
     button1.Content = listBox1.Items.Count.ToString();
     },
     () => { btnStatus.Content = "Finished"; }
     );
    }

    Have finished writing MVVM(Nano View model) based scenario & Rx implementations …

    Will write soon and publish code..

    Regards
    Rajnish

  • Enterprise configuration management

    Almost every application requires some form of configuration information. This information can be as simple as a database connection string or as complex as multipart and hierarchical user preference information. How and where to store an application’s configuration data are questions you often face as a developer.

    Any large enterprise application has many moving blocks. They all need to be configured for a proper working of the application. As the application size increases or for scalability the same configuration has to be repeated in different applications. For most applications once the configuration has been changed the application needs to be restarted.

    Sample Code (create a blank console project, add json.net nuget)

    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace CM
    {
        // configuration management API, 
        //1. allow clients specify typesafe models for configuations
        //2. Store flat data on server (table) which is easy to edit
        //3. can be extended to have inheritance of values (overrides)
        //4. can be extended to lock / unlock certain property by admins etc.
        
        class Program
        {
            //Sample configuration model
            internal class SampleConfigModal
            {
                public SampleConfigModal()
                {
                    Address = new Address();
                }
                public string Name { get; set; }
                public Address Address { get; set; }
                public int Age { get; set; }
    
    
            }
            public class Address
            {
                public string Street { get; set; }
    
            }
    
            // this is how client api will look like
            static void Main(string[] args)
            {
                // sample client code
                var data = new SampleConfigModal() { Name = "Rajnish", Age = 18, Address = new Address() { Street = "Oxley" } };
    
                // save Configuration
                SaveConfiguration("app", "section", data);
    
                //get Configuration
                var data2 = GetConfiguration("app", "section");
            }
    
            // Client side framework api -> call to rest end point
            private static T GetConfiguration(string appName, string SectionName) where T : new()
            {
                var defaultValue = new T();
                var samplePayload = JsonConvert.SerializeObject(defaultValue);
                var payload = GetConfiguration(appName, SectionName, samplePayload);
                return JsonConvert.DeserializeObject(payload);
            }
    
            // Client side framework api -> call to rest end point
            private static void SaveConfiguration(string appName, string SectionName, T data)
            {
                var payload = Newtonsoft.Json.JsonConvert.SerializeObject(data);
                SaveConfigurationa(appName, SectionName, payload);
            }
    
    
            //---------------------------------------- Server Code -------------------------- 
            //-------------- server has no knowledge of configuration structure or model
    
            private static Dictionary<string, string> storage;
    
            private static void SaveConfigurationa(string appName, string SectionName, string payload, string enumForHierarchyLevel = null)
            {
                // transformer
                var section = string.Format("{0}.{1}", appName, SectionName);
                var data = (JObject)JsonConvert.DeserializeObject(payload);
                var keyValueData = Flatten(data, section);
    
                // check if user has permission for level overrides
                // store with proper overides
    
                // store the flat list in sql or data 
                //| KEY |           |Value|            |OverrideType| - default,sysadmin,appadmin,groups,user etc
                //app.section.Name, Rajnish
                //app.section.Address.Street, Oxley
                //app.section.Age, 18
    
                storage = keyValueData;
            }
    
            private static string GetConfiguration(string appName, string SectionName, string samplePayload)
            {
                var section = string.Format("{0}.{1}", appName, SectionName);
                var data = (JObject)JsonConvert.DeserializeObject(samplePayload);
                var keyValueSample = Flatten(data, section);
                // update data from sql or data store
                // apply property override rules and get value from overrides if exists
                var keyValueData = keyValueSample.Select(x => new KeyValuePair<string, string>(x.Key, storage[x.Key]));
    
                //read these
                //app.section.Name, Rajnish
                //app.section.Address.Street, Oxley
                //app.section.Age, 18
    
                UnFlatten(data, section, keyValueData);
    
                var formatedData = JsonConvert.SerializeObject(data);
                /*
                 * {
                      "Name": "Rajnish",
                      "Address": {
                        "Street": "Oxley"
                      },
                      "Age": "18"
                    }
                 * */
                return formatedData;
    
            }
            
            // Server side json helper
    
            private static void UnFlatten(JObject jsonObject, string prefix, IEnumerable<KeyValuePair<string, string>> data)
            {
                foreach (var item in data)
                {
                    var keyName = item.Key.Substring(prefix.Length + 1);
                    var storageValue = item.Value;
                    if (keyName.Contains("."))
                    {
                        var keys = keyName.Split('.');
                        var jtoken = (JToken)jsonObject;
                        foreach (var k in keys)
                        {
                            jtoken = jtoken.SelectToken(k);
                        }
                        ((JValue)jtoken).Value = storageValue;
                    }
                    else
                    {
                        jsonObject[keyName] = storageValue;
                    }
                }
            }
    
            private static Dictionary<string, string> Flatten(JObject jsonObject, string prefix)
            {
    
                IEnumerable jTokens = jsonObject.Descendants().Where(p => p.Count() == 0);
                Dictionary<string, string> results = jTokens.Aggregate(new Dictionary<string, string>(), (properties, jToken) =>
                {
                    properties.Add(string.Format("{0}.{1}", prefix, jToken.Path), jToken.ToString());
                    return properties;
                });
                return results;
            }
        }
    }
    
    
  • TPL Dataflow – Concurrent Programming

    TPL DataFlow

    TPL Dataflow is an in-process actor library on top of the Task Parallel Library enabling more robust concurrent programming.

    Parallel computing is a form of computation in which multiple operations are carried out simultaneously.Parallel computing is closely related to asynchronous programming, using many of the same core concepts and support. Asynchronous programming is an approach to writing code that involves invoking operations such that they don’t block the current thread of execution.Many personal computers and workstations have two or four or 8 cores (that is, CPUs) that enable multiple threads to be executed simultaneously. Computers in the near future are expected to have significantly more cores. To take advantage of the hardware of today and tomorrow, you can parallelize your code to distribute work across multiple processors. In the past, parallelization required low-level manipulation of threads and locks.

    The purpose of the TPL is to make developers more productive by simplifying the process of adding parallelism and concurrency to applications. The TPL scales the degree of concurrency dynamically to most efficiently use all the processors that are available. In addition, the TPL handles the partitioning of the work, the scheduling of threads on the ThreadPool, cancellation support, state management, and other low-level details. By using TPL, you can maximize the performance of your code while focusing on the work that your program is designed to accomplish.

    Data parallelism refers to scenarios in which the same operation is performed concurrently (that is, in parallel) on elements in a source collection or array. In data parallel operations, the source collection is partitioned so that multiple threads can operate on different segments concurrently.

    The Task Parallel Library (TPL) is based on the concept of a task, which represents an asynchronous operation. In some ways, a task resembles a thread or ThreadPool work item, but at a higher level of abstraction. The term task parallelism refers to one or more independent tasks running concurrently. Tasks provide two primary benefits:More efficient and more scalable use of system resources & More programmatic control than is possible with a thread or work item.

    The concurrency models we has discussed so far have the notion of shared state (data) in common.Shared state can be accessed by multiple threads at the same time and must be thus protected, either by locking or by using transactions. Both, mutability and sharing of state are not just inherent for these models, they are also inherent for the complexities.Unfortunately, programmers have found it very difficult to reliably build robust multi-threaded applications using the shared data and locks model, especially as applications grow in size and complexity.Making things worse, testing is not reliable with multi-threaded code. Since threads are non-deterministic, you might successfully test a program one thousand times, yet still the program could go wrong the first time it runs on a customer’s machine.

    We now have a look at an entirely different approach that bans the notion of shared state altogether. State is still mutable, however it is exclusively coupled to single entities that are allowed to alter it, so-called actors.The actor model in computer science is a mathematical model of concurrent computation that treats “actors” as the universal primitives of concurrent digital computation: in response to a message that it receives, an actor can make local decisions, create more actors, send more messages, and determine how to respond to the next message received.For communication, the actor model uses asynchronous message passing. In particular, it does not use any intermediate entities such as channels. Instead, each actor possesses a mailbox and can be addressed. These addresses are not to be confused with identities, and each actor can have no, one or multiple addresses. When an actor sends a message, it must know the address of the recipient. In addition, actors are allowed to send messages to themselves, which they will receive and handle later in a future step.

    The Task Parallel Library (TPL) provides dataflow components to help increase the robustness of concurrency-enabled applications. These dataflow components are collectively referred to as the TPL Dataflow Library. This dataflow model promotes actor-based programming by providing in-process message passing for coarse-grained dataflow and pipelining tasks.The TPL Dataflow Library provides a foundation for message passing and parallelizing CPU-intensive and I/O-intensive applications that have high throughput and low latency. It also gives you explicit control over how data is buffered and moves around the system.

    If you want to scale your application beyond single machine or process then ServiceBus (NServiceBus, Microsoft Azure, etc) are the best candidates. These are designed around message oriented architecture and you can achieve highly reliable, available and scalable application. As an architect i always focus on reliability, after all, a highly available and scalable service that produces unreliable results isn’t very valuable. – We will need another post to cover the in-depth of service bus..

    TPL Dataflow (TDF) is a library for building concurrent applications. It promotes actor/agent-oriented designs through primitives for in-process message passing, dataflow, and pipelining. I have been playing with dataflow since its CTP was released and i found its very use in cases where you have to process data in form of a pipeline.With just few in-build blocks you can easily and quickly build concurrent app..

    The primitive blocks provided by dataflow are

    • Buffering Blocks – Holds data for use by data consumers.
      • BufferBock(T) – FIFO queue of message that can be written to multiple sources or read from by multiple targets.
      • BroadcastBlock(T) – Used when you must pass multiple messages to another component.
      • WriteOnceBlock(T) – similar to broadcastblock except object can be written to one time only
    • Execution Block – call a user provided delegate for each piece of received data
      • ActionBlock(t) – calls a delegate when it receives a data – excepts synchronous or asynchronous delegates
      • TransformBlock(Tinout,TOutput) – call function delegates to transform the incoming message to another type- excepts synchronous or asynchronous delegates
      • TransformManyBlock(TInput , TOutput) – similar to TransformBlock except it can produce zero or more output values for each input value, instead of only one output value for each input value. – excepts synchronous or asynchronous delegates

    Degree of Parallelism

    Every ActionBlock<TInput>, TransformBlock<TInput, TOutput>, and TransformManyBlock<TInput, TOutput> object buffers input messages until the block is ready to process them. By default, these classes process messages in the order in which they are received, one message at a time. You can also specify the degree of parallelism to enable ActionBlock<TInput>, TransformBlock<TInput, TOutput> and TransformManyBlock<TInput, TOutput> objects to process multiple messages concurrently.

    Now lets look at the implementation details of a web crawler

    Request Buffer
    |
    PageDownload
    / Save     ParseLink
    |
    RaiseLinkFound

    The messages to download a Url is received in the request buffer which is downloaded by a TranformBlock and converted into type safe page type message. The page message is then broadcasted using Broadcast block, which is further received by save ActionBlock and ParseLink Block. The parse link block parses the urls in the page and if they belong to same page it will raise an event for each url. The consumer of engine will receive the url and if its a new URL it will be posted back to engine.. The save action block will save the page to disk.

    This is very basic example but you can see the message based approach is much more simpler than a shared resource + threading approach.

    The TPL dataflow is good in case you don’t want to scale the solution beyond single machine as it offer a in process message base approach.With a proper service bus like NServiceBus you can scale out the solution beyond single machine and multiple servers could process the request to achieve the high throughput and off-course with easy to build,maintain clean code base.

    In production there are more things you have to take care like logging, error handling, transactions, unexpected failure recovery, dependency injection,loosely coupled components, extensibility, scalability and so on.. Frameworks like NServiceBus provides all these features alone with API to handle more complex business problems.

    Download Code : Here (Partially finished but working POC)

  • WPF Multithreading

    WPF have the concept of the UI thread and background (or secondary) threads. Code on a background thread is not allowed to interact with UI elements (controls, etc.) on the foreground thread.  What happens when you need to add and remove items from a collection from that background thread? That’s pretty common in applications which must keep a list updated . When the collection raises the change notification events, the binding system and listeners, on the UI thread, have code executed.

    Before WPF 4.5 we have to use dispatcher to enable this feature in observable collection or we end up creating thread safe observable collection and also expose Add Range function which is not available in observable collections.

    WPF 4.5 includes a number of key targeted performance and capability features, one of which is cross-thread collection change notification. Enabling the change notification is as simple as the inclusion of a lock object and a single function call. Versus the manual dispatching approach, this can be a real performance win, not to mention save you some coding time.

    Use BindingOperations.EnableCollectionSynchronization to enable this cross thread access of observable collections.

    So inside your view model you call this method and provide the lock to synchronise the collection from multiple threads. BindingOperations.EnableCollectionSynchronization, the WPF data binding engine participates in locking. The default behavior is to acquire a lock on the object specified in the aforementioned call, but you also have the option to use more complex locking schemes.

    something like

    BindingOperations.EnableCollectionSynchronization(this.items, itemsSyncLock);

    where

    private readonly object itemsSyncLock = new object();

    private readonly ObservableCollection<string> items = new ObservableCollection<string>();

    Now when you have to add data into collection from background thread

    simply lock the collection and update it

    lock (itemsSyncLock)
    {
    // Once locked, you can manipulate the collection safely from another thread
    items.Add(value);
    }

    This means now you don’t need IDispatcher in you application anymore..

    There are also other binding properties like IsAsync (to get the data asynchronously) , Delay (provides delay before committing binding values etc)..