Skip to content
Eduardo Bart edited this page Oct 7, 2024 · 3 revisions

binds

This library is intented to be used with programming languages other than C. They achieve this by different means.

Python

Python has multiple Foreign Function Interface (FFI) options for interoperability with C. This example uses CFFI and the libcmt mock. Which is assumed to be installed at ./libcmt-0.1.0.

This document uses the main mode of CFFI and works in two steps: build, then use.

Build

The build step has the objective of creating a python module for libcmt. To achieve this we'll use libcmt/ffi.h and the script below. Paths may need adjustments.

import os
from cffi import FFI

ffi = FFI()
with open(os.path.join(os.path.dirname(__file__), "../libcmt-0.1.0/usr/include/libcmt/ffi.h")) as f:
    ffi.cdef(f.read())
ffi.set_source("pycmt",
"""
#include "libcmt/rollup.h"
""",
     include_dirs=["libcmt-0.1.0/usr/include"],
     library_dirs=["libcmt-0.1.0/usr/lib"],
     libraries=['cmt'])

if __name__ == "__main__":
    ffi.compile(verbose=True)

Use

With the module built, we can import it with python and use its functions. This example uses the raw bindings and just serves to illustrate the process. Better yet would be to wrap these functions into a more "pythonic" API.

LD_LIBRARY_PATH=libcmt-0.1.0/lib/ python

#!/usr/bin/env python

# run this file only after building pycmt!
import os
from pycmt.lib import ffi, lib

r = ffi.new("cmt_rollup_t[1]")
assert(lib.cmt_rollup_init(r) == 0)

address = ffi.new("uint8_t[20]", b"1000000000000")
value   = ffi.new("uint8_t[32]", b"0000000000001")
data    = b"hello world"
index   = ffi.new("uint64_t *")
print(os.strerror(-lib.cmt_rollup_emit_voucher(r,
                            20, address,
                            32, value,
                            len(data), data, index)))

ffi.gc(r, lib.cmt_rollup_fini)

Common errors such as the one below indicate that python couldn't find libcmt, make sure LD_LIBRARY_PATH points to the correct directory, or better yet, link against the static library.

>>> import pycmt
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: libcmt.so: cannot open shared object file: No such file or directory

Go

Go is able to include C headers directly with cgo.

The application can be compiled with: go build main.go. Assuming that the mock was installed at ./libcmt-0.1.0. For the actual risc-v binaries, use:

CC=riscv64-linux-gnu-gcc-12 CGO_ENABLED=1 GOOS=linux GOARCH=riscv64 go build

In Debian, the cross compiler can be obtained with:

apt-get install crossbuild-essential-riscv64 gcc-12-riscv64-linux-gnu g++-12-riscv64-linux-gnu

(Code below is for demonstration purposes and not intended for production)

package main

/*
#cgo CFLAGS: -Ilibcmt-0.1.0/include/
#cgo LDFLAGS: -Llibcmt-0.1.0/lib/ -lcmt

#include "libcmt/rollup.h"
*/
import "C"

import (
    "math/big"
    "fmt"
    "unsafe"
)

func main() {
    var rollup C.cmt_rollup_t
    err := C.cmt_rollup_init(&rollup)
    if err != 0 {
        fmt.Printf("initialization failed\n")
    }

    bytes_s := []byte{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    }
    wei := new(big.Int).SetBytes(bytes_s)

    finish := C.cmt_rollup_finish_t{
        accept_previous_request: true,
    }
    for {
        var advance C.cmt_rollup_advance_t
        err = C.cmt_rollup_finish(&rollup, &finish)
        if err != 0 { return; }

        err = C.cmt_rollup_read_advance_state(&rollup, &advance);
        if err != 0 { return; }

        bytes:= wei.Bytes()
        size := len(bytes)
        C.cmt_rollup_emit_voucher(&rollup,
                                  C.CMT_ADDRESS_LENGTH, &advance.sender[0],
                                  C.uint(size), unsafe.Pointer(&bytes[0]),
                                  advance.length, advance.data, NULL)
        C.cmt_rollup_emit_report(&rollup, advance.length, advance.data)
    }
}

Rust

Rust interop with C requires bindings to be generated, we'll use bindgen to accomplish this.

Create the initial directory layout with cargo, then add the library to it:

cargo init --bin cmt
# download the library and extract it into cmt/libcmt-0.1.0
cd cmt

Generate the bindings, execute:

# join all header files into one.
cat libcmt-0.1.0/include/libcmt/*.h > src/libcmt.h

# generate the bindings
bindgen src/libcmt.h -o src/bindings.rs --allowlist-function '^cmt_.*' --allowlist-type '^cmt_.*' --no-doc-comments -- -I libcmt-0.1.0/include/libcmt

Include the bindings to the project, add the following to: src/lib.rs

#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]

include!("bindings.rs");

Tell cargo to link with the C library, add the following to: build.rs

fn main() {
    println!("cargo:rustc-link-search=libcmt-0.1.0/lib");
    println!("cargo:rustc-link-lib=cmt");
}

An example of the raw calls to C, add the following to: src/main.rs

use std::mem;

fn main() {
    let mut rollup: cmt::cmt_rollup_t = unsafe { mem::zeroed() };
    let rc = unsafe { cmt::cmt_rollup_init(&mut rollup) };
    println!("got return value: {}!", rc);
}

Messages like the one below most likely mean that cargo didn't find the library libcmt.a or build.rs is incomplete:

undefined reference to `cmt_rollup_emit_voucher'
Clone this wiki locally