0
0
mirror of https://github.com/cjdelisle/cjdns synced 2025-10-05 16:22:54 +02:00

Unified Errors: Move JSON-benc to Rust, update bencoder, and remove Er.h entirely.

This commit is contained in:
Caleb James DeLisle
2024-09-19 00:01:11 +00:00
parent 0ad8909af0
commit 513a4b3cec
36 changed files with 1110 additions and 1287 deletions

14
Cargo.lock generated
View File

@@ -165,6 +165,16 @@ version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "bendy"
version = "0.3.2"
source = "git+https://github.com/cjdelisle/bendy-cjdns?tag=v0.3.2-cjdns#21849683c9e9b83b8d217d044c1da78ee573926b"
dependencies = [
"failure",
"serde",
"serde_bytes",
]
[[package]]
name = "bendy"
version = "0.3.2"
@@ -354,7 +364,7 @@ name = "cjdns-bencode"
version = "0.1.0"
source = "git+https://github.com/cjdelisle/cjdns-route-server?rev=9ea38312c9a849cf1a0be05332c9e4c96f49a758#9ea38312c9a849cf1a0be05332c9e4c96f49a758"
dependencies = [
"bendy",
"bendy 0.3.2 (git+https://github.com/CJDNS-Development-Team/bendy-cjdns?tag=v0.3.2-cjdns)",
"hex",
"serde",
]
@@ -402,11 +412,13 @@ dependencies = [
"anyhow",
"async-recursion",
"async-trait",
"bendy 0.3.2 (git+https://github.com/cjdelisle/bendy-cjdns?tag=v0.3.2-cjdns)",
"bindgen",
"boringtun",
"byteorder",
"cbindgen",
"cc",
"cjdns-bencode",
"cjdns-bytes",
"cjdns-crypto",
"cjdns-keys",

View File

@@ -14,6 +14,8 @@ cjdns-admin = { git = "https://github.com/cjdelisle/cjdns-route-server", rev = "
cjdns-crypto = { git = "https://github.com/cjdelisle/cjdns-route-server", rev = "9ea38312c9a849cf1a0be05332c9e4c96f49a758", version = "0.1.0" }
cjdns-keys = { git = "https://github.com/cjdelisle/cjdns-route-server", rev = "9ea38312c9a849cf1a0be05332c9e4c96f49a758", version = "0.1.0" }
cjdns-bytes = { git = "https://github.com/cjdelisle/cjdns-route-server", rev = "9ea38312c9a849cf1a0be05332c9e4c96f49a758", version = "0.1.0" }
cjdns-bencode = { git = "https://github.com/cjdelisle/cjdns-route-server", rev = "9ea38312c9a849cf1a0be05332c9e4c96f49a758", version = "0.1.0" }
bendy = { git = "https://github.com/cjdelisle/bendy-cjdns", tag = "v0.3.2-cjdns", features = ["std", "serde"] }
sodiumoxide = { git = "https://github.com/cjdelisle/sodiumoxide", rev = "9f6a18d40a4db253edfebac9f2ce5c22d09b1f47", version = "0.2", default-features = false, features = ["std"] }
clap = { version = "4", features = ["derive"] }
const_format = "0.2"

View File

@@ -469,7 +469,8 @@ int Core_main(int argc, char** argv)
Log_debug(logger, "Finished getting pre-configuration from client");
struct Sockaddr_storage addr;
Err_assert(AddrIface_popAddr(&addr, preConf));
Dict* config = Er_assert(BencMessageReader_read(preConf, tempAlloc));
Dict* config = NULL;
Err_assert(BencMessageReader_read(&config, preConf, tempAlloc));
String* privateKeyHex = Dict_getStringC(config, "privateKey");
Dict* adminConf = Dict_getDictC(config, "admin");

View File

@@ -20,9 +20,9 @@
#include "util/Linker.h"
Linker_require("benc/Dict.c")
struct Dict_Entry;
typedef struct Dict_Entry Dict_Entry_t;
struct Dict_Entry {
struct Dict_Entry* next;
Dict_Entry_t* next;
String* key;
Object* val;
};

View File

@@ -20,9 +20,9 @@
#include "util/Linker.h"
Linker_require("benc/List.c")
struct List_Item;
typedef struct List_Item List_Item_t;
struct List_Item {
struct List_Item* next;
List_Item_t* next;
Object* elem;
};

View File

@@ -17,8 +17,6 @@
#include <stdint.h>
#include "memory/Allocator.h"
// Dictionaries and lists are pointers to the head entry so that the head can change.
typedef struct Dict_Entry* Dict;
typedef struct List_Item* List;
@@ -28,6 +26,8 @@ typedef struct String_s {
} String;
typedef String String_t;
typedef Dict Dict_t;
typedef List List_t;
enum Object_Type {
Object_INTEGER,
@@ -41,14 +41,12 @@ typedef struct {
enum Object_Type type;
union {
int64_t number;
String* string;
List* list;
Dict* dictionary;
String_t* string;
List_t* list;
Dict_t* dictionary;
} as;
} Object;
//#include "benc/List.h"
//#include "benc/Dict.h"
typedef Object Object_t;
#endif

View File

@@ -14,323 +14,42 @@
*/
#include "benc/serialization/json/JsonBencMessageReader.h"
#include "benc/List.h"
#include "benc/Dict.h"
#include "benc/String.h"
#include "exception/Er.h"
#include "benc/Object.h"
#include "exception/Err.h"
#include "memory/Allocator.h"
#include "rust/cjdns_sys/RTypes.h"
#include "rust/cjdns_sys/Rffi.h"
#include "wire/Message.h"
#include "util/Gcc.h"
#include "util/Hex.h"
#include "util/Base10.h"
#include <stdbool.h>
struct Context {
Message_t* const msg;
struct Allocator* const alloc;
const bool lax;
int line;
uintptr_t beginningLastLine;
};
static int getColumn(struct Context* ctx)
{
return (uintptr_t) Message_bytes(ctx->msg) - ctx->beginningLastLine;
}
#define ERROR0(ctx, message) \
return Er__raise(Gcc_SHORT_FILE, Gcc_LINE, ctx->alloc, \
"Error parsing config (line %d column %d): " message, \
ctx->line, getColumn(ctx))
#define ERROR(ctx, message, ...) \
return Er__raise(Gcc_SHORT_FILE, Gcc_LINE, ctx->alloc, \
"Error parsing config (line %d column %d): " message, \
ctx->line, getColumn(ctx), __VA_ARGS__)
static Er_DEFUN(uint8_t peak(struct Context* ctx))
{
if (!Message_getLength(ctx->msg)) { ERROR0(ctx, "Out of content while reading"); }
Er_ret(Message_bytes(ctx->msg)[0]);
}
static Er_DEFUN(void skip(struct Context* ctx, int num))
{
if (num > Message_getLength(ctx->msg)) { ERROR0(ctx, "Out of content while reading"); }
for (int i = 0; i < num; i++) {
if (Message_bytes(ctx->msg)[i] == '\n') {
ctx->beginningLastLine = (uintptr_t) &Message_bytes(ctx->msg)[i];
ctx->line++;
}
}
Er(Er_fromErr(Message_eshift(ctx->msg, -num)));
Er_ret();
}
static Er_DEFUN(bool assertChar(struct Context* ctx, char chr, bool lax))
{
char c = Er(peak(ctx));
if (c != chr) {
if (lax == true) { Er_ret(false); }
ERROR(ctx, "Expected char [%c] but got [%c]", chr, c);
}
Er_ret(true);
}
static Er_DEFUN(void parseComment(struct Context* ctx))
{
Er(assertChar(ctx, '/', false));
Er(skip(ctx, 1));
uint8_t secondChar = Er(peak(ctx));
if (secondChar != '/' && secondChar != '*') { ERROR(ctx, "Unexpected char [%c]", secondChar); }
bool lastCharSplat = false;
for (;;) {
Er(skip(ctx, 1));
uint8_t chr = Er(peak(ctx));
if (lastCharSplat && secondChar == '*' && chr == '/') {
// get rid of the trailing *
Er(skip(ctx, 1));
} else if (secondChar == '/' && chr == '\n') {
} else {
lastCharSplat = (chr == '*');
continue;
}
Er_ret();
}
}
static Er_DEFUN(void parseWhitespaceAndComments(struct Context* ctx))
{
for (;;) {
switch (Er(peak(ctx))) {
case '\n':
case ' ':
case '\r':
case '\t':
Er(skip(ctx, 1));
continue;
case '/':
Er(parseComment(ctx));
continue;
default: break;
}
Er_ret();
}
ERROR0(ctx, "Reached end of message while parsing");
}
static Er_DEFUN(String* parseString(struct Context* ctx))
{
Er(assertChar(ctx, '"', false));
int line = ctx->line;
uintptr_t beginningLastLine = ctx->beginningLastLine;
int msgLen = Message_getLength(ctx->msg);
String* out = NULL;
uint32_t pos = 0;
#define PUSHOUT(chr) do { \
if (out) { \
Assert_true(out->len > pos); \
out->bytes[pos] = (chr); \
} \
pos++; \
} while (0)
// CHECKFILES_IGNORE expecting a ;
for (;;) {
Er(skip(ctx, 1));
uint8_t bchar = Er(peak(ctx));
switch (bchar) {
case '"': {
Er(skip(ctx, 1));
if (out) { Er_ret(out); }
// got the length, reset and then copy the string next cycle
ctx->line = line;
ctx->beginningLastLine = beginningLastLine;
Er(Er_fromErr(Message_eshift(ctx->msg, msgLen - Message_getLength(ctx->msg))));
out = String_newBinary(NULL, pos, ctx->alloc);
pos = 0;
continue;
}
case '\0':
case '\n': {
ERROR0(ctx, "Unterminated string");
}
case '\\': {
Er(skip(ctx, 1));
uint8_t x = Er(peak(ctx));
Er(skip(ctx, 1));
if (x != 'x') {
ERROR0(ctx, "Char \\ only allowed if followed by x (as in \\xff)");
}
uint8_t high = Er(peak(ctx));
Er(skip(ctx, 1));
uint8_t low = Er(peak(ctx));
int byte = Hex_decodeByte(high, low);
if (byte < 0 || (byte > 255)) { ERROR0(ctx, "Invalid hex encoding"); }
PUSHOUT(byte);
continue;
}
default: {
PUSHOUT(bchar);
continue;
}
}
}
#undef PUSHOUT
}
static Er_DEFUN(int64_t parseInteger(struct Context* ctx))
{
Er_ret( Er(Base10_read(ctx->msg)) );
}
static Er_DEFUN(Object* parseGeneric(struct Context* ctx));
static Er_DEFUN(List* parseList(struct Context* ctx))
{
Er(assertChar(ctx, '[', false));
Er(skip(ctx, 1));
struct List_Item* first = NULL;
struct List_Item* last = NULL;
for (int i = 0; ; i++) {
for (;;) {
Er(parseWhitespaceAndComments(ctx));
// lax mode, allow ,, extra ,,, commas
if (!ctx->lax || Er(peak(ctx)) != ',') { break; }
Er(skip(ctx, 1));
}
if (Er(peak(ctx)) == ']') {
Er(skip(ctx, 1));
List* out = Allocator_malloc(ctx->alloc, sizeof(List));
*out = first;
Er_ret(out);
}
if (i && Er(assertChar(ctx, ',', ctx->lax))) {
Er(skip(ctx, 1));
Er(parseWhitespaceAndComments(ctx));
}
struct List_Item* item = Allocator_calloc(ctx->alloc, sizeof(struct List_Item), 1);
item->elem = Er(parseGeneric(ctx));
if (last) {
last->next = item;
} else {
first = item;
}
last = item;
}
}
static Er_DEFUN(Dict* parseDict(struct Context* ctx))
{
Er(assertChar(ctx, '{', false));
Er(skip(ctx, 1));
struct Dict_Entry* last = NULL;
struct Dict_Entry* first = NULL;
for (int i = 0; ; i++) {
for (;;) {
Er(parseWhitespaceAndComments(ctx));
if (!ctx->lax || Er(peak(ctx)) != ',') { break; }
Er(skip(ctx, 1));
}
if (Er(peak(ctx)) == '}') {
Er(skip(ctx, 1));
Dict* out = Allocator_malloc(ctx->alloc, sizeof(Dict));
*out = first;
Er_ret(out);
}
if (i && Er(assertChar(ctx, ',', ctx->lax))) {
Er(skip(ctx, 1));
Er(parseWhitespaceAndComments(ctx));
}
struct Dict_Entry* entry = Allocator_calloc(ctx->alloc, sizeof(struct Dict_Entry), 1);
entry->key = Er(parseString(ctx));
Er(parseWhitespaceAndComments(ctx));
if (Er(assertChar(ctx, ':', ctx->lax))) {
Er(skip(ctx, 1));
Er(parseWhitespaceAndComments(ctx));
}
entry->val = Er(parseGeneric(ctx));
if (last) {
last->next = entry;
} else {
first = entry;
}
last = entry;
}
}
static Er_DEFUN(Object* parseGeneric(struct Context* ctx))
{
Object* out = Allocator_calloc(ctx->alloc, sizeof(Object), 1);
uint8_t c = Er(peak(ctx));
switch (c) {
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
out->type = Object_INTEGER;
out->as.number = Er(parseInteger(ctx));
break;
}
case '[': {
out->type = Object_LIST;
out->as.list = Er(parseList(ctx));
break;
}
case '{': {
out->type = Object_DICT;
out->as.dictionary = Er(parseDict(ctx));
break;
}
case '"': {
out->type = Object_STRING;
out->as.string = Er(parseString(ctx));
break;
}
default:
ERROR(ctx, "While looking for something to parse: "
"expected one of - 0 1 2 3 4 5 6 7 8 9 [ { \", found [%c]", c);
}
Er_ret(out);
}
Er_DEFUN(Dict* JsonBencMessageReader_read(
Err_DEFUN JsonBencMessageReader_read(
Dict_t** out,
Message_t* msg,
struct Allocator* alloc,
bool lax
)) {
struct Context ctx = {
.msg = msg,
.alloc = alloc,
.lax = lax,
.line = 1,
.beginningLastLine = (uintptr_t) Message_bytes(msg)
};
Er_ret( Er(parseDict(&ctx)) );
) {
return Rffi_Benc_decodeJson(out, msg, lax, alloc);
}
Err_DEFUN JsonBencMessageReader_write(
Dict_t* input,
Message_t* msg,
struct Allocator* alloc
) {
return Rffi_Benc_encodeJson(input, msg, alloc);
}
const char* JsonBencMessageReader_readNoExcept(
Message_t* msg,
struct Allocator* alloc,
Dict** outPtr,
Dict_t** out,
bool lax)
{
struct Er_Ret* er = NULL;
Dict* out = Er_check(&er, JsonBencMessageReader_read(msg, alloc, lax));
RTypes_Error_t* er = JsonBencMessageReader_read(out, msg, alloc, lax);
if (er) {
return er->message;
return Rffi_printError(er, alloc);
}
*outPtr = out;
return NULL;
}

View File

@@ -15,8 +15,8 @@
#ifndef JsonBencMessageReader_H
#define JsonBencMessageReader_H
#include "benc/Dict.h"
#include "exception/Er.h"
#include "benc/Object.h"
#include "exception/Err.h"
#include "memory/Allocator.h"
#include "wire/Message.h"
#include "util/Linker.h"
@@ -24,12 +24,23 @@ Linker_require("benc/serialization/json/JsonBencMessageReader.c")
#include <stdbool.h>
Er_DEFUN(Dict* JsonBencMessageReader_read(
Err_DEFUN JsonBencMessageReader_read(
Dict_t** out,
Message_t* msg,
struct Allocator* alloc,
bool lax
));
);
Err_DEFUN JsonBencMessageReader_write(
Dict_t* input,
Message_t* msg,
struct Allocator* alloc
);
const char* JsonBencMessageReader_readNoExcept(
Message_t* msg, struct Allocator* alloc, Dict** outPtr, bool lax);
Message_t* msg,
struct Allocator* alloc,
Dict** out,
bool lax);
#endif

View File

@@ -1,594 +0,0 @@
/* vim: set expandtab ts=4 sw=4: */
/*
* You may redistribute this program and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "memory/Allocator.h"
#include "io/Reader.h"
#include "io/Writer.h"
#include "benc/Dict.h"
#include "benc/List.h"
#include "benc/String.h"
#include "benc/serialization/BencSerializer.h"
#include "benc/serialization/json/JsonBencSerializer.h"
#include "util/Bits.h"
#include "util/Hex.h"
#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
static int32_t parseGeneric(struct Reader* reader,
struct Allocator* allocator,
Object** output);
static int32_t serializeGenericWithPadding(struct Writer* writer,
size_t padSpaceCount,
const Object* obj);
/** What the name says. */
static const char* thirtyTwoSpaces = " ";
/**
* Write some number of spaces for padding.
*
* @param padSpaces the number of spaces to pad.
* @param padCounter an integer which is used for internal bookkeeping.
* @param writer where to write the padding.
*/
#define PAD(padSpaces, padCounter, writer) \
padCounter = 0; \
while (32 < padSpaces + padCounter) { \
Writer_write(writer, thirtyTwoSpaces, 32); \
padCounter += 32; \
} \
Writer_write(writer, thirtyTwoSpaces, padSpaces - padCounter)
static inline int outOfContent()
{
return -2;
}
#define OUT_OF_CONTENT_TO_READ outOfContent()
static inline int unparsable()
{
return -3;
}
#define UNPARSABLE unparsable()
/** @see BencSerializer.h */
static int32_t serializeString(struct Writer* writer,
const String* string)
{
Writer_write(writer, "\"", 1);
size_t i;
uint8_t chr;
char buffer[5];
for (i = 0; i < string->len; i++) {
chr = (uint8_t) string->bytes[i] & 0xFF;
/* Nonprinting chars, \ and " are hex'd */
if (chr < 126 && chr > 31 && chr != '\\' && chr != '"') {
snprintf(buffer, 5, "%c", chr);
Writer_write(writer, buffer, 1);
} else {
snprintf(buffer, 5, "\\x%.2X", chr);
Writer_write(writer, buffer, 4);
}
}
return Writer_write(writer, "\"", 1);
}
/**
* Read until 1 char after the target character.
*/
static inline int readUntil(uint8_t target, struct Reader* reader)
{
uint8_t nextChar;
do {
if (Reader_read(reader, (char*)&nextChar, 1)) {
printf("Unexpected end of input while looking for '%c'\n",target);
return OUT_OF_CONTENT_TO_READ;
}
} while (nextChar != target);
return 0;
}
static inline int parseString(struct Reader* reader,
struct Allocator* allocator,
String** output)
{
#define BUFF_SZ (1<<8)
#define BUFF_MAX (1<<20)
int curSize = BUFF_SZ;
struct Allocator* localAllocator = Allocator_child(allocator);
uint8_t* buffer = Allocator_malloc(localAllocator, curSize);
if (readUntil('"', reader) || Reader_read(reader, buffer, 1)) {
printf("Unterminated string\n");
Allocator_free(localAllocator);
return OUT_OF_CONTENT_TO_READ;
}
for (int i = 0; i < BUFF_MAX - 1; i++) {
if (buffer[i] == '\\') {
// \x01 (skip the x)
Reader_skip(reader, 1);
uint8_t hex[2];
if (Reader_read(reader, (char*)hex, 2)) {
printf("Unexpected end of input parsing escape sequence\n");
Allocator_free(localAllocator);
return OUT_OF_CONTENT_TO_READ;
}
int byte = Hex_decodeByte(hex[0], hex[1]);
if (byte == -1) {
printf("Invalid escape \"%c%c\" after \"%.*s\"\n",hex[0],hex[1],i+1,buffer);
Allocator_free(localAllocator);
return UNPARSABLE;
}
buffer[i] = (uint8_t) byte;
} else if (buffer[i] == '"') {
*output = String_newBinary((char*)buffer, i, allocator);
Allocator_free(localAllocator);
return 0;
}
if (i == curSize - 1) {
curSize <<= 1;
buffer = Allocator_realloc(localAllocator, buffer, curSize);
}
if (Reader_read(reader, buffer + i + 1, 1)) {
if (i+1 <= 20) {
printf("Unterminated string \"%.*s\"\n", i+1, buffer);
} else {
printf("Unterminated string starting with \"%.*s...\"\n", 20, buffer);
}
Allocator_free(localAllocator);
return OUT_OF_CONTENT_TO_READ;
}
}
printf("Maximum string length of %d bytes exceeded.\n",BUFF_SZ);
Allocator_free(localAllocator);
return UNPARSABLE;
#undef BUFF_SZ
#undef BUFF_MAX
}
/** @see BencSerializer.h */
static int32_t serializeint64_t(struct Writer* writer,
int64_t integer)
{
char buffer[32];
Bits_memset(buffer, 0, 32);
snprintf(buffer, 32, "%" PRId64, integer);
return Writer_write(writer, buffer, CString_strlen(buffer));
}
/** @see BencSerializer.h */
static int32_t parseint64_t(struct Reader* reader,
int64_t* output)
{
uint8_t buffer[32];
for (int i = 0; i < 21; i++) {
int32_t status = Reader_read(reader, buffer + i, 0);
if (i == 0 && buffer[i] == '-' && status == 0) {
// It's just a negative number, no need to fail it.
continue;
}
if (buffer[i] < '0' || buffer[i] > '9' || status != 0 /* end of input */) {
buffer[i] = '\0';
int64_t out = strtol((char*)buffer, NULL, 10);
// Failed parse causes 0 to be set.
if (out == 0 && buffer[0] != '0' && (buffer[0] != '-' || buffer[1] != '0')) {
printf("Failed to parse \"%s\": not a number\n",buffer);
return UNPARSABLE;
}
if ((out == INT64_MAX || out == INT64_MIN) && errno == ERANGE) {
printf("Failed to parse \"%s\": number too large/small\n",buffer);
return UNPARSABLE;
}
*output = out;
return 0;
}
Reader_skip(reader, 1);
}
// Larger than the max possible int64.
buffer[22] = '\0';
printf("Failed to parse \"%s\": number too large\n",buffer);
return UNPARSABLE;
}
/**
* Serialize a bencoded list with padding at the beginning of each line.
*
* @param writer the place to write the output to.
* @param padSpaceCount the number of spaces to place at the beginning of each line.
* @param list the list to serialize
*/
static int32_t serializeListWithPadding(struct Writer* writer,
const size_t padSpaceCount,
const List* list)
{
int padCounter;
Writer_write(writer, "[\n", 2);
const struct List_Item* entry = *list;
while (entry != NULL) {
PAD(padSpaceCount + 2, padCounter, writer);
serializeGenericWithPadding(writer, padSpaceCount + 2, entry->elem);
entry = entry->next;
if (entry != NULL) {
Writer_write(writer, ",\n", 2);
}
}
Writer_write(writer, "\n", 1);
PAD(padSpaceCount, padCounter, writer);
return Writer_write(writer, "]", 1);
}
/** @see BencSerializer.h */
static int32_t serializeList(struct Writer* writer,
const List* list)
{
return serializeListWithPadding(writer, 0, list);
}
/**
* Parse a comment in with "slash splat" or double slash notation,
* leave the reader on the first character after the last end of comment mark.
*/
static inline int parseComment(struct Reader* reader)
{
char chars[2];
int ret = Reader_read(reader, &chars, 2);
if (ret) {
printf("Warning: expected comment\n");
return OUT_OF_CONTENT_TO_READ;
}
if (chars[0] != '/') {
printf("Warning: expected a comment starting with '/', instead found '%c'\n",chars[0]);
return UNPARSABLE;
}
switch (chars[1]) {
case '*':;
do {
readUntil('*', reader);
} while (!(ret = Reader_read(reader, &chars, 1)) && chars[0] != '/');
if (ret) {
printf("Unterminated multiline comment\n");
return OUT_OF_CONTENT_TO_READ;
}
return 0;
case '/':;
return readUntil('\n', reader);
default:
printf("Warning: expected a comment starting with \"//\" or \"/*\", "
"instead found \"/%c\"\n",chars[1]);
return UNPARSABLE;
}
}
/** @see BencSerializer.h */
static int32_t parseList(struct Reader* reader,
struct Allocator* allocator,
List* output)
{
char nextChar;
readUntil('[', reader);
Object* element;
struct List_Item* thisEntry = NULL;
struct List_Item** lastEntryPointer = output;
int ret;
for (;;) {
for (;;) {
if (Reader_read(reader, &nextChar, 0) != 0) {
printf("Unterminated list\n");
return OUT_OF_CONTENT_TO_READ;
}
if (nextChar == '/') {
if ((ret = parseComment(reader)) != 0) {
return ret;
}
continue;
}
switch (nextChar) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '[':
case '{':
case '"':
break;
case ']':
Reader_skip(reader, 1);
return 0;
default:
// FIXME(gerard): silently skipping anything we don't understand
// might not be the best idea
Reader_skip(reader, 1);
continue;
}
break;
}
if ((ret = parseGeneric(reader, allocator, &element)) != 0) {
return ret;
}
thisEntry = Allocator_malloc(allocator, sizeof(struct List_Item));
thisEntry->elem = element;
thisEntry->next = NULL;
// Read backwards so that the list reads out forward.
*lastEntryPointer = thisEntry;
lastEntryPointer = &(thisEntry->next);
}
}
/**
* Serialize a bencoded dictionary with padding before each line.
*
* @param writer the place to write the output to.
* @param padSpaceCount the number of spaces to place at the beginning of each line.
* @param dictionary the dictionary to serialize.
*/
static int32_t serializeDictionaryWithPadding(struct Writer* writer,
size_t padSpaceCount,
const Dict* dictionary)
{
int padCounter = 0;
Writer_write(writer, "{\n", 2);
const struct Dict_Entry* entry = *dictionary;
while (entry != NULL) {
PAD(padSpaceCount + 2, padCounter, writer);
serializeString(writer, entry->key);
Writer_write(writer, " : ", 3);
serializeGenericWithPadding(writer, padSpaceCount + 2, entry->val);
entry = entry->next;
if (entry != NULL) {
Writer_write(writer, ",\n", 2);
}
}
Writer_write(writer, "\n", 1);
PAD(padSpaceCount, padCounter, writer);
return Writer_write(writer, "}", 1);
}
/** @see BencSerializer.h */
static int32_t serializeDictionary(struct Writer* writer,
const Dict* dictionary)
{
return serializeDictionaryWithPadding(writer, 0, dictionary);
}
/** @see BencSerializer.h */
static int32_t parseDictionary(struct Reader* reader,
struct Allocator* allocator,
Dict* output)
{
uint8_t nextChar;
readUntil('{', reader);
String* key;
Object* value;
struct Dict_Entry* first = NULL;
struct Dict_Entry* last = NULL;
int ret = 0;
for (;;) {
while (!ret) {
ret = Reader_read(reader, &nextChar, 0);
switch (nextChar) {
case '"':
break;
case '}':
Reader_skip(reader, 1);
*output = first;
return 0;
case '/':
parseComment(reader);
continue;
default:
Reader_skip(reader, 1);
continue;
}
break;
}
if (ret) {
printf("Unterminated dictionary\n");
return OUT_OF_CONTENT_TO_READ;
}
// Get key and value.
if ((ret = parseString(reader, allocator, &key)) != 0) {
return ret;
}
readUntil(':', reader);
if ((ret = parseGeneric(reader, allocator, &value)) != 0) {
return ret;
}
/* Allocate the entry. */
struct Dict_Entry* entry = Allocator_calloc(allocator, sizeof(struct Dict_Entry), 1);
entry->key = key;
entry->val = value;
if (last) {
last->next = entry;
} else {
first = entry;
}
last = entry;
}
}
static int32_t parseGeneric(struct Reader* reader,
struct Allocator* allocator,
Object** output)
{
int ret = 0;
char firstChar;
for (;;) {
ret = Reader_read(reader, &firstChar, 0);
switch (firstChar) {
case ' ':
case '\r':
case '\n':
case '\t':
Reader_skip(reader, 1);
continue;
case '/':;
if ((ret = parseComment(reader)) != 0) {
return ret;
}
continue;
default:
break;
}
if (ret) {
printf("Unexpected end of input\n");
return OUT_OF_CONTENT_TO_READ;
}
break;
}
Object* out = Allocator_malloc(allocator, sizeof(Object));
switch (firstChar) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':;
// int64_t. Int is special because it is not a pointer but a int64_t.
int64_t bint;
if ((ret = parseint64_t(reader, &bint)) == UNPARSABLE) {
break;
}
out->type = Object_INTEGER;
out->as.number = bint;
break;
case '[':;
// List.
List* list = Allocator_calloc(allocator, sizeof(List), 1);
ret = parseList(reader, allocator, list);
out->type = Object_LIST;
out->as.list = list;
break;
case '{':;
// Dictionary
Dict* dict = Allocator_calloc(allocator, sizeof(Dict), 1);
ret = parseDictionary(reader, allocator, dict);
out->type = Object_DICT;
out->as.dictionary = dict;
break;
case '"':;
// String
String* string = NULL;
ret = parseString(reader, allocator, &string);
out->type = Object_STRING;
out->as.string = string;
break;
default:
printf("While looking for something to parse: "
"expected one of 0 1 2 3 4 5 6 7 8 9 [ { \", found '%c'\n", firstChar);
return UNPARSABLE;
}
if (ret != 0) {
// Something went wrong while parsing.
return ret;
}
*output = out;
return 0;
}
/**
* Serialize a benc object into a json string with padding before each line.
*
* @param writer a Writer which to write the output to.
* @param number of pad spaces to place before each line.
* @param obj the object to serialize.
* @return -2 if the type of object cannot be determined, otherwise
* whatever is returned by the Writer.
*/
static int32_t serializeGenericWithPadding(struct Writer* writer,
size_t padSpaceCount,
const Object* obj)
{
switch (obj->type)
{
case Object_STRING:
return serializeString(writer, obj->as.string);
case Object_DICT:
return serializeDictionaryWithPadding(writer, padSpaceCount, obj->as.dictionary);
case Object_LIST:
return serializeListWithPadding(writer, padSpaceCount, obj->as.list);
case Object_INTEGER:
return serializeint64_t(writer, obj->as.number);
default:
return -2;
}
}
static const struct BencSerializer SERIALIZER =
{
.serializeString = serializeString,
.parseString = parseString,
.serializeint64_t = serializeint64_t,
.parseint64_t = parseint64_t,
.serializeList = serializeList,
.parseList = parseList,
.serializeDictionary = serializeDictionary,
.parseDictionary = parseDictionary
};
const struct BencSerializer* JsonBencSerializer_get(void)
{
return &SERIALIZER;
}
#undef PAD

View File

@@ -1,23 +0,0 @@
/* vim: set expandtab ts=4 sw=4: */
/*
* You may redistribute this program and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef JsonBencSerializer_H
#define JsonBencSerializer_H
#include "benc/serialization/BencSerializer.h"
#include "util/Linker.h"
Linker_require("benc/serialization/json/JsonBencSerializer.c")
const struct BencSerializer* JsonBencSerializer_get(void);
#endif

View File

@@ -20,7 +20,7 @@
void CJDNS_FUZZ_MAIN(void* vctx, Message_t* fuzz)
{
struct Allocator* alloc = (struct Allocator*) vctx;
Dict* out = NULL;
Dict_t* out = NULL;
const char* res = JsonBencMessageReader_readNoExcept(fuzz, alloc, &out, true);
if (res) { return; }
res = JsonBencMessageReader_readNoExcept(fuzz, alloc, &out, false);

View File

@@ -18,102 +18,110 @@
#include "benc/Dict.h"
#include "benc/List.h"
#include "benc/serialization/standard/BencMessageReader.h"
#include "exception/Er.h"
#include "exception/Err.h"
#include "rust/cjdns_sys/RTypes.h"
#include "rust/cjdns_sys/Rffi.h"
#include "wire/Message.h"
#include "util/Base10.h"
static Er_DEFUN(Object* readGeneric(Message_t* msg, struct Allocator* alloc));
static Err_DEFUN readGeneric(Object** outP, Message_t* msg, struct Allocator* alloc);
static Er_DEFUN(int64_t readInt(Message_t* msg, struct Allocator* alloc))
static Err_DEFUN readInt(int64_t* outP, Message_t* msg, struct Allocator* alloc)
{
int64_t num = Er(Base10_read(msg));
int64_t num = -1;
Err(Base10_read(&num, msg));
uint8_t e = '\0';
Er(Er_fromErr(Message_epop8(&e, msg)));
Err(Message_epop8(&e, msg));
if (e != 'e') {
Er_raise(Message_getAlloc(msg), "Int not terminated with 'e'");
Err_raise(Message_getAlloc(msg), "Int not terminated with 'e'");
}
Er_ret(num);
*outP = num;
return NULL;
}
static Er_DEFUN(String* readString(Message_t* msg, struct Allocator* alloc))
static Err_DEFUN readString(String** outP, Message_t* msg, struct Allocator* alloc)
{
int64_t len = Er(Base10_read(msg));
int64_t len = -1;
Err(Base10_read(&len, msg));
if (len < 0) {
Er_raise(alloc, "Negative string length");
Err_raise(alloc, "Negative string length");
}
uint8_t flag = '\0';
Er(Er_fromErr(Message_epop8(&flag, msg)));
Err(Message_epop8(&flag, msg));
if (flag != ':') {
Er_raise(alloc, "String not deliniated with a ':'");
Err_raise(alloc, "String not deliniated with a ':'");
}
if (len > Message_getLength(msg)) {
Er_raise(alloc, "String too long");
Err_raise(alloc, "String too long");
}
String* str = String_newBinary(NULL, len, alloc);
Er(Er_fromErr(Message_epop(msg, str->bytes, len)));
Er_ret(str);
Err(Message_epop(msg, str->bytes, len));
*outP = str;
return NULL;
}
static Er_DEFUN(List* readList(Message_t* msg, struct Allocator* alloc))
static Err_DEFUN readList(List** outP, Message_t* msg, struct Allocator* alloc)
{
struct List_Item* last = NULL;
for (;;) {
uint8_t chr = '\0';
Er(Er_fromErr(Message_epop8(&chr, msg)));
Err(Message_epop8(&chr, msg));
if (chr == 'e') {
List* out = Allocator_malloc(alloc, sizeof(List));
*out = last;
Er_ret(out);
*outP = out;
return NULL;
}
Er(Er_fromErr(Message_epush8(msg, chr)));
Err(Message_epush8(msg, chr));
struct List_Item* item = Allocator_malloc(alloc, sizeof(struct List_Item));
item->elem = Er(readGeneric(msg, alloc));
Err(readGeneric(&item->elem, msg, alloc));
item->next = last;
last = item;
}
}
static Er_DEFUN(Dict* readDict(Message_t* msg, struct Allocator* alloc))
static Err_DEFUN readDict(Dict** outP, Message_t* msg, struct Allocator* alloc)
{
struct Dict_Entry* last = NULL;
for (;;) {
uint8_t chr = '\0';
Er(Er_fromErr(Message_epop8(&chr, msg)));
Err(Message_epop8(&chr, msg));
if (chr == 'e') {
Dict* out = Allocator_malloc(alloc, sizeof(Dict));
*out = last;
Er_ret(out);
*outP = out;
return NULL;
}
Er(Er_fromErr(Message_epush8(msg, chr)));
Err(Message_epush8(msg, chr));
struct Dict_Entry* entry = Allocator_malloc(alloc, sizeof(struct Dict_Entry));
entry->key = Er(readString(msg, alloc));
entry->val = Er(readGeneric(msg, alloc));
Err(readString(&entry->key, msg, alloc));
Err(readGeneric(&entry->val, msg, alloc));
entry->next = last;
last = entry;
}
}
static Er_DEFUN(Object* readGeneric(Message_t* msg, struct Allocator* alloc))
static Err_DEFUN readGeneric(Object** outP, Message_t* msg, struct Allocator* alloc)
{
uint8_t chr = '\0';
Er(Er_fromErr(Message_epop8(&chr, msg)));
Err(Message_epop8(&chr, msg));
Object* out = Allocator_calloc(alloc, sizeof(Object), 1);
switch (chr) {
case 'l': {
out->type = Object_LIST;
out->as.list = Er(readList(msg, alloc));
Err(readList(&out->as.list, msg, alloc));
break;
}
case 'd': {
out->type = Object_DICT;
out->as.dictionary = Er(readDict(msg, alloc));
Err(readDict(&out->as.dictionary, msg, alloc));
break;
}
case 'i': {
out->type = Object_INTEGER;
out->as.number = Er(readInt(msg, alloc));
Err(readInt(&out->as.number, msg, alloc));
break;
}
case '0':
@@ -127,34 +135,33 @@ static Er_DEFUN(Object* readGeneric(Message_t* msg, struct Allocator* alloc))
case '8':
case '9': {
out->type = Object_STRING;
Er(Er_fromErr(Message_epush8(msg, chr)));
out->as.string = Er(readString(msg, alloc));
Err(Message_epush8(msg, chr));
Err(readString(&out->as.string, msg, alloc));
break;
}
default: Er_raise(alloc, "Unexpected character in message [%c]", (char)chr);
default: Err_raise(alloc, "Unexpected character in message [%c]", (char)chr);
}
Er_ret(out);
*outP = out;
return NULL;
}
Er_DEFUN(Dict* BencMessageReader_read(Message_t* msg, struct Allocator* alloc))
Err_DEFUN BencMessageReader_read(Dict** outP, Message_t* msg, struct Allocator* alloc)
{
uint8_t d = '\0';
Er(Er_fromErr(Message_epop8(&d, msg)));
Err(Message_epop8(&d, msg));
if (d != 'd') {
Er_raise(alloc, "Message does not begin with a 'd' to open the dictionary");
Err_raise(alloc, "Message does not begin with a 'd' to open the dictionary");
}
Dict* out = Er(readDict(msg, alloc));
Er_ret(out);
Err(readDict(outP, msg, alloc));
return NULL;
}
const char* BencMessageReader_readNoExcept(
Message_t* msg, struct Allocator* alloc, Dict** outPtr)
{
struct Er_Ret* er = NULL;
Dict* out = Er_check(&er, BencMessageReader_read(msg, alloc));
RTypes_Error_t* er = BencMessageReader_read(outPtr, msg, alloc);
if (er) {
return er->message;
return Rffi_printError(er, alloc);
}
*outPtr = out;
return NULL;
}

View File

@@ -15,14 +15,14 @@
#ifndef BencMessageReader_H
#define BencMessageReader_H
#include "benc/Dict.h"
#include "exception/Er.h"
#include "benc/Object.h"
#include "exception/Err.h"
#include "memory/Allocator.h"
#include "wire/Message.h"
#include "util/Linker.h"
Linker_require("benc/serialization/standard/BencMessageReader.c")
Er_DEFUN(Dict* BencMessageReader_read(Message_t* msg, struct Allocator* alloc));
Err_DEFUN BencMessageReader_read(Dict** outP, Message_t* msg, struct Allocator* alloc);
const char* BencMessageReader_readNoExcept(
Message_t* msg, struct Allocator* alloc, Dict** outPtr);

View File

@@ -15,6 +15,7 @@
#include "exception/Err.h"
#include "rust/cjdns_sys/RTypes.h"
#include "util/events/Socket.h"
#include "wire/Message.h"
#define _POSIX_C_SOURCE 200112L
#include "client/AdminClient.h"
#include "admin/angel/Core.h"
@@ -25,7 +26,6 @@
#include "benc/Int.h"
#include "benc/List.h"
#include "benc/serialization/BencSerializer.h"
#include "benc/serialization/json/JsonBencSerializer.h"
#include "benc/serialization/json/JsonBencMessageReader.h"
#include "benc/serialization/standard/BencMessageReader.h"
#include "benc/serialization/standard/BencMessageWriter.h"
@@ -681,32 +681,34 @@ int cjdroute2_main(int argc, char** argv)
// and if the old parser fails or the parsed content contains "version": 2,
// fail to launch
Message_t* confMsg = readToMsg(stdin, allocator);
struct Reader* confReader = ArrayReader_new(Message_bytes(confMsg), Message_getLength(confMsg), allocator);
Dict _config;
Dict* config = &_config;
const char* err = JsonBencMessageReader_readNoExcept(confMsg, allocator, &config, false);
if (!err) {
// If old version is specified, always use old parser so there is no possible error
int64_t* v = Dict_getIntC(config, "version");
if (!v || *v < 2) { err = "using old parser"; }
}
Dict* config = NULL;
RTypes_Error_t* err = JsonBencMessageReader_read(
&config,
Message_clone(confMsg, allocator),
allocator,
false
);
if (err) {
if (JsonBencSerializer_get()->parseDictionary(confReader, allocator, &_config)) {
fprintf(stderr, "Failed to parse configuration.\n%s\n", err);
return -1;
}
int64_t* version = Dict_getIntC(config, "version");
if (version && *version >= 2) {
fprintf(stderr, "Invalid cjdroute.conf\n%s\n", err);
return -1;
// Try again with lax parsing to check if version is < 2
Err_assert(JsonBencMessageReader_read(
&config,
Message_clone(confMsg, allocator),
allocator,
true
));
int64_t* ver = Dict_getIntC(config, "version");
if (ver && *ver > 1) {
Err_assert(err);
}
}
if (argc == 2 && CString_strcmp(argv[1], "--cleanconf") == 0) {
// Slip a v2 in there because at this point, the conf file is definitely v2 valid
Dict_putIntC(config, "version", 2, allocator);
struct Message* msg = Message_new(0, 10000, allocator);
Err_assert(JsonBencMessageReader_write(config, msg, allocator));
struct Writer* stdoutWriter = FileWriter_new(stdout, allocator);
JsonBencSerializer_get()->serializeDictionary(stdoutWriter, config);
stdoutWriter->write(stdoutWriter, Message_bytes(msg), Message_getLength(msg));
printf("\n");
return 0;
}
@@ -819,7 +821,8 @@ int cjdroute2_main(int argc, char** argv)
Message_t* fromCoreMsg = NULL;
Err_assert(InterfaceWaiter_waitForData(&fromCoreMsg, corePipe, eventBase, allocator));
Dict* responseFromCore = Er_assert(BencMessageReader_read(fromCoreMsg, allocator));
Dict* responseFromCore = NULL;
Err_assert(BencMessageReader_read(&responseFromCore, fromCoreMsg, allocator));
// --------------------- Close the Core Pipe --------------------- //
Allocator_free(corePipeAlloc);

View File

@@ -1,75 +0,0 @@
/* vim: set expandtab ts=4 sw=4: */
/*
* You may redistribute this program and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#define _POSIX_C_SOURCE 200112L
#include "exception/Er.h"
#include "util/CString.h"
#include "rust/cjdns_sys/Rffi.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
Gcc_USE_RET
struct Er_Ret* Er__raise(char* file, int line, struct Allocator* alloc, char* format, ...)
{
va_list args;
va_start(args, format);
if (alloc) {
int written = snprintf(NULL, 0, "%s:%d ", file, line);
Assert_true(written >= 0);
va_list argsCopy;
va_copy(argsCopy, args);
int written2 = vsnprintf(NULL, 0, format, argsCopy);
Assert_true(written2 >= 0);
va_end(argsCopy);
int len = written + written2 + 1;
char* buf = Allocator_calloc(alloc, len, 1);
snprintf(buf, len, "%s:%d ", file, line);
vsnprintf(&buf[written], len - written, format, args);
struct Er_Ret* res = Allocator_calloc(alloc, sizeof(struct Er_Ret), 1);
res->message = buf;
va_end(args);
return res;
} else {
fprintf(stderr, "%s:%d ", file, line);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
}
abort();
exit(100);
}
void Er__assertFail(struct Er_Ret* er)
{
if (!er) { return; }
Assert_failure("%s", er->message);
}
Er_DEFUN(void Er_fromErr(RTypes_Error_t* err)) {
if (err) {
Allocator_t* alloc = Allocator_new(8192);
char* p = Rffi_printError(err, alloc);
struct Er_Ret* res = Allocator_calloc(alloc, sizeof(struct Er_Ret), 1);
res->message = p;
return res;
}
return NULL;
}

View File

@@ -1,66 +0,0 @@
/* vim: set expandtab ts=4 sw=4: */
/*
* You may redistribute this program and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef Er_H
#define Er_H
#include "exception/Err.h"
#include "memory/Allocator.h"
#include "rust/cjdns_sys/RTypes.h"
#include "util/Gcc.h"
#include "util/Js.h"
#include "util/Linker.h"
Linker_require("exception/Er.c")
struct Er_Ret
{
const char* message;
};
Js({ this.Er_JS = require("../exception/Er.js").create(); })
#define Er_DEFUN(...) \
Gcc_USE_RET Js_or({ return this.Er_JS.defun(Js_Q __VA_ARGS__ Js_Q) }, __VA_ARGS__)
Gcc_PRINTF(4, 5)
Gcc_USE_RET
struct Er_Ret* Er__raise(char* file, int line, struct Allocator* alloc, char* format, ...);
#define Er_raise(...) \
do { \
struct Er_Ret* Er_ret = Er__raise(Gcc_SHORT_FILE, Gcc_LINE, __VA_ARGS__); \
Js_or({ return 'return Er_ret;' }, Er__assertFail(Er_ret)); \
} while (0)
// CHECKFILES_IGNORE missing ;
#define Er(expr) Js_or({ return this.Er_JS.er(Js_Q expr Js_Q, Gcc_SHORT_FILE, Gcc_LINE); }, expr)
#define Er_assert(expr) \
Js_or({ return this.Er_JS.assert(Js_Q expr Js_Q, Gcc_SHORT_FILE, Gcc_LINE); }, expr)
#define Er_check(ret, expr) \
Js_or({ return this.Er_JS.check(#ret, Js_Q expr Js_Q, Gcc_SHORT_FILE, Gcc_LINE); }, expr)
#define Er_ret(val) Js_or({ return this.Er_JS.ret(Js_Q val Js_Q); }, return val)
static inline struct Er_Ret* Er_unwind(const char* file, int line, struct Er_Ret* ret)
{
return ret;
}
void Er__assertFail(struct Er_Ret* er);
Er_DEFUN(void Er_fromErr(RTypes_Error_t* err));
#endif

View File

@@ -1,114 +0,0 @@
/* vim: set expandtab ts=4 sw=4: */
/*
* You may redistribute this program and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
'use strict';
const trim = (x) => {
if (x[0] !== ' ' || x[x.length-1] !== ' ') {
throw new Error("INTERNAL: Er input must begin " +
"with and end with a space, unrecognized [" + x + "]");
}
return x.trim();
};
const defun = (ctx, spec) => {
trim(spec);
if (spec.lastIndexOf(')') !== spec.length - 2) {
throw new Error("Er function spec must end " +
"with a ), unrecognized [" + spec + "]");
}
let c = 1;
let i = spec.length - 3;
for (; c && i >= 0; i--) {
c += spec[i] === ')';
c -= spec[i] === '(';
}
const args = spec.slice(i + 2, spec.length - 2);
const rettAndFunc = spec.slice(0, i + 1).trim();
const func = rettAndFunc.replace(/^.*\s+([a-zA-Z_][a-zA-Z0-9_]*)$/, (all, a) => a);
//console.log('defun: ' + rettAndFunc + ' - ' + args);
if (func === rettAndFunc) {
throw new Error("Could not parse function [" + spec + "]");
}
const rett = rettAndFunc.replace(/\s+[a-zA-Z_][a-zA-Z0-9_]*$/, '').trim();
ctx.activeFunction = ctx.functions[func] = { rett: rett };
if (rett === 'void') {
return 'struct Er_Ret* ' + func + '(' + args + ')';
} else {
return 'struct Er_Ret* ' + func + '(' + rett + ' *Er_returnValP, ' + args + ')';
}
};
const ret = (ctx, val) => {
val = trim(val);
if (ctx.activeFunction.rett === 'void') {
return 'return (struct Er_Ret*)0';
} else {
return '*Er_returnValP = ' + val + '; return (struct Er_Ret*)0';
}
};
const er = (ctx, assert, errOut, expr, file, line) => {
expr = trim(expr);
if (!/[a-zA-Z_][a-zA-Z0-9_]*\(.*\)$/.test(expr)) {
throw new Error("Er() expr must be in the form Er(funcName(arg1, arg2, ...)) " +
"in [" + expr + "]");
}
const funcName = expr.slice(0, expr.indexOf('('));
const f = ctx.functions[funcName];
if (!f) {
throw new Error("Er() not a defined function [" + funcName + "] in [" + expr + "]");
}
let ifret = `if (Er_ret) { return Er_unwind("${file}", ${line}, Er_ret); }`;
if (assert) {
ifret = `
if (Er_ret) {
struct Er_Ret** Er_errOut = ${errOut ? errOut : '(struct Er_Ret**)0'};
if (Er_errOut) {
*Er_errOut = Er_unwind("${file}", ${line}, Er_ret);
} else {
Er__assertFail(Er_unwind("${file}", ${line}, Er_ret));
}
}
`;
}
if (f.rett === 'void') {
return `do {
struct Er_Ret* Er_ret = ${expr};
${ifret}
} while (0)`;
} else {
const args = expr.slice(expr.indexOf('(') + 1);
return `(__extension__({
${f.rett} Er_returnVal;
__builtin_memset(&Er_returnVal, 0, sizeof(Er_returnVal));
struct Er_Ret* Er_ret = ${funcName}(&Er_returnVal, ${args};
${ifret}
Er_returnVal;
}))`;
}
};
module.exports.create = () => {
const ctx = {
activeFunction: undefined,
functions: {},
};
return {
defun: (spec) => defun(ctx, spec).replace(/\n/g, ' '),
ret: (val) => ret(ctx, val).replace(/\n/g, ' '),
er: (expr, file, line) => er(ctx, false, null, expr, file, line).replace(/\n/g, ' '),
assert: (expr, file, line) => er(ctx, true, null, expr, file, line).replace(/\n/g, ' '),
check: (out, expr, file, line) => er(ctx, true, out, expr, file, line).replace(/\n/g, ' '),
};
};

View File

@@ -13,7 +13,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "interface/ETHInterface_admin.h"
#include "exception/Er.h"
#include "exception/Err.h"
#include "interface/ETHInterface.h"
#include "benc/Int.h"
#include "admin/Admin.h"

View File

@@ -12,7 +12,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "exception/Er.h"
#include "exception/Err.h"
#include "rust/cjdns_sys/Rffi.h"
#include "benc/StringList.h"
#include "interface/UDPInterface.h"

View File

@@ -15,7 +15,7 @@
#ifndef UDPInterface_H
#define UDPInterface_H
#include "exception/Er.h"
#include "exception/Err.h"
#include "interface/addressable/AddrIface.h"
#include "benc/List.h"
#include "util/events/EventBase.h"

View File

@@ -48,7 +48,7 @@ Err_DEFUN TUNInterface_new(struct Iface** out,
{
char deviceFile[TUNInterface_IFNAMSIZ];
if (isTapMode) { Er_raise(alloc, "tap mode not supported on this platform"); }
if (isTapMode) { Err_raise(alloc, "tap mode not supported on this platform"); }
// We are on FreeBSD so we just need to read /dev/tunxx to create the tun interface
if (interfaceName) {

View File

@@ -94,7 +94,7 @@ Err_DEFUN TUNInterface_new(struct Iface** out,
int isTapMode,
EventBase_t* base,
struct Log* logger,
struct Allocator* alloc))
struct Allocator* alloc)
{
// tap mode is not supported at all by the sunos tun driver.
if (isTapMode) { Err_raise(alloc, "tap mode not supported on this platform"); }
@@ -139,7 +139,7 @@ Err_DEFUN TUNInterface_new(struct Iface** out,
} else if (tunFd2 < 0) {
error = "open(\"/dev/tun\") (opening for plumbing interface)";
}
Er_raise(alloc, "%s [%s]", error, strerror(err));
Err_raise(alloc, "%s [%s]", error, strerror(err));
}
struct lifreq ifr = {

View File

@@ -9,6 +9,8 @@ build = "build.rs"
cjdns-crypto = { workspace = true }
cjdns-keys = { workspace = true }
cjdns-bytes = { workspace = true }
cjdns-bencode = { workspace = true }
bendy = { workspace = true }
sodiumoxide = { workspace = true }
hex = { workspace = true }
log = { workspace = true }

View File

@@ -269,4 +269,11 @@ RTypes_Error_t *Rffi_Seeder_listDnsSeeds(RTypes_Seeder_DnsSeeds_t **seeds_out,
void Rffi_Seeder_new(Rffi_Seeder **seeder_out, Iface_t **iface_out, Allocator_t *alloc);
RTypes_Error_t *Rffi_Benc_decodeJson(Dict_t **out,
Message_t *msg,
bool lax_mode,
Allocator_t *alloc);
RTypes_Error_t *Rffi_Benc_encodeJson(Dict_t *input, Message_t *msg, Allocator_t *alloc);
#endif /* rffi_H */

View File

@@ -15,7 +15,9 @@
// This file is used to generate src/cffi.rs using bindgen
#include "benc/String.h"
#include "benc/Object.h"
#include "benc/List.h"
#include "benc/Dict.h"
#include "interface/Iface.h"
#include "interface/test/RustIface_test.h"
#include "crypto/CryptoAuth.h"
@@ -38,7 +40,9 @@ struct RBindings_Whitelist {
Iface_t b;
enum CryptoAuth_addUser_Res c;
Message_t d;
String_t e;
List_Item_t e;
Dict_Entry_t ee;
Object_t eee;
Log_t* f;
enum RBindings_Version g;
Sockaddr_t h;

View File

@@ -10,13 +10,10 @@ pub type __uint8_t = ::std::os::raw::c_uchar;
pub type __uint16_t = ::std::os::raw::c_ushort;
pub type __int32_t = ::std::os::raw::c_int;
pub type __uint32_t = ::std::os::raw::c_uint;
pub type __int64_t = ::std::os::raw::c_long;
pub type __uint64_t = ::std::os::raw::c_ulong;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Allocator {
_unused: [u8; 0],
}
pub type Allocator_t = Allocator;
pub type Dict = *mut Dict_Entry;
pub type List = *mut List_Item;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct String_s {
@@ -30,6 +27,74 @@ impl Default for String_s {
}
pub type String = String_s;
pub type String_t = String;
pub type Dict_t = Dict;
pub type List_t = List;
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Object_Type {
Object_INTEGER = 0,
Object_STRING = 1,
Object_LIST = 2,
Object_DICT = 3,
Object_UNPARSABLE = 4,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct Object {
pub type_: Object_Type,
pub as_: Object__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union Object__bindgen_ty_1 {
pub number: i64,
pub string: *mut String_t,
pub list: *mut List_t,
pub dictionary: *mut Dict_t,
_bindgen_union_align: u64,
}
impl Default for Object__bindgen_ty_1 {
fn default() -> Self {
unsafe { ::std::mem::zeroed() }
}
}
impl Default for Object {
fn default() -> Self {
unsafe { ::std::mem::zeroed() }
}
}
pub type Object_t = Object;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Allocator {
_unused: [u8; 0],
}
pub type Allocator_t = Allocator;
pub type List_Item_t = List_Item;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct List_Item {
pub next: *mut List_Item_t,
pub elem: *mut Object,
}
impl Default for List_Item {
fn default() -> Self {
unsafe { ::std::mem::zeroed() }
}
}
pub type Dict_Entry_t = Dict_Entry;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Dict_Entry {
pub next: *mut Dict_Entry_t,
pub key: *mut String,
pub val: *mut Object,
}
impl Default for Dict_Entry {
fn default() -> Self {
unsafe { ::std::mem::zeroed() }
}
}
pub type Iface_t = Iface;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
@@ -423,7 +488,9 @@ pub struct RBindings_Whitelist {
pub b: Iface_t,
pub c: CryptoAuth_addUser_Res,
pub d: Message_t,
pub e: String_t,
pub e: List_Item_t,
pub ee: Dict_Entry_t,
pub eee: Object_t,
pub f: *mut Log_t,
pub g: RBindings_Version,
pub h: Sockaddr_t,

View File

@@ -1818,9 +1818,9 @@ mod tests {
fn fake_random(alloc: &mut Allocator) -> *mut cffi::Random_t {
unsafe {
let fake_seed = cffi::DeterminentRandomSeed_new(alloc.c(), std::ptr::null_mut());
let mut out: *mut cffi::Random_t = std::ptr::null_mut();
let err = cffi::Random_newWithSeed(&out, alloc.c(), std::ptr::null_mut(), fake_seed);
// let fake_seed = cffi::DeterminentRandomSeed_new(alloc.c(), std::ptr::null_mut());
// let mut out: *mut cffi::Random_t = std::ptr::null_mut();
// let err = cffi::Random_newWithSeed(&out, alloc.c(), std::ptr::null_mut(), fake_seed);
todo!()
}
}

View File

@@ -0,0 +1,173 @@
use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{anyhow, bail, Result};
use bendy::value::Value;
use crate::{
cffi::{
Allocator_t,
Dict_Entry_t,
Dict_t,
List_Item_t,
List_t,
Message_t,
Object_Type,
Object_t,
String_t,
},
interface::wire::message,
rtypes::RTypes_Error_t,
util::serialization::jsonbenc,
};
use super::allocator;
pub fn dict_to_c<'a>(alloc: *mut Allocator_t, v: &BTreeMap<Cow<'a, [u8]>, Value<'a>>) -> *mut Dict_t {
let mut de = std::ptr::null_mut();
for (k, v) in v.iter().rev() {
de = allocator::adopt(alloc, Dict_Entry_t{
next: de,
key: string_to_c(alloc, &k[..]),
val: value_to_c(alloc, v),
});
}
allocator::adopt(alloc, de)
}
pub fn list_to_c<'a>(alloc: *mut Allocator_t, v: &Vec<Value<'a>>) -> *mut List_t {
let mut list = std::ptr::null_mut();
for v in v.iter().rev() {
list = allocator::adopt(alloc, List_Item_t{
next: list,
elem: value_to_c(alloc, v),
});
}
allocator::adopt(alloc, list)
}
pub fn string_to_c<'a>(alloc: *mut Allocator_t, v: impl Into<&'a[u8]>) -> *mut String_t {
let v: &'a[u8] = v.into();
let bytes = allocator::adopt(alloc, Vec::from(v));
let bytes = unsafe { (*bytes)[..].as_mut_ptr() };
allocator::adopt(alloc, String_t{
len: v.len(),
bytes: bytes as _,
})
}
pub fn value_to_c<'a>(alloc: *mut Allocator_t, v: &Value<'a>) -> *mut Object_t {
let mut out = Object_t::default();
match v {
Value::Bytes(s) => {
out.type_ = Object_Type::Object_STRING;
out.as_.string = string_to_c(alloc, &s[..]);
}
Value::Dict(d) => {
out.type_ = Object_Type::Object_DICT;
out.as_.dictionary = dict_to_c(alloc, d);
}
Value::List(l) => {
out.type_ = Object_Type::Object_LIST;
out.as_.list = list_to_c(alloc, l);
}
Value::Integer(i) => {
out.type_ = Object_Type::Object_INTEGER;
out.as_.number = *i;
}
}
allocator::adopt(alloc, out)
}
pub fn string_from_c(string: *mut String_t) -> Result<Cow<'static, [u8]>> {
let (len, ptr) = unsafe { ( (*string).len, (*string).bytes ) };
let mut out = vec![0_u8;len];
unsafe {
let buf = std::slice::from_raw_parts(ptr as *const u8, len);
out.copy_from_slice(buf);
}
Ok(Cow::Owned(out))
}
pub fn dict_from_c(dict: *mut Dict_t) -> Result<BTreeMap<Cow<'static, [u8]>, Value<'static>>> {
let mut out = Vec::new();
unsafe {
let mut d = *dict;
while !d.is_null() {
let k = string_from_c((*d).key)?;
let v = value_from_c((*d).val)?;
out.push((k,v));
d = (*d).next;
}
}
Ok(out.into_iter().rev().collect())
}
pub fn list_from_c(list: *mut List_t) -> Result<Vec<Value<'static>>> {
let mut out: Vec<Value<'_>> = Vec::new();
unsafe {
let mut l = *list;
while !l.is_null() {
out.push(value_from_c( (*l).elem )?);
l = (*l).next;
}
}
out.reverse();
Ok(out)
}
pub fn value_from_c(obj: *mut Object_t) -> Result<Value<'static>> {
match unsafe { (*obj).type_ } {
Object_Type::Object_STRING => Ok(Value::Bytes(string_from_c(unsafe { (*obj).as_.string })?)),
Object_Type::Object_DICT => Ok(Value::Dict(dict_from_c(unsafe { (*obj).as_.dictionary })?)),
Object_Type::Object_LIST => Ok(Value::List(list_from_c(unsafe { (*obj).as_.list })?)),
Object_Type::Object_INTEGER => Ok(Value::Integer(unsafe { (*obj).as_.number })),
t => bail!("value_from_c() invalid type: {}", t as u32),
}
}
#[no_mangle]
pub extern "C" fn Rffi_Benc_decodeJson(
out: *mut *mut Dict_t,
msg: *mut Message_t,
lax_mode: bool,
alloc: *mut Allocator_t,
) -> *mut RTypes_Error_t {
let mut msg = message::Message::from_c_message(msg);
let val = match jsonbenc::parse(&mut msg, lax_mode) {
Ok(x) => x,
Err(e) => {
return allocator::adopt(alloc, RTypes_Error_t{
e: Some(anyhow!("Error decoding json: {:?}", e)),
});
}
};
let d = match val {
Value::Dict(d) => dict_to_c(alloc, &d),
_ => {
return allocator::adopt(alloc, RTypes_Error_t{ e: Some(anyhow!("Json not a dict")) });
}
};
unsafe {
*out = d;
}
std::ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn Rffi_Benc_encodeJson(
input: *mut Dict_t,
msg: *mut Message_t,
alloc: *mut Allocator_t,
) -> *mut RTypes_Error_t {
let mut msg = message::Message::from_c_message(msg);
let input = match dict_from_c(input) {
Err(e) => {
return allocator::adopt(alloc, RTypes_Error_t{ e: Some(anyhow!("Error converting benc: {}", e)) });
}
Ok(input) => input,
};
if let Err(e) = jsonbenc::serialize(&mut msg, &Value::Dict(input)) {
return allocator::adopt(alloc, RTypes_Error_t{ e: Some(anyhow!("Error decoding json: {}", e)) });
}
std::ptr::null_mut()
}

View File

@@ -14,6 +14,7 @@ mod glock;
mod base10;
pub mod allocator;
mod seeder;
mod benc;
use anyhow::{bail, Result};

View File

@@ -5,6 +5,7 @@ pub mod sockaddr;
pub mod identity;
pub mod async_callable;
pub mod callable;
pub mod serialization;
pub mod events {
use std::time::{SystemTime, UNIX_EPOCH};

View File

@@ -0,0 +1,683 @@
use bendy::value::Value;
use std::collections::BTreeMap;
use std::io::Read;
use std::borrow::Cow;
use cjdns_bytes::message::RWrite;
use std::io::Result as IoResult;
#[derive(Debug)]
pub enum ParseError {
MalformedInput(String, usize), // Error with description and line number
}
struct JsonParser<R: Read> {
reader: R,
current_line: usize,
lax_mode: bool,
last_chr: Option<u8>,
}
fn format_chr(chr: u8) -> String {
char::from_u32(chr as _).map(|x|format!("{x}")).unwrap_or_else(||format!("CODE[{chr}]"))
}
impl<R: Read> JsonParser<R> {
fn new(reader: R, lax_mode: bool) -> Self {
JsonParser {
reader,
current_line: 1,
lax_mode,
last_chr: None,
}
}
fn parse(&mut self) -> Result<Value<'static>, ParseError> {
loop {
let byte = self.next_meaningful_char()?;
match byte {
b'{' => return self.parse_dict(),
b'[' => return self.parse_list(),
b'"' => return self.parse_string(),
b'0'..=b'9' | b'-' => return self.parse_int(byte),
x => {
return Err(ParseError::MalformedInput(
format!("Unexpected char {}", format_chr(x)),
self.current_line,
));
}
}
}
}
fn read_byte(&mut self) -> Result<Option<u8>, ParseError> {
if let Some(lc) = self.last_chr.take() {
return Ok(Some(lc));
}
let mut byte = [0; 1];
match self.reader.read(&mut byte) {
Ok(0) => Ok(None), // End of stream
Ok(_) => Ok(Some(byte[0])),
Err(_) => Err(ParseError::MalformedInput("Error reading input".into(), self.current_line)),
}
}
fn next_meaningful_char(&mut self) -> Result<u8, ParseError> {
while let Some(byte) = self.read_byte()? {
match byte {
b'/' => self.handle_comment()?,
b' ' | b'\t' => {}
b'\n' => { self.current_line += 1 }
x => {
return Ok(x);
}
}
}
Err(ParseError::MalformedInput("Ran out of content while parsing".into(), self.current_line))
}
fn parse_dict(&mut self) -> Result<Value<'static>, ParseError> {
let mut dict = BTreeMap::new();
let mut need_comma = false;
loop {
let byte = self.next_meaningful_char()?;
if need_comma && byte != b'}' && byte != b',' && !self.lax_mode {
return Err(ParseError::MalformedInput(
format!("Dict: Expecting ',' but got '{}'", format_chr(byte)),
self.current_line)
);
}
match byte {
b'}' => {
if !dict.is_empty() && !need_comma && !self.lax_mode {
return Err(ParseError::MalformedInput(
format!("Dict: Unexpected trailing ','"),
self.current_line)
);
}
return Ok(Value::Dict(dict));
}
b'"' => {
let key = if let Value::Bytes(x) = self.parse_string()? {
x
} else {
unreachable!();
};
let mut need_colon = true;
let val = loop {
let byte = self.next_meaningful_char()?;
if need_colon && byte != b':' && !self.lax_mode {
return Err(ParseError::MalformedInput(
format!("Dict: Expecting ':' but got '{}'", format_chr(byte)),
self.current_line)
);
}
match byte {
b':' => {
if !need_colon && !self.lax_mode {
return Err(ParseError::MalformedInput(
"Dict: Expected value but got duplicate ':'".into(), self.current_line));
}
need_colon = false;
}
b'{' | b'[' | b'"' | b'0'..=b'9' | b'-' => {
self.last_chr = Some(byte);
break self.parse()?;
}
x => {
return Err(ParseError::MalformedInput(
format!("Dict: Unexpected character '{}', exppecting value", format_chr(x)),
self.current_line)
);
}
}
};
dict.insert(key, val);
need_comma = true;
}
b',' => {
if !need_comma && !self.lax_mode {
return Err(ParseError::MalformedInput("Dict: Unexpected comma".into(), self.current_line));
}
need_comma = false;
}
x => {
return Err(ParseError::MalformedInput(
format!("Dict: Unexpected character '{}'", format_chr(x)),
self.current_line)
);
}
}
}
}
fn parse_list(&mut self) -> Result<Value<'static>, ParseError> {
let mut list = vec![];
let mut need_comma = false;
loop {
let byte = self.next_meaningful_char()?;
if need_comma && byte != b']' && byte != b',' && !self.lax_mode {
return Err(ParseError::MalformedInput(
format!("List: Expecting ',' but got '{}'", format_chr(byte)),
self.current_line)
);
}
match byte {
b']' => {
if !list.is_empty() && !need_comma && !self.lax_mode {
return Err(ParseError::MalformedInput(
format!("List: Unexpected trailing ','"),
self.current_line)
);
}
return Ok(Value::List(list));
}
b',' => {
if !need_comma && !self.lax_mode {
if !need_comma && !self.lax_mode {
return Err(ParseError::MalformedInput("List: Unexpected comma".into(), self.current_line));
}
}
need_comma = false;
}
b'{' | b'[' | b'"' | b'0'..=b'9' | b'-' => {
self.last_chr = Some(byte);
list.push(self.parse()?);
need_comma = true;
}
x => {
return Err(ParseError::MalformedInput(
format!("List: Unexpected character '{}'", format_chr(x)),
self.current_line)
);
}
}
}
}
fn parse_string(&mut self) -> Result<Value<'static>, ParseError> {
let mut result = vec![];
while let Some(byte) = self.read_byte()? {
match byte {
b'"' => break,
b'\\' => {
if let Some(escaped_byte) = self.read_byte()? {
match escaped_byte {
b'x' => {
let hex = self.read_two_hex_digits()?;
result.push(hex);
}
_ => {
return Err(ParseError::MalformedInput(
format!("String: Invalid escape sequence: '\\{}'", format_chr(byte)),
self.current_line,
));
}
}
}
}
_ => result.push(byte),
}
}
Ok(Value::Bytes(Cow::Owned(result)))
}
fn parse_int(&mut self, first_byte: u8) -> Result<Value<'static>, ParseError> {
let mut num_str = String::new();
num_str.push(first_byte as char);
while let Some(byte) = self.read_byte()? {
match byte {
b'0'..=b'9' | b'-' => num_str.push(byte as char),
x => {
self.last_chr = Some(x);
break;
},
}
}
match num_str.parse::<i64>() {
Ok(num) => Ok(Value::Integer(num)),
Err(_) => Err(ParseError::MalformedInput("Invalid integer".into(), self.current_line)),
}
}
fn handle_comment(&mut self) -> Result<(), ParseError> {
let next_byte = self.read_byte()?;
match next_byte {
Some(b'*') => self.consume_multiline_comment(),
Some(b'/') => self.consume_singleline_comment(),
_ => Ok(()), // Ignore invalid comment
}
}
fn consume_singleline_comment(&mut self) -> Result<(), ParseError> {
while let Some(byte) = self.read_byte()? {
if byte == b'\n' {
self.current_line += 1;
break;
}
}
Ok(())
}
fn consume_multiline_comment(&mut self) -> Result<(), ParseError> {
while let Some(byte) = self.read_byte()? {
if byte == b'*' {
if let Some(b'/') = self.read_byte()? {
break;
}
}
}
Ok(())
}
fn read_two_hex_digits(&mut self) -> Result<u8, ParseError> {
let mut hex = [0u8; 2];
for i in 0..2 {
if let Some(byte) = self.read_byte()? {
hex[i] = byte;
} else {
return Err(ParseError::MalformedInput("Incomplete hex escape".into(), self.current_line));
}
}
let hex_str = std::str::from_utf8(&hex).map_err(|_| ParseError::MalformedInput("Invalid hex escape".into(), self.current_line))?;
u8::from_str_radix(hex_str, 16).map_err(|_| ParseError::MalformedInput("Invalid hex digits".into(), self.current_line))
}
}
fn serialize_string<W: RWrite>(writer: &mut W, s: &[u8]) -> std::io::Result<()> {
writer.write_all(b"\"")?;
for &byte in s.iter().rev() {
if byte < 126 && byte > 31 && byte != b'\\' && byte != b'"' {
writer.write_all(&[byte])?;
} else {
let escaped = format!(r"\x{:02X}", byte);
writer.write_all(escaped.as_bytes())?;
}
}
writer.write_all(b"\"")?;
Ok(())
}
fn serialize_reverse<'a, W: RWrite>(writer: &mut W, obj: &Value<'a>, indent_level: usize) -> std::io::Result<()> {
let indent = " ".repeat(indent_level);
let indent2 = " ".repeat(indent_level + 1);
match obj {
Value::Bytes(s) => serialize_string(writer, s)?,
Value::Integer(i) => {
let int_str = i.to_string();
writer.write_all(&int_str.as_bytes())?;
}
Value::List(list) => {
writer.write_all(b"]")?;
if !list.is_empty() {
writer.write_all(indent.as_bytes())?;
writer.write_all(b"\n")?;
for (i, item) in list.iter().rev().enumerate() {
if i > 0 {
writer.write_all(b",")?;
}
serialize_reverse(writer, item, indent_level + 1)?;
writer.write_all(indent2.as_bytes())?;
writer.write_all(b"\n")?;
}
}
writer.write_all(b"[")?;
}
Value::Dict(dict) => {
writer.write_all(b"}")?;
if !dict.is_empty() {
writer.write_all(indent.as_bytes())?;
writer.write_all(b"\n")?;
for (i, (key, value)) in dict.iter().rev().enumerate() {
if i > 0 {
writer.write_all(b",\n")?;
}
serialize_reverse(writer, value, indent_level + 1)?;
writer.write_all(b": ")?;
serialize_string(writer, key)?;
writer.write_all(indent2.as_bytes())?;
}
writer.write_all(b"{\n")?;
} else {
writer.write_all(b"{")?;
}
}
}
Ok(())
}
pub fn parse<R: Read>(reader: R, lax_mode: bool) -> Result<Value<'static>, ParseError> {
JsonParser::new(reader, lax_mode).parse()
}
pub fn serialize<'a, W: RWrite>(writer: &mut W, obj: &Value<'a>) -> IoResult<()> {
serialize_reverse(writer, obj, 0)
}
#[cfg(test)]
mod test {
use anyhow::bail;
use bendy::value::Value;
use crate::interface::wire::message::Message;
use super::{parse, serialize};
#[test]
fn test() {
let conf = r#"
{
// Private key:
// Your confidentiality and data integrity depend on this key, keep it secret!
"privateKey": "0b4eed1835d708a79914a8925e8d5fff706a5258dfb4826f56d4c29eaec7abc5",
// This key corresponds to the public key and ipv6 address:
"publicKey": "f09rvtmr2s25942ygutztv8bf70ny8zd0kpffk8lmbzm40qty9k0.k",
"ipv6": "fcea:4775:eacd:7c3b:6fbc:c02b:43d6:53e1",
// Anyone connecting and offering these passwords on connection will be allowed.
//
// WARNING: If a "login" parameter is passed, someone sniffing on the wire can
// sniff the packet and crack to find it. If the "login" is not passed
// then the hash of the 'password' is effectively the login, therefore
// that can be cracked.
//
"authorizedPasswords": [
// Password is a unique string which is known to the client and server.
// User is an optional login name and will also be used to display the peer.
{ "password": "dq1nlc6g46k9v5pd6hkrclpsz7807nl", "user": "default-login" }
// More passwords should look like this.
// { "password": "86251h6k89u20rx9k0f4tlhwc6qkj5j", "user": "my-second-peer" },
// { "password": "xzmnu69xqhff297pz1v3rnbhl034z1f", "user": "my-third-peer" },
// { "password": "ksr4nftwznrsqw5z3t69dwn6v3rw6z4", "user": "my-fourth-peer" },
// Below is an example of your connection credentials
// that you can give to other people so they can connect
// to you using your default password (from above).
// The login field here yourself to your peer and the peerName field
// is the name the peer which will be displayed in peerStats
// Adding a unique password for each peer is advisable
// so that leaks can be isolated.
/*
"your.external.ip.goes.here:65288": {
"login": "default-login",
"password": "dq1nlc6g46k9v5pd6hkrclpsz7807nl",
"publicKey": "f09rvtmr2s25942ygutztv8bf70ny8zd0kpffk8lmbzm40qty9k0.k",
"peerName": "your-name-goes-here"
},
*/
],
// Settings for administering and extracting information from your router.
// This interface provides functions which can be called through a UDP socket.
// See admin/Readme.md for more information about the API and try:
// ./tools/cexec
// For a list of functions which can be called.
// For example: ./tools/cexec 'memory()'
// will call a function which gets the core's current memory consumption.
// ./tools/cjdnslog
// is a tool which uses this admin interface to get logs from cjdns.
"admin": {
// Port to bind the admin RPC server to.
"bind": "127.0.0.1:11234",
// Password for admin RPC server.
// This is a static password by default, so that tools like
// ./tools/cexec can use the API without you creating a
// config file at ~/.cjdnsadmin first. If you decide to
// expose the admin API to the network, change the password!
"password": "NONE"
},
// Interfaces to connect to the switch core.
"interfaces": {
// The interface which connects over UDP/IP based VPN tunnel.
"UDPInterface": [
{
// Bind to this port.
"bind": "0.0.0.0:65288",
// Set the DSCP value for Qos. Default is 0.
// "dscp": 46,
// Automatically connect to other nodes on the same LAN
// This works by binding a second port and sending beacons
// containing the main data port.
// beacon is a number between 0 and 2:
// 0 -> do not beacon nor connect to other nodes who beacon
// 1 -> quiet mode, accept beacons from other nodes only
// 2 -> send and accept beacons
// beaconDevices is a list which can contain names of devices such
// as eth0, as well as broadcast addresses to send to, such as
// 192.168.101.255, or the pseudo-name "all".
// in order to auto-peer, all cjdns nodes must use the same
// beaconPort.
"beacon": 2,
"beaconDevices": [ "all" ],
"beaconPort": 64512,
// Nodes to connect to (IPv4 only).
"connectTo": {
// Add connection credentials here to join the network
// If you have several, don't forget the separating commas
// They should look like:
// "ipv4 address:port": {
// "login": "(optional) name your peer has for you"
// "password": "password to connect with",
// "publicKey": "remote node key.k",
// "peerName": "(optional) human-readable name for peer"
// },
// Ask somebody who is already connected.
}
},
{
// Bind to this port.
"bind": "[::]:65288",
// Set the DSCP value for Qos. Default is 0.
// "dscp": 46,
// Nodes to connect to (IPv6 only).
"connectTo": {
// Add connection credentials here to join the network
// Ask somebody who is already connected.
}
}
],
// The interface which allows peering using layer-2 ethernet frames
"_disabled_ETHInterface": [
// Alternatively bind to just one device and either beacon and/or
// connect to a specified MAC address
{
// Bind to this device (interface name, not MAC)
// "all" is a pseudo-name which will try to connect to all devices.
"bind": "all",
// Auto-connect to other cjdns nodes on the same network.
// Options:
//
// 0 -- Disabled.
//
// 1 -- Accept beacons, this will cause cjdns to accept incoming
// beacon messages and try connecting to the sender.
//
// 2 -- Accept and send beacons, this will cause cjdns to broadcast
// messages on the local network which contain a randomly
// generated per-session password, other nodes which have this
// set to 1 or 2 will hear the beacon messages and connect
// automatically.
//
"beacon": 2,
// Node(s) to connect to manually
// Note: does not work with "all" pseudo-device-name
"connectTo": {
// Credentials for connecting look similar to UDP credentials
// except they begin with the mac address, for example:
// "01:02:03:04:05:06":{"password":"a","publicKey":"b"}
}
}
]
},
// Configuration for the router.
"router": {
// DNS Seeds, these will be used to add peers automatically.
// The first seed in the list is trusted to provide the snode.
"dnsSeeds": [
"seed.cjdns.fr"
],
// supernodes, if none are specified they'll be taken from your peers
"supernodes": [
//"6743gf5tw80ExampleExampleExampleExamplevlyb23zfnuzv0.k",
],
// The interface which is used for connecting to the cjdns network.
"interface": {
// The type of interface (only TUNInterface is supported for now)
"type": "TUNInterface"
// The name of a persistent TUN device to use.
// This for starting cjdroute as its own user.
// *MOST USERS DON'T NEED THIS*
//"tunDevice": "tun0"
},
// As an alternative to the TUN interface, you can create a socket interface
// which will create a UNIX socket which emits packets that would otherwise
// be sent through the TUN device.
// To enable this interface, change the name of the above TUN interface to
// "_disabled_interface" and change the name of this interface to
// simply "interface"
"_disabled_interface": {
"type": "SocketInterface",
// The filesystem path to the socket to create or connect to.
"socketFullPath": "/var/run/cjdns.sock"
},
// System for tunneling IPv4 and ICANN IPv6 through cjdns.
// This is using the cjdns switch layer as a VPN carrier.
"ipTunnel": {
// Nodes allowed to connect to us.
// When a node with the given public key connects, give them the
// ip4 and/or ip6 addresses listed.
"allowedConnections": [
// Give the client an address on 192.168.1.0/24, and an address
// it thinks has all of IPv6 behind it.
// ip4Prefix is the set of addresses which are routable from the tun
// for example, if you're advertizing a VPN into a company network
// which exists in 10.123.45.0/24 space, ip4Prefix should be 24
// default is 32 for ipv4 and 128 for ipv6
// so by default it will not install a route
// ip4Alloc is the block of addresses which are allocated to the
// for example if you want to issue 4 addresses to the client, those
// being 192.168.123.0 to 192.168.123.3, you would set this to 30
// default is 32 for ipv4 and 128 for ipv6 (1 address)
// {
// "publicKey": "f64hfl7c4uxt6krmhPutTheRealAddressOfANodeHere7kfm5m0.k",
// "ip4Address": "192.168.1.24",
// "ip4Prefix": 0,
// "ip4Alloc": 32,
// "ip6Address": "2001:123:ab::10",
// "ip6Prefix": 0
// "ip6Alloc": 64,
// },
// It's ok to only specify one address and prefix/alloc are optional.
// {
// "publicKey": "ydq8csdk8p8ThisIsJustAnExampleAddresstxuyqdf27hvn2z0.k",
// "ip4Address": "192.168.1.25",
// "ip4Prefix": 0,
// }
],
"outgoingConnections": [
// Connect to one or more machines and ask them for IP addresses.
// "6743gf5tw80ExampleExampleExampleExamplevlyb23zfnuzv0.k",
// "pw9tfmr8pcrExampleExampleExampleExample8rhg1pgwpwf80.k",
// "g91lxyxhq0kExampleExampleExampleExample6t0mknuhw75l0.k"
]
}
},
// Dropping permissions.
// In the event of a serious security exploit in cjdns, leak of confidential
// network traffic and/or keys is highly likely but the following rules are
// designed to prevent the attack from spreading to the system on which cjdns
// is running.
// Counter-intuitively, cjdns is *more* secure if it is started as root because
// non-root users do not have permission to use chroot or change usernames,
// limiting the effectiveness of the mitigations herein.
"security": [
// Change the user id to sandbox the cjdns process after it starts.
// If keepNetAdmin is set to 0, IPTunnel will be unable to set IP addresses
// and ETHInterface will be unable to hot-add new interfaces
// Use { "setuser": 0 } to disable.
// Default: enabled with keepNetAdmin
{ "setuser": "nobody", "keepNetAdmin": 1 },
// Chroot changes the filesystem root directory which cjdns sees, blocking it
// from accessing files outside of the chroot sandbox, if the user does not
// have permission to use chroot(), this will fail quietly.
// Use { "chroot": 0 } to disable.
// Default: enabled (using "/var/run")
{ "chroot": "/var/run/" },
// Nofiles is a deprecated security feature which prevents cjdns from opening
// any files at all, using this will block setting of IP addresses and
// hot-adding ETHInterface devices but for users who do not need this, it
// provides a formidable sandbox.
// Default: disabled
{ "nofiles": 0 },
// Noforks will prevent cjdns from spawning any new processes or threads,
// this prevents many types of exploits from attacking the wider system.
// Default: enabled
{ "noforks": 1 },
// The client sets up the core using a sequence of RPC calls, the responses
// to these calls are verified but in the event that the client crashes
// setup of the core completes, it could leave the core in an insecure state
// This call constitutes the client telling the core that the security rules
// have been fully applied and the core may run. Without it, the core will
// exit within a few seconds with return code 232.
// Default: enabled
{ "setupComplete": 1 }
],
// Logging
"logging": {
// Uncomment to have cjdns log to stdout rather than making logs available
// via the admin socket.
// "logTo": "stdout"
},
// If set to non-zero, cjdns will not fork to the background.
// Recommended for use in conjunction with "logTo":"stdout".
"noBackground": 0,
// Path for admin control pipe:
// If you pass only a filename then cjdns will guess the full path
// On unix the default path is /tmp/
// On windows: \\.\pipe\
"pipe": "cjdroute.sock",
// This is to make the configuration be parsed in strict mode, which allows
// it to be edited externally using cjdnsconf.
"version": 2
}
"#;
let mut msg = Message::new(20000);
msg.push_bytes(conf.as_bytes()).unwrap();
let res = parse(msg, false).unwrap();
let _ = match res {
bendy::value::Value::Dict(d) => d,
_ => panic!("Wrong type"),
};
}
}

View File

@@ -0,0 +1 @@
pub mod jsonbenc;

View File

@@ -15,7 +15,7 @@
#include "util/Base10.h"
#include "wire/Message.h"
#include "exception/Er.h"
#include "exception/Err.h"
#include "util/CString.h"
#include "rust/cjdns_sys/Rffi.h"
@@ -40,17 +40,18 @@ Err_DEFUN Base10_write(Message_t* msg, int64_t num)
return NULL;
}
Er_DEFUN(int64_t Base10_read(Message_t* msg))
Err_DEFUN Base10_read(int64_t* outP, Message_t* msg)
{
int64_t numOut = 0;
uint32_t bytes = 0;
int out = Rffi_parseBase10(
Message_bytes(msg), Message_getLength(msg), &numOut, &bytes);
if (out != 0) {
Er_raise(Message_getAlloc(msg), "Error reading base10: %d", out);
Err_raise(Message_getAlloc(msg), "Error reading base10: %d", out);
}
Er(Er_fromErr(Message_eshift(msg, -bytes)));
Er_ret(numOut);
Err(Message_eshift(msg, -bytes));
*outP = numOut;
return NULL;
}
int Base10_fromString(uint8_t* str, int64_t* numOut)

View File

@@ -15,7 +15,7 @@
#ifndef Base10_H
#define Base10_H
#include "exception/Er.h"
#include "exception/Err.h"
#include "wire/Message.h"
#include "util/Linker.h"
Linker_require("util/Base10.c")
@@ -23,7 +23,7 @@ Linker_require("util/Base10.c")
#include <stdint.h>
Err_DEFUN Base10_write(Message_t* msg, int64_t num);
Er_DEFUN(int64_t Base10_read(Message_t* msg));
Err_DEFUN Base10_read(int64_t* outP, Message_t* msg);
int Base10_fromString(uint8_t* str, int64_t* numOut);
#endif

View File

@@ -109,7 +109,7 @@ static Err_DEFUN setupRoute(const char* deviceName,
void WINAPI InitializeIpForwardEntry(PMIB_IPFORWARD_ROW2 Row);
MIB_IPFORWARD_ROW2 row = {
.InterfaceLuid = Er(getLuid(deviceName, alloc)),
.InterfaceLuid = 0,
.ValidLifetime = WSA_INFINITE,
.PreferredLifetime = WSA_INFINITE,
.Metric = 0xffffffff,
@@ -129,6 +129,7 @@ static Err_DEFUN setupRoute(const char* deviceName,
.Age = 0,
.Origin = 0
};
Err(getLuid(&row.InterfaceLuid deviceName, alloc));
if (addrFam == AF_INET6) {
Bits_memcpy(&row.DestinationPrefix.Prefix.Ipv6.sin6_addr, addrBytes, 15);
@@ -176,7 +177,7 @@ Err_DEFUN NetPlatform_addAddress(const char* interfaceName,
.OnLinkPrefixLength = 0xFF
};
ipRow.InterfaceLuid = Er(getLuid(interfaceName, tempAlloc));
Err(getLuid(&ipRow.InterfaceLuid, interfaceName, tempAlloc));
ipRow.Address.si_family = addrFam;
if (addrFam == AF_INET6) {

View File

@@ -45,7 +45,8 @@ int main()
Assert_true(Message_getLength(msg) == (int)CString_strlen(buff));
Assert_true(!Bits_memcmp(Message_bytes(msg), buff, Message_getLength(msg)));
int64_t read = Er_assert(Base10_read(msg));
int64_t read = -1;
Err_assert(Base10_read(&read, msg));
Assert_true(read == num);
Assert_true(Message_getLength(msg) == 0);
}