Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: fix simple typo, beggining -> beginning #6

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: c

script:
- make
- make test
150 changes: 126 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Netstring parsing
=================
Netstring for C
===============
[![Build Status](https://travis-ci.org/liteserver/netstring-c.svg?branch=master)](https://travis-ci.org/liteserver/netstring-c)

A [netstring](http://en.wikipedia.org/wiki/Netstring) is a way of encoding a sequence of bytes for transmission over a network, or for serialization. They're very easy to work with. They encode the data's length, and can be concatenated trivially. The format was [defined by D. J. Bernstein](http://cr.yp.to/proto/netstrings.txt) and is used in various software. Examples of netstrings:

Expand All @@ -18,19 +19,65 @@ Basic API

All the code is in `netstring.c` and `netstring.h`, and these have no external dependencies. To use them, just include them in your application. Include `netstring.h` and link with the C code.

**Parsing netstrings**
Creating netstrings
-------------------

To parse a netstring, use `netstring_read()`.
You can create your netstrings manually like in this example:

int netstring_read(char *buffer, size_t buffer_length,
char **netstring_start, size_t *netstring_length);
```C
sprintf(buf, "%lu:%s,", strlen(str), str);
```

This code provides a convenience function for creating netstrings:

```C
size_t netstring_add(char **netstring, char *data);
```

Here is how to use it:

```C
char *netstring=0; /* we must initialize it to zero */

netstring_add(&netstring, "first");
netstring_add(&netstring, "second");
netstring_add(&netstring, "third");

do_something(netstring);

free(netstring); /* we must free after using it */
```

The extended version `netstring_add_ex` accepts a string length as the last argument:

```C
size_t netstring_add_ex(char **netstring, char *data, size_t len);
```

This allocates and creates a netstring containing the first `len` bytes of `data`. If `len` is 0 then no data will be read from `data`, and it may be null.

Parsing netstrings
------------------

To parse a netstring use `netstring_read()`:

```C
int netstring_read(char **buffer_start, size_t *buffer_length,
char **netstring_start, size_t *netstring_length);
```

It reads a netstring from `buffer_start` of initial `buffer_length` and writes
to `netstring_start` a pointer to the beginning of the string in the
buffer and to `netstring_length` the length of the string. It also updates
the `buffer_start` to the start of the next netstring item and `buffer_length`
to the number of remaining bytes not processed in the buffer.

It does not allocate any memory.

Reads a netstring from a `buffer` of length `buffer_length`. Writes to
`netstring_start` a pointer to the beginning of the string in the
buffer, and to `netstring_length` the length of the string. Does not
allocate any memory. If it reads successfully, then it returns 0. If
there is an error, then the return value will be negative. The error
values are:
### Return Value

If it reads successfully then it returns 0. If there is an error then the
return value will be negative. The error values are:

NETSTRING_ERROR_TOO_LONG More than 999999999 bytes in a field
NETSTRING_ERROR_NO_COLON No colon was found after the number
Expand All @@ -39,27 +86,82 @@ values are:
NETSTRING_ERROR_LEADING_ZERO Leading zeros are not allowed
NETSTRING_ERROR_NO_LENGTH Length not given at start of netstring

If you're sending messages with more than 999999999 bytes -- about 2
GB -- then you probably should not be doing so in the form of a single
Usage Example:

```C
char *str, *base = buffer;
size_t len, size = bytes_read;

while(netstring_read(&base, &size, &str, &len) == 0) {
do_something(str, len);
}
```

We can replace the comma with a null terminator when reading (zero copy):

```C
while(netstring_read(&base, &size, &str, &len) == 0) {
str[len] = 0;
puts(str);
str[len] = ','; /* and optionally restore it */
}
```

If you're sending messages with more than 999999999 bytes (about 2
GB) then you probably should not be doing so in the form of a single
netstring. This restriction is in place partially to protect from
malicious or erroneous input, and partly to be compatible with
D. J. Bernstein's reference implementation.

**Creating netstrings**
Message Framing on stream-based connections (sockets, pipes...)
---------------------------------------------------------------

To create a netstring, there are a few ways to do it. You could do something really simple like this example from [the spec](http://cr.yp.to/proto/netstrings.txt):
On stream-based connections the messages can arrive coalesced or fragmented.

if (printf("%lu:", len) < 0) barf();
if (fwrite(buf, 1, len, stdout) < len) barf();
if (putchar(',') < 0) barf();

This code provides a convenience function for creating a new netstring:
Here is an example of reading those messages using netstring for message framing:
```C
char buffer[1024], *buffer_base, *str;
int bytes_read, buffer_used = 0, len;

while(1) {
/* read data from socket */
bytes_read = recv(sock, &buffer[buffer_used], sizeof(buffer) - buffer_used);
if (bytes_read < 0) break; if (bytes_read == 0) continue;
buffer_used += bytes_read;

/* parse the strings from the read buffer */
buffer_base = buffer;
while(netstring_read(&buffer_base, &buffer_used, &str, &len) == 0) {
do_something(str, len);
}

/* if there are remaining bytes, move to the beginning of buffer */
if (buffer_base > buffer && buffer_used > 0)
memmove(buffer, buffer_base, buffer_used);
}
```
Note: this example is lacking error checking from netstring_read function and it does not allocate memory for bigger messages.

Additional Functions
--------------------

### netstring_list_size

Retrieves the size of the netstring list (concatenated netstrings) discarding trailing spaces.

```C
int netstring_list_size(char *buffer, size_t size, size_t *ptotal);
```

### netstring_list_count

size_t netstring_encode_new(char **netstring, char *data, size_t len);
Retrieves the number of items in a netstring list.

This allocates and creates a netstring containing the first `len` bytes of `data`. This must be manually freed by the client. If `len` is 0 then no data will be read from `data`, and it may be null.
```C
int netstring_list_count(char *buffer, size_t size, int *pcount);
```

Contributing
------------

All this code is Public Domain. If you want to contribute, you can send bug reports, or fork the project on GitHub. Contributions are welcomed with open arms.
All this code is Public Domain. If you want to contribute, you can send bug reports, or fork the project on GitHub. Contributions are welcomed with open arms.
1 change: 0 additions & 1 deletion makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
CFLAGS = -O2 -Wall
LDFLAGS= -lm

.PHONY: test clean

Expand Down
108 changes: 84 additions & 24 deletions netstring.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include "netstring.h"

/* Reads a netstring from a `buffer` of length `buffer_length`. Writes
Expand All @@ -28,12 +27,14 @@
D. J. Bernstein's reference implementation.

Example:
if (netstring_read("3:foo,", 6, &str, &len) < 0) explode_and_die();
if (netstring_read(&buf, &buflen, &str, &len) < 0) failed();
*/
int netstring_read(char *buffer, size_t buffer_length,
char **netstring_start, size_t *netstring_length) {
int netstring_read(char **pbuffer, size_t *pbuffer_length,
char **netstring_start, size_t *netstring_length) {
int i;
size_t len = 0;
char *buffer = *pbuffer;
size_t buffer_length = *pbuffer_length;

/* Write default values for outputs */
*netstring_start = NULL; *netstring_length = 0;
Expand Down Expand Up @@ -64,41 +65,100 @@ int netstring_read(char *buffer, size_t buffer_length,
/* Read the colon */
if (buffer[i++] != ':') return NETSTRING_ERROR_NO_COLON;

/* Test for the trailing comma, and set the return values */
/* Test for the trailing comma */
if (buffer[i + len] != ',') return NETSTRING_ERROR_NO_COMMA;
*netstring_start = &buffer[i]; *netstring_length = len;

/* Set the return values */
*netstring_start = &buffer[i];
*netstring_length = len;
*pbuffer = *netstring_start + len + 1;
*pbuffer_length = buffer_length - (i + len + 1);

return 0;
}

/* Retrieves the size of the concatenated netstrings */
int netstring_list_size(char *buffer, size_t size, size_t *ptotal) {
char *str, *base = buffer;
size_t len, remaining = size;
int rc;

while( remaining>0 && (rc=netstring_read(&base, &remaining, &str, &len))==0 ){
}

if( rc==NETSTRING_ERROR_NO_LENGTH || rc==NETSTRING_ERROR_TOO_SHORT ) rc = 0;
*ptotal = size - remaining;
return rc;
}

/* Retrieves the number of concatenated netstrings */
int netstring_list_count(char *buffer, size_t size, int *pcount) {
char *str, *base = buffer;
size_t len, remaining = size;
int rc, count = 0;

while( remaining>0 && (rc=netstring_read(&base, &remaining, &str, &len))==0 ){
count++;
}

if( rc==NETSTRING_ERROR_NO_LENGTH || rc==NETSTRING_ERROR_TOO_SHORT ) rc = 0;
*pcount = count;
return rc;
}

/* count the number of digits (base 10) in a positive integer */
int numdigits(size_t len) {
int n = 1;
if ( len >= 100000000 ) { n += 8; len /= 100000000; }
if ( len >= 10000 ) { n += 4; len /= 10000; }
if ( len >= 100 ) { n += 2; len /= 100; }
if ( len >= 10 ) { n += 1; }
return n;
}

/* Return the length, in ASCII characters, of a netstring containing
`data_length` bytes. */
size_t netstring_buffer_size(size_t data_length) {
if (data_length == 0) return 3;
return (size_t)ceil(log10((double)data_length + 1)) + data_length + 2;
return (size_t)numdigits(data_length) + data_length + 2;
}

/* Allocate and create a netstring containing the first `len` bytes of
`data`. This must be manually freed by the client. If `len` is 0
then no data will be read from `data`, and it may be NULL. */
size_t netstring_encode_new(char **netstring, char *data, size_t len) {
char *ns;
size_t num_len = 1;
then no data will be read from `data`, and it may be NULL.
Returns the netstring size not including the null terminator */
size_t netstring_add_ex(char **netstring, char *data, size_t len) {
size_t num_len, size_prev=0, size_next;
char *ptr;

if (netstring == 0 || (len > 0 && data == 0)) return 0;

num_len = numdigits(len);
size_next = num_len + len + 2;

if (*netstring == 0) {
ptr = malloc(size_next + 1);
if (ptr == 0) return 0;
*netstring = ptr;
} else {
size_prev = strlen(*netstring);
ptr = realloc(*netstring, size_prev + size_next + 1);
if (ptr == 0) return 0;
*netstring = ptr;
ptr += size_prev;
}

if (len == 0) {
ns = malloc(3);
ns[0] = '0';
ns[1] = ':';
ns[2] = ',';
strcpy(ptr, "0:,");
} else {
num_len = (size_t)ceil(log10((double)len + 1));
ns = malloc(num_len + len + 2);
sprintf(ns, "%lu:", (unsigned long)len);
memcpy(ns + num_len + 1, data, len);
ns[num_len + len + 1] = ',';
sprintf(ptr, "%lu:", (unsigned long)len);
ptr += num_len + 1;
memcpy(ptr, data, len);
ptr += len; *ptr = ',';
ptr++; *ptr = 0;
}
return size_prev + size_next;
}

*netstring = ns;
return num_len + len + 2;
size_t netstring_add(char **netstring, char *data) {
return netstring_add_ex(netstring, data, strlen(data));
}

10 changes: 7 additions & 3 deletions netstring.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@

#include <string.h>

int netstring_read(char *buffer, size_t buffer_length,
char **netstring_start, size_t *netstring_length);
size_t netstring_add(char **netstring, char *data);
size_t netstring_add_ex(char **netstring, char *data, size_t len);

int netstring_read(char **buffer_start, size_t *buffer_length,
char **netstring_start, size_t *netstring_length);

size_t netstring_buffer_size(size_t data_length);

size_t netstring_encode_new(char **netstring, char *data, size_t len);
int netstring_list_size(char *buffer, size_t size, size_t *ptotal);
int netstring_list_count(char *buffer, size_t size, int *pcount);

/* Errors that can occur during netstring parsing */
#define NETSTRING_ERROR_TOO_LONG -1
Expand Down
Loading