CPort (Work In Progress)

Frank Mitchell

Posted: 2023-03-16
Last Modified: 2023-07-14
Word Count: 1547
Tags: c-programming programming vaporware

Table of Contents

Purpose

CPort stands for both C Portability and Character Ports. As the overloaded name suggests, its goal is to provide an API to read and write streams of Unicode code points on any platform that supports C, even embedded and non-standard platforms that cannot use <stdio.h>.

Concepts

Ports

A port provides a stream of characters. An input port provides an incoming stream for an application to read; an output port provides an outgoing stream to which an application may write.

From the application’s perspective, they use ports something like this:

C_Port *p = NULL;

C_Port_new(&p);

ASSERT(p != NULL);

C_Port_Status status = 
    C_Port_get(p, "/home/fmitchell/test.json");
    // or C_Port_uri_get(p, "file:///home/fmitchell/test.json"));

if (status == CPORT_OK) {
    wchar_t cp;

    do {
        status = C_Port_read_char(p, &cp);
        if (status == CPORT_OK) {
            /* do something with code point `cp` */

        }

    } while (cp >= 0 && status == CPORT_OK);
}

if (status != CPORT_OK) {
    /* Handle error somewhow */
}

C_Port_release(&p);  /* also closes the port if necessary */

As an abstraction layer over innumerable sources and sinks of text, ports provide no advanced functions to rewind or fast-forward along a file, interrogate the size or source of the stream, and so on.

Converter

To convert read octets (8-bit bytes) to UTF-8 or UTF-32, and to convert UTF-32 or UTF-8 back to arbitrary octets, C-Port will use a custom library called C-Converter to do the conversions.

Like the API below this avoids a direct dependency on libraries like iconv which may not exist – or be necessary – on all platforms.

Restricted Environments

Whike Unix Services lists the APIs used internally on Unix platforms, compiler switches will choose native APIs for non-Unix environments, including but not limited to Windows and embedded environments.

Internals are still a work in progress, but implementors need only implement the function prototypes below for their specific platform.

typedef enum _C_Port_Method {
    MODE_CONNECT = 0,
    MODE_DELETE,
    MODE_GET,
    MODE_PUT,
    MODE_POST,

    MODE_HEAD,      /* RESERVED */
    MODE_OPTIONS,   /* RESERVED */
    MODE_PATCH,     /* RESERVED */
    MODE_TRACE,     /* RESERVED */

    MODE_UNDEFINED
} C_Port_Method;

/*
 * Callbacks to open/create an I/O stream.
 * `uri` is the full URI given to a `C_Port_uri_<method>()` function.
 * `method` denotes which `C_Port_uri_<method>()` function was called.
 *
 * If the resource at the `uri` may be read and/or written by `method`
 * implementers should set `(*data)` with a non-NULL pointer 
 * to a data structure used to read and/or write to the resource
 * and return CPORT_OK.
 * Otherwise the implementer should return an informative error code.
 */
typedef C_Port_Status (*C_Port_Opener)(C_Port_Method method,
                                        const char* uri, 
                                        void* (*data));

/*
 * Callback to close/delete the I/O stream implementing `port`.
 * `data` is a pointer to the structure created in the opener function.
 */
typedef C_Port_Status (*C_Port_Closer)(void* data);

/*
 * Callback to read bytes and return a pointer to the bytes read,
 * placing the number of bytes read in `*sptr`.
 * `data` is a pointer to the structure created in the opener function.
 *
 * The implementer is responsible for creating and managing any buffers
 * used in successive function calls, with the understanding that an
 * application *could* be multi-threaded, but only one thread will access
 * `data` at any given time.
 */
typedef const octet_t* (*C_Port_Reader)(void* data, size_t *sptr);

/*
 * Callback to write up to `size` bytes from `buf` 
 * and return the number of bytes written.
 * `data` is a pointer to the structure created in the opener function.
 */
typedef size_t (*C_Port_Writer)(void* data, size_t size, const octet_t* buf);

API

The full CPort API will look something like this.

Types

Data Types

/* defined in C99 */
#include <stdbool.h> /* defines bool */
#include <stddef.h>  /* defines size_t */
#include <stdint.h>  /* defines uint8_t */
#include <wchar.h>   /* defines wchar_t */

typedef uint8_t octet_t;
typedef uint8_t utf8_t;

typedef struct _C_Port      C_Port;

Status/Error Codes

typedef enum _C_Port_Status {
    CPORT_OK = 0,

    CPORT_OPEN_ERROR     = 0x100,
    CPORT_NAME_NOT_FOUND = 0x101,
    CPORT_ALREADY_OPEN   = 0x102,
    /* to be continued ... */

    CPORT_READ_ERROR     = 0x200,
    /* to be continued ... */

    CPORT_WRITE_ERROR    = 0x300,
    /* to be continued ... */

    CPORT_CONV_ERROR     = 0x500,
    /* to be continued ... */

    CPORT_CLOSE_ERROR    = 0x800,
    CPORT_END_OF_STREAM  = 0x801,
    /* to be continued ... */

    /* Uncategorized Error */
    CPORT_UNKNOWN_ERROR = 0x7FFF
} C_Port_Status;

/* TODO: cross-reference with <errno.h> */

Error Handling

typedef void (*C_Port_Error_Handler)(C_Port* port, C_Port_Status code);

C_Port_Status C_Port_status(C_Port* port);
void          C_Port_set_status(C_Port* port, C_Port_Status);
void          C_Port_clear_status(C_Port* port);

Ports

Creating Ports

void C_Port_new(C_Port* *pptr);

Opening Ports

In the functions below:

/** Open `path` for reading. */
C_Port_Status C_Port_get(C_Port* port, const char* path);

/**
 * Open the `path` for reading or writing based on `mode`,
 * which is the same as in Posix and Windows fopen(): "a", "r", or "w", 
 * with an optional "+".
 * Windows may add a "t" or "b" for text or binary mode.
 * Fully POSIX-compliant systems simply ignore "t" or "b".
 */
C_Port_Status C_Port_open(C_Port* port, const char* path, const char* mode);

/** Opens STDERR as a port. */
C_Port_Status C_Port_stderr(C_Port* port);

/** Opens STDIN as a port. */
C_Port_Status C_Port_stdin(C_Port* port);

/** Opens STDOUT as a port. */
C_Port_Status C_Port_stdout(C_Port* port);

/** Opens the resource at `uri` for reading and writing. */
C_Port_Status C_Port_uri_connect(C_Port* port, const char* uri);

/** Deletes the resource at `uri` without opening for reading or writing. */
C_Port_Status C_Port_uri_delete(C_Port* port, const char* uri);

/** Opens the resource at `uri` for reading. */
C_Port_Status C_Port_uri_get(C_Port* port, const char* uri);

/** Opens the resource at `uri` for writing THEN reading (e.g. HTTP POST). */
C_Port_Status C_Port_uri_post(C_Port* port, const char* uri);

/** Opens the resource at `uri` for writing. */
C_Port_Status C_Port_uri_put(C_Port* port, const char* uri);

C_Port_uri_<method>() can open non-HTTP URIs including “file:” paths. The translates as follows:

HTTP Method Files FTP Other Network Protocols
post POSIX fopen(path, "a+") read and write
delete POSIX unlink(path) DELETE
get POSIX fopen(path, "r") GET read only
post POSIX fopen(path, "a") write THEN read
put POSIX fopen(path, "w") PUT write only

Configuring Ports

The set_* methods return boolean values to indicate whether the command succeeded. Generally applications should configure a port before reading or writing data.

const char* C_Port_attribute(C_Port* port, const char* name);
const char* C_Port_encoding(C_Port* port);
bool        C_Port_is_binary(C_Port* port);
const char* C_Port_mime_type(C_Port* port);

bool        C_Port_set_attribute(C_Port* port, 
                                        const char* name,
                                        const char* value);
bool        C_Port_set_binary(C_Port* port, bool bool);
bool        C_Port_set_encoding(C_Port* port, const char* encoding);
bool        C_Port_set_mime_type(C_Port* port, const char* type);

Reading From Input Ports

bool    C_Port_is_end_of_stream(C_Port* port);
bool    C_Port_is_input(C_Port* port);
bool    C_Port_is_ready(C_Port* port);

C_Port_Status C_Port_peek_char(C_Port* port, wchar_t *cptr);
C_Port_Status C_Port_read_char(C_Port* port, wchar_t *cptr);
C_Port_Status C_Port_read_raw(C_Port* port, 
                                size_t bufsiz,
                                size_t *szptr,
                                octet_t* buffer);
C_Port_Status C_Port_read_utf8(C_Port* port, 
                                size_t bufsiz,
                                size_t *szptr,
                                utf8_t* buffer);
C_Port_Status C_Port_skip_char(C_Port* port);

Writing To Output Ports

size_t C_Port_capacity(C_Port* port, size_t *cptr);
bool   C_Port_is_output(C_Port* port);
bool   C_Port_is_writable(C_Port* port);

C_Port_Status C_Port_flush(C_Port* port);
C_Port_Status C_Port_write_char(C_Port* port, wchar_t char);
C_Port_Status C_Port_write_newline(C_Port* port);
C_Port_Status C_Port_write_raw(C_Port* port, 
                                    size_t bsz, 
                                    const octet_t* buf, 
                                    size_t *szptr);
C_Port_Status C_Port_write_utf8(C_Port* port, 
                                    size_t bsz, 
                                    const utf8_t* buf, 
                                    size_t *szptr);

/*
 * for protocols like POST where the remote side will not respond
 * until the other side finishes its response.
 */
C_Port_Status C_Port_write_end_of_stream(C_Port* port);

Closing Ports

/* closes and cleans up a Port */
void C_Port_close(C_Port* port);
bool C_Port_is_closed(C_Port* port);

Reference Counting Ports

/* adds reference */
C_Port* C_Port_retain(C_Port* port);

/* removes reference, closing if no longer needed */
void C_Port_release(C_Port* *pptr);

Unix Services

File I/O

#include <stdio.h>
name
Either a relative file name in the platform’s conventions or a File URL.
Open_File: Port Open
Open a file for reading (GET), writing (PUT), appending (POST), or reading and appending (CONNECT). Does nothing and returns NULL on other methods. Uses character mode on platforms where that matters. Returns the file handle if successful.
Read_File: Reader
Read bytes from the file if GET mode.
Write_File: Writer
Writes bytes to the file if PUT or POST mode.
Delete_File: Deleter
Deletes the named file.
Close_File: Port Close
Closes the file handle.

Socket I/O

#include <sys/types.h>
#include <sys/socket.h>
name
Either “host:port” pair or a URL with a host and port.
Open_Socket: Port Open
Opens a client socket based on the hostname and port given in name. Uses full duplex if CONNECT, read-only if GET, write-only if PUT or POST. Does nothing and returns nothing on other methods. Returns the socket descriptor if successful.
Read_Socket: Reader
Read bytes from the socket.
Write_Socket: Writer
Write bytes to the socket.
Close_Socket: Port Close
Close both sides of the socket.

Pipe I/O

#include <sys/types.h>
#include <sys/stat.h>
#indlude <fcntl.h>
name
The file name or other identifier for a named pipe.
Open_Pipe: Port Open
Opens a named pipe based on name. Makes the pipe if it doesn’t exist. Opens for reading if GET, writing if PUT or POST, both if CONNECT. Does nothing and returns nothing on other methods. Returns the socket descriptor if successful.
Read_Pipe: Reader
Read from the UNIX pipe.
Write_Pipe: Writer
Write to the UNIX pipe.
Close_Pipe: Port Close
Close the UNIX pipe.

CURL Services

#include <curl/curl.h>

The application will use the libcurl API to perform the requested operation to the requested URL, reading and writing from internal buffers rather than external files.

Unlike the other APIs, libcurl has been ported to Windows 64-bit and several other operating systems, and handles virtually every Internet protocol ever written.

TODO