C interop in V

Published .. 05-07-2024
Type ....... article
Tags ....... v, c

V has great C interop. Here are an example taken from a program which checks different issues with a website. This example shows how to the the expiring date of the sites certificate using C interop.

V uses the mbedtls library, but not all of this library is exposed from V. For instance there are no functions to access the details of the certificate, only function to check for validity is supplied.

But we can access the full mbedtls library by declaring the structures and function you whish to use from V.

module main

import time
import cli
import os
import net.mbedtls


// Declare that the following is a typedef declared in C. Used in function declararions. After declaration it can be used from V. But as you can see, no properties are made available.

@[typedef]
pub struct C.mbedtls_x509_crt {
}

// We can have our own V version of the C structures. The version can have properties.

pub struct Mbedtls_x509_buf { // x509.h : 228 (is a mbedtls_asn1_buf)
    tag int
    len isize	// in C the type is size_t and the corrosponding type in v is isize (i used i64 until i found out)
    p   &char
}

// Our local struct can have methods

fn (n Mbedtls_x509_buf) str() string {
    unsafe {
        vs := n.p.vstring_literal_with_len(int(n.len))
        return '${vs}'
    }
}

@[typedef]
pub struct C.Mbedtls_x509_name {
}

pub struct Mbedtls_x509_name { // x509.h : 239 (is a mbedtls_asn1_named_data)
    oid    Mbedtls_x509_buf
    val    Mbedtls_x509_buf
    next   &Mbedtls_x509_name
    merged u8
}

fn (n Mbedtls_x509_name) str() string {
    mut r := n.val.str()
    if n.next != C.NULL {
        r += ' [ ' + n.next.str() + ' ] '
    }
    return r
}

@[typedef]
pub struct C.mbedtls_x509_time {
}

pub struct Mbedtls_x509_time { // x509.h : 247
    year int
    mon  int
    day  int
    hour int
    min  int
    sec  int
}

fn (t Mbedtls_x509_time) time() time.Time {
    return time.Time{
        year: t.year
        month: t.mon
        day: t.day
        hour: t.hour
        minute: t.min
        second: t.sec
    }
}

fn (t Mbedtls_x509_time) str() string {
    return t.time().format_rfc3339()
}

// The struct that hold the certificate, the mbedtls has further fields, I have only declared the first few. So this struct does not have same size as the C equivalent. 

pub struct Mbedtls_x509_crt_view { // x509_crt.h : 54
    own         int
    raw         Mbedtls_x509_buf
    tbs         Mbedtls_x509_buf
    version     int
    serial      Mbedtls_x509_buf
    sig_oid     Mbedtls_x509_buf
    issuer_raw  Mbedtls_x509_buf
    subject_raw Mbedtls_x509_buf
    issuer      Mbedtls_x509_name
    subject     Mbedtls_x509_name
    valid_from  Mbedtls_x509_time
    valid_to    Mbedtls_x509_time
}

// This function is not exposed in the stdlibs implementation but i can easily declare it and use it from V.

fn C.mbedtls_ssl_get_peer_cert(sslctx &C.mbedtls_ssl_context) &C.mbedtls_x509_crt

// Retrieve and print info about the certificate of a given URL.
fn cert_expire_mbedtls(url string) {
    host := url.all_after_first('//')

    println('CERTEXPIRE')
    println('URL : ${url}')
    println('HOST : ${host}')

    mut client := mbedtls.new_ssl_conn(mbedtls.SSLConnectConfig{
        validate: false
    }) or {
        eprintln('new_ssl_conn failed: ${err}')
        return
    }

    client.dial(host, 443) or {
        eprintln('dialing failed: ${err}')
        return
    }

    // Use declared C function to get the peer certificate
    cert := C.mbedtls_ssl_get_peer_cert(voidptr(&client.ssl))
    if cert == C.NULL {
        eprintln('no certificate found')
        return
    }

    // Use out V version of C.bbedtls_x509_crt_view to be able to access the properties of the struct
    xc := &Mbedtls_x509_crt_view(cert)

    println('CERT INFO:')
    println('  Version : ${xc.version}')
    println('  Issuer : ${xc.issuer}')
    println('  Valid from : ${xc.valid_from}')
    rel := xc.valid_to.time().relative_short()
    println('  Valid to : ${xc.valid_to} [${rel}]')

}


fn main() {
    mut app := cli.Command{
        name: 'certexp'
        description: 'check certificate expiring date'
        commands: [
            cli.Command{
                name: 'cert'
                required_args: 1
                execute: fn (cmd cli.Command) ! {
                    for c in cmd.args {
                        // cert_expire(c)
                        cert_expire_mbedtls(c)
                    }
                    return
                }
            },
        ]
    }
    app.setup()
    app.parse(os.args)
}