Archive

Posts Tagged ‘c’

Porting a PHP OAuth Spotler Client to C#: Lessons Learned

Recently I had to integrate with Spotler’s REST API from a .NET application. Spotler provides a powerful marketing automation platform, and their API uses OAuth 1.0 HMAC-SHA1 signatures for authentication.

They provided a working PHP client, but I needed to port this to C#. Here’s what I learned (and how you can avoid some common pitfalls).


🚀 The Goal

We started with a PHP class that:

✅ Initializes with:

  • consumerKey
  • consumerSecret
  • optional SSL certificate verification

✅ Creates properly signed OAuth 1.0 requests

✅ Makes HTTP requests with cURL and parses the JSON responses.

I needed to replicate this in C# so we could use it inside a modern .NET microservice.


🛠 The Port to C#

🔑 The tricky part: OAuth 1.0 signatures

Spotler’s API requires a specific signature format. It’s critical to:

  1. Build the signature base string by concatenating:
    • The uppercase HTTP method (e.g., GET),
    • The URL-encoded endpoint,
    • And the URL-encoded, sorted OAuth parameters.
  2. Sign it using HMAC-SHA1 with the consumerSecret followed by &.
  3. Base64 encode the HMAC hash.

This looks simple on paper, but tiny differences in escaping or parameter order will cause 401 Unauthorized.

💻 The final C# solution

We used HttpClient for HTTP requests, and HMACSHA1 from System.Security.Cryptography for signatures. Here’s what our C# SpotlerClient does:

✅ Generates the OAuth parameters (consumer_key, nonce, timestamp, etc).
✅ Creates the exact signature base string, matching the PHP implementation character-for-character.
✅ Computes the HMAC-SHA1 signature and Base64 encodes it.
✅ Builds the Authorization header.
✅ Sends the HTTP request, with JSON bodies if needed.

We also added better exception handling: if the API returns an error (like 401), we throw an exception that includes the full response body. This made debugging much faster.


🐛 Debugging tips for OAuth 1.0

  1. Print the signature base string.
    It needs to match exactly what Spotler expects. Any stray spaces or wrong escaping will fail.
  2. Double-check timestamp and nonce generation.
    OAuth requires these to prevent replay attacks.
  3. Compare with the PHP implementation.
    We literally copied the signature generation line-by-line from PHP into C#, carefully mapping rawurlencode to Uri.EscapeDataString.
  4. Turn off SSL validation carefully.
    During development, you might disable certificate checks (ServerCertificateCustomValidationCallback), but never do this in production.

using System.Security.Cryptography;
using System.Text;

namespace SpotlerClient
{
 
    public class SpotlerClient
    {
        private readonly string _consumerKey;
        private readonly string _consumerSecret;
        private readonly string _baseUrl = "https://restapi.mailplus.nl";
        private readonly HttpClient _httpClient;

        public SpotlerClient(string consumerKey, string consumerSecret, bool verifyCertificate = true)
        {
            _consumerKey = consumerKey;
            _consumerSecret = consumerSecret;

            var handler = new HttpClientHandler();
            if (!verifyCertificate)
            {
                handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
            }

            _httpClient = new HttpClient(handler);
        }

        public async Task<string> ExecuteAsync(string endpoint, HttpMethod method, string jsonData = null)
        {
            var request = new HttpRequestMessage(method, $"{_baseUrl}/{endpoint}");
            var authHeader = CreateAuthorizationHeader(method.Method, endpoint);
            request.Headers.Add("Accept", "application/json");
            request.Headers.Add("Authorization", authHeader);

            if (jsonData != null)
            {
                request.Content = new StringContent(jsonData, Encoding.UTF8, "application/json");
            }

            var response = await _httpClient.SendAsync(request);

            if (!response.IsSuccessStatusCode)
            {
                var body = await response.Content.ReadAsStringAsync();
                return body;
            }

            return await response.Content.ReadAsStringAsync();
        }

        private string CreateAuthorizationHeader(string httpMethod, string endpoint)
        {
            var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
            var nonce = Guid.NewGuid().ToString("N");

            var paramString = "oauth_consumer_key=" + Uri.EscapeDataString(_consumerKey) +
                              "&oauth_nonce=" + Uri.EscapeDataString(nonce) +
                              "&oauth_signature_method=" + Uri.EscapeDataString("HMAC-SHA1") +
                              "&oauth_timestamp=" + Uri.EscapeDataString(timestamp) +
                              "&oauth_version=" + Uri.EscapeDataString("1.0");

            var sigBase = httpMethod.ToUpper() + "&" +
                          Uri.EscapeDataString(_baseUrl + "/" + endpoint) + "&" +
                          Uri.EscapeDataString(paramString);

            var sigKey = _consumerSecret + "&";

            var signature = ComputeHmacSha1Signature(sigBase, sigKey);

            var authHeader = $"OAuth oauth_consumer_key=\"{_consumerKey}\", " +
                             $"oauth_nonce=\"{nonce}\", " +
                             $"oauth_signature_method=\"HMAC-SHA1\", " +
                             $"oauth_timestamp=\"{timestamp}\", " +
                             $"oauth_version=\"1.0\", " +
                             $"oauth_signature=\"{Uri.EscapeDataString(signature)}\"";

            return authHeader;
        }

        private string ComputeHmacSha1Signature(string data, string key)
        {
            using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(key));
            var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
            return Convert.ToBase64String(hash);
        }
    }
}

✅ The payoff

Once the signature was constructed precisely, authentication errors disappeared. We could now use the Spotler REST API seamlessly from C#, including:

  • importing contact lists,
  • starting campaigns,
  • and fetching campaign metrics.

📚 Sample usage

var client = new SpotlerClient(_consumerKey, _consumerSecret, false);
var endpoint = "integrationservice/contact/email@gmail.com";
var json = client.ExecuteAsync(endpoint, HttpMethod.Get).GetAwaiter().GetResult();

🎉 Conclusion

Porting from PHP to C# isn’t always as direct as it looks — especially when it comes to cryptographic signatures. But with careful attention to detail and lots of testing, we managed to build a robust, reusable client.

If you’re facing a similar integration, feel free to reach out or clone this approach. Happy coding!

Categories: Uncategorized Tags: , , , ,

Storing data directly in GPU memory with #CLOO in C#

Although I’m not entirely sure of a practical application for this. This application, using C# and CLOO can store arbitrary data in the GPU memory. In this case, I’m picking a large file off the disk, and putting it in GPU memory.

In the case of this NVIDIA Geforce card, the memory is dedicated to the GPU, and not shared with the system, ordinarily.

TL;DR; The Github repo is here – https://github.com/infiniteloopltd/GpuMemoryDemo

The core function is here;

 static void Main()
        {
            var platform = ComputePlatform.Platforms[0];
            var device = platform.Devices.FirstOrDefault(d => d.Type.HasFlag(ComputeDeviceTypes.Gpu));
            var context = new ComputeContext(ComputeDeviceTypes.Gpu, new ComputeContextPropertyList(platform), null, IntPtr.Zero);
            var queue = new ComputeCommandQueue(context, device, ComputeCommandQueueFlags.None);

            const string largeFilePath = "C:\\Users\\fiach\\Downloads\\datagrip-2024.3.exe";
            var contents = File.ReadAllBytes(largeFilePath);

            var clBuffer = Store(contents, context, queue);

            var readBackBytes = Retrieve(contents.Length, clBuffer, queue);

            Console.WriteLine($"Original String: {contents[0]}");
            Console.WriteLine($"Read Back String: {readBackBytes[0]}");
            Console.WriteLine($"Strings Match: {contents[0] == readBackBytes[0]}");

            
            // Memory leak here. 
            //Marshal.FreeHGlobal(readBackPtr);
            //Marshal.FreeHGlobal(buffer);
            
        }

        public static ComputeBuffer<byte> Store(byte[] stringBytes, ComputeContext context, ComputeCommandQueue queue)
        {
            var buffer = Marshal.AllocHGlobal(stringBytes.Length);

            Marshal.Copy(stringBytes, 0, buffer, stringBytes.Length);

            var clBuffer = new ComputeBuffer<byte>(context, ComputeMemoryFlags.ReadWrite, stringBytes.Length);

            queue.Write(clBuffer, true, 0, stringBytes.Length, buffer, null);
            
            return clBuffer;
        }

        public static byte[] Retrieve(int size, ComputeBuffer<byte> clBuffer, ComputeCommandQueue queue)
        {
            var readBackPtr = Marshal.AllocHGlobal(size);

            queue.Read(clBuffer, true, 0, size, readBackPtr, null);

            var readBackBytes = new byte[size];

            Marshal.Copy(readBackPtr, readBackBytes, 0, size);

            return readBackBytes;
        }
    }

we’ll walk through a C# program that demonstrates the use of OpenCL to store and retrieve data using the GPU, which can be beneficial for performance in data-heavy applications. Here’s a breakdown of the code:

1. Setting Up OpenCL Context and Queue

The program begins by selecting the first available compute platform and choosing a GPU device from the platform:

csharpCopy codevar platform = ComputePlatform.Platforms[0];
var device = platform.Devices.FirstOrDefault(d => d.Type.HasFlag(ComputeDeviceTypes.Gpu));
var context = new ComputeContext(ComputeDeviceTypes.Gpu, new ComputeContextPropertyList(platform), null, IntPtr.Zero);
var queue = new ComputeCommandQueue(context, device, ComputeCommandQueueFlags.None);
  • ComputePlatform.Platforms[0]: Selects the first OpenCL platform on the machine (typically corresponds to a GPU vendor like NVIDIA or AMD).
  • platform.Devices.FirstOrDefault(...): Finds the first GPU device available on the platform.
  • ComputeContext: Creates an OpenCL context for managing resources like buffers and command queues.
  • ComputeCommandQueue: Initializes a queue to manage commands that will be executed on the selected GPU.

2. Reading a Large File into Memory

The program then loads the contents of a large file into a byte array:

csharpCopy codeconst string largeFilePath = "C:\\Users\\fiach\\Downloads\\datagrip-2024.3.exe";
var contents = File.ReadAllBytes(largeFilePath);

This step reads the entire file into memory, which will later be uploaded to the GPU.

3. Storing Data on the GPU

The Store method is responsible for transferring the byte array to the GPU:

csharpCopy codevar clBuffer = Store(contents, context, queue);
  • It allocates memory using Marshal.AllocHGlobal to hold the byte array.
  • The byte array is then copied into this allocated buffer.
  • A ComputeBuffer<byte> is created on the GPU, and the byte array is written to it using the Write method of the ComputeCommandQueue.

Note: The Store method utilizes Marshal.Copy to handle memory copying between managed memory (RAM) and unmanaged memory (GPU).

4. Retrieving Data from the GPU

The Retrieve method is responsible for reading the data back from the GPU into a byte array:

csharpCopy codevar readBackBytes = Retrieve(contents.Length, clBuffer, queue);
  • The method allocates memory using Marshal.AllocHGlobal to hold the data read from the GPU.
  • The Read method of the ComputeCommandQueue is used to fetch the data from the GPU buffer back into the allocated memory.
  • The memory is then copied into a managed byte array (readBackBytes).

5. Verifying the Data Integrity

The program prints the first byte of the original and retrieved byte arrays, comparing them to verify if the data was correctly transferred and retrieved:

csharpCopy codeConsole.WriteLine($"Original String: {contents[0]}");
Console.WriteLine($"Read Back String: {readBackBytes[0]}");
Console.WriteLine($"Strings Match: {contents[0] == readBackBytes[0]}");

This checks whether the first byte of the file content remains intact after being transferred to and retrieved from the GPU.

6. Memory Management

The program has a commented-out section for freeing unmanaged memory:

csharpCopy code//Marshal.FreeHGlobal(readBackPtr);
//Marshal.FreeHGlobal(buffer);

These lines should be used to free the unmanaged memory buffers allocated with Marshal.AllocHGlobal to avoid memory leaks, but they are commented out here, leaving room for improvement.

Potential Improvements and Issues

  • Memory Leaks: The program does not properly free the unmanaged memory allocated via Marshal.AllocHGlobal, leading to potential memory leaks if run multiple times.
  • Error Handling: The program lacks error handling for situations like missing GPU devices or file read errors.
  • Large File Handling: For large files, this approach may run into memory constraints, and you might need to manage chunked transfers for efficiency.

In summary, this program demonstrates how to work with OpenCL in C# to transfer data between the host system and the GPU. While it shows the core functionality, handling memory leaks and improving error management should be considered for a production-level solution.

C# – using #OpenCV to determine if an image contains an image of a car (or a duck)

TL;DR; Here is the repo: https://github.com/infiniteloopltd/IsItACar

This demo application can take an image and derermine if the image is that of a Car, or not a car. My test image was of a duck, which was very defintely not car-like. But sillyness aside, this can be very useful for image upload validation – if you want to ensure that your car-sales website doesn’t allow their users to upload nonsense pictures, but only of cars, then this code could be useful.

Why Use Emgu.CV for Computer Vision?

Emgu.CV simplifies the use of OpenCV in C# projects, providing an intuitive interface while keeping the full functionality of OpenCV. For tasks like object detection, it is an ideal choice due to its performance and flexibility.


Prerequisites

Before diving into the code, make sure you have the following set up:

  • Visual Studio (or another preferred C# development environment)
  • Emgu.CV library installed via NuGet:
    • Search for Emgu.CV and Emgu.CV.runtime.windows in the NuGet Package Manager and install them.

Setting Up Your Project

We’ll write a simple application to detect cars in an image. The code uses a pre-trained Haar cascade classifier, which is a popular method for object detection.

The Code

Here’s a complete example demonstrating how to load an image from a byte array and run car detection using Emgu.CV:

csharpCopy codeusing Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using System;
using System.Drawing;
using System.IO;

class Program
{
    static void Main(string[] args)
    {
        // Load the image into a byte array (this could come from a database or API)
        byte[] imageBytes = File.ReadAllBytes("path_to_your_image.jpg");

        // Create a Mat object to hold the decoded image
        Mat mat = new Mat();

        // Decode the image from the byte array into the Mat object
        CvInvoke.Imdecode(imageBytes, ImreadModes.Color, mat);

        // Convert the Mat to an Image<Bgr, byte> for further processing
        Image<Bgr, byte> image = mat.ToImage<Bgr, byte>();

        // Load the Haar cascade for car detection
        string cascadeFilePath = "path_to_haarcascade_car.xml"; // Download a Haar cascade for cars
        CascadeClassifier carClassifier = new CascadeClassifier(cascadeFilePath);

        // Convert to grayscale for better detection performance
        using (var grayImage = image.Convert<Gray, byte>())
        {
            // Detect cars in the image
            Rectangle[] cars = carClassifier.DetectMultiScale(
                grayImage, 
                scaleFactor: 1.1, 
                minNeighbors: 5, 
                minSize: new Size(30, 30));

            // Draw rectangles around detected cars
            foreach (var car in cars)
            {
                image.Draw(car, new Bgr(Color.Red), 2);
            }

            // Save or display the image with the detected cars
            image.Save("output_image_with_cars.jpg");
            Console.WriteLine($"Detected {cars.Length} car(s) in the image.");
        }
    }
}

Breaking Down the Code

  1. Loading the Image as a Byte Array:csharpCopy codebyte[] imageBytes = File.ReadAllBytes("path_to_your_image.jpg"); Instead of loading an image from a file directly, we load it into a byte array. This approach is beneficial if your image data is not file-based but comes from a more dynamic source, such as a database.
  2. Decoding the Image:csharpCopy codeMat mat = new Mat(); CvInvoke.Imdecode(imageBytes, ImreadModes.Color, mat); We use CvInvoke.Imdecode to convert the byte array into a Mat object, which is OpenCV’s matrix representation of images.
  3. Converting Mat to Image<Bgr, byte>:csharpCopy codeImage<Bgr, byte> image = mat.ToImage<Bgr, byte>(); The Mat is converted to Image<Bgr, byte> to make it easier to work with Emgu.CV functions.
  4. Car Detection Using Haar Cascades:csharpCopy codeRectangle[] cars = carClassifier.DetectMultiScale(grayImage, 1.1, 5, new Size(30, 30)); The Haar cascade method is used for object detection. You’ll need to download a Haar cascade XML file for cars and provide the path.
  5. Drawing Detected Cars:csharpCopy codeimage.Draw(car, new Bgr(Color.Red), 2); Rectangles are drawn around detected cars, and the image is saved or displayed.

Downloading Haar Cascade for Cars

To detect cars, you need a pre-trained Haar cascade file. You can find these files on the OpenCV GitHub repository or by searching online for “haarcascade for car detection.”


Conclusion

This example demonstrates a simple yet powerful way to use Emgu.CV for car detection in C#. While Haar cascades are efficient, modern machine learning methods like YOLO or SSD are more accurate for complex tasks. However, for basic object detection, this approach is easy to implement and performs well for simpler use cases.

Feel free to experiment with different parameters to improve detection accuracy or try integrating more advanced models for more complex scenarios. Happy coding!