Jeko Eustaquio Jeko Eustaquio - 2 months ago 25
C Question

Websocket: How to encode a text to send to a client, in C

I'm trying to develop a function for encode a text in a correct format of websocket data send. this is a function in PHP but I can't translate this in C language.

private function encode($text) {
// 0x1 text frame (FIN + opcode)
$b1 = 0x80 | (0x1 & 0x0f);
$length = strlen($text);

if($length <= 125)
$header = pack('CC', $b1, $length);
elseif($length > 125 && $length < 65536)
$header = pack('CCS', $b1, 126, $length);
elseif($length >= 65536)
$header = pack('CCN', $b1, 127, $length);

return $header.$text;
}

Answer

There's less need for bureaucracy, so it's much simpler to implement. Normally you expect the caller to provide the output buffer and the text size, so let's do that.

Also, be extra careful with the endianness, your second use of pack in that PHP code should be with 'CCn' instead of 'CCS'.

Example implementation:

size_t encode(char *buf, size_t bufsize, const char *text, size_t len)
{
    const size_t hdrlen = len > 65535 ? 6 : (len > 255 ? 4 : 2);
    if (bufsize < hdrlen + len)
        return 0;

    *buf++ = 0x81; /* b1 */

    switch (hdrlen) {
    case 6:
        *buf++ = 127;
        break;
    case 4:
        *buf++ = 126;
    }

    /* Store length in big endian order */
    switch (hdrlen) {
    case 6:
        *buf++ = len >> 24;
        *buf++ = len >> 16;
    case 4:
        *buf++ = len >> 8;
    case 2:
        *buf++ = len;
    }

    memcpy(buf, text, len);
    return hdrlen + len;
}

For convenience it returns the result's size. You will need at least <stddef.h> (unless you replace size_t) and <string.h>.

However, you may want to use a scatter-gather approach instead, as it avoids the copying. In that case you just need to build the header and adjust it's vector. Also, IMO it's more elegant:

int setup_header(struct iovec *v, int n)
{
    /* We expect a valid buffer in v[0], and the payload in v[1+] */
    if (n < 2 || v[0]->iov_len < 6)
        return -1;

    size_t len = v[1]->iov_len;
    for (int i = 2; i < n; i++)
        len += v[n]->iov_len;

    v[0]->iov_len = len > 65535 ? 6 : (len > 255 ? 4 : 2);

    char *buf = v[0]->iov_base;
    *buf++ = 0x81; /* b1 */
    switch (v[0]->iov_len) {
    case 6:
        *buf++ = 127;
        break;
    case 4:
        *buf++ = 126;
    }

    /* Store length in big endian order */
    switch (v[0]->iov_len) {
    case 6:
        *buf++ = len >> 24;
        *buf++ = len >> 16;
    case 4:
        *buf++ = len >> 8;
    case 2:
        *buf++ = len;
    }

    return 0;
}   

In this case the return value is zero if successful. For this one you need <sys/uio.h> or equivalent.

Comments