adds minimal websocket implementation

Mon, 26 Dec 2016 14:00:10 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 26 Dec 2016 14:00:10 +0100
changeset 127
84e206063b64
parent 126
631aaa01b2b5
child 128
288fd9b9a739

adds minimal websocket implementation

src/server/daemon/httprequest.c file | annotate | diff | comparison | revisions
src/server/daemon/objs.mk file | annotate | diff | comparison | revisions
src/server/daemon/websocket.c file | annotate | diff | comparison | revisions
src/server/daemon/websocket.h file | annotate | diff | comparison | revisions
src/server/daemon/ws-fn.c file | annotate | diff | comparison | revisions
src/server/public/nsapi.h file | annotate | diff | comparison | revisions
src/server/safs/service.c file | annotate | diff | comparison | revisions
src/server/safs/service.h file | annotate | diff | comparison | revisions
src/server/util/util.c file | annotate | diff | comparison | revisions
src/server/util/util.h file | annotate | diff | comparison | revisions
--- a/src/server/daemon/httprequest.c	Sun Oct 30 11:44:04 2016 +0100
+++ b/src/server/daemon/httprequest.c	Mon Dec 26 14:00:10 2016 +0100
@@ -396,7 +396,6 @@
 
 /*
  * NSAPI Processing
- * TODO: add this to new file
  */
 
 int nsapi_handle_request(NSAPISession *sn, NSAPIRequest *rq) {
--- a/src/server/daemon/objs.mk	Sun Oct 30 11:44:04 2016 +0100
+++ b/src/server/daemon/objs.mk	Mon Dec 26 14:00:10 2016 +0100
@@ -38,6 +38,7 @@
 DAEMONOBJ += main.o
 DAEMONOBJ += protocol.o
 DAEMONOBJ += http.o
+DAEMONOBJ += websocket.o
 DAEMONOBJ += request.o
 DAEMONOBJ += session.o
 DAEMONOBJ += sessionhandler.o
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/daemon/websocket.c	Mon Dec 26 14:00:10 2016 +0100
@@ -0,0 +1,336 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "websocket.h"
+
+#include "../util/io.h"
+#include "../util/pblock.h"
+#include "../util/util.h"
+#include "../util/strbuf.h"
+#include <ucx/string.h>
+
+#define WS_BUFFER_LEN 2048
+
+NSAPI_PUBLIC int http_handle_websocket(Session *sn, Request *rq, WebSocket *websocket) {
+    char *connection = pblock_findkeyval(pb_key_connection, rq->headers);
+    char *upgrade = pblock_findval("upgrade", rq->headers);
+    char *origin = pblock_findval("origin", rq->headers);
+    char *wskey = pblock_findval("sec-websocket-key", rq->headers);
+    char *wsprot = pblock_findval("sec-websocket-protocol", rq->headers);
+    char *wsv = pblock_findval("sec-websocket-version", rq->headers);
+    
+    if(!connection || !upgrade) {
+        return REQ_NOACTION;
+    }
+    
+    if(sstrcasecmp(sstr(connection), S("upgrade"))) {
+        return REQ_NOACTION;
+    }
+    if(sstrcasecmp(sstr(upgrade), S("websocket"))) {
+        return REQ_NOACTION;
+    }
+    
+    sstr_t wsaccept = sstrcat(2, sstr(wskey), S("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
+    unsigned char hash[20];
+    SHA1((const unsigned char*)wsaccept.ptr, wsaccept.length, hash);
+    char *websocket_accept = util_base64encode((char*)hash, 20);
+    
+    sbuf_t *response = sbuf_new(512);
+    sbuf_append(response, S("HTTP/1.1 101 Switching Protocols\r\n"));
+    sbuf_append(response, S("Upgrade: websocket\r\n"));
+    sbuf_append(response, S("Connection: Upgrade\r\n"));
+    sbuf_append(response, S("Sec-WebSocket-Accept: "));
+    sbuf_puts(response, websocket_accept);
+    sbuf_append(response, S("\r\n\r\n"));
+    
+    net_write(sn->csd, response->ptr, response->length);
+    sbuf_free(response);
+    free(websocket_accept);
+    free(wsaccept.ptr);
+    
+    // start websocket I/O
+    WSParser *parser = websocket_parser(sn);
+    
+    WSFrame frame;
+    
+    int ret = REQ_PROCEED;
+    char *inbuf = pool_malloc(sn->pool, WS_BUFFER_LEN);
+    ssize_t r = 0;
+    while((r = net_read(sn->csd, inbuf, WS_BUFFER_LEN)) > 0) {
+        websocket_input(parser, inbuf, r);
+        WSMessage *msg;
+        int error;
+        while((msg = websocket_get_message(parser, &error)) != NULL) {
+            websocket->on_message(websocket, msg);
+        }
+        if(error) {
+            log_ereport(LOG_FAILURE, "websocket protocol error");
+            break;
+        }
+    }
+    
+    return ret;
+}
+
+
+WSParser* websocket_parser(Session *sn) {
+    WSParser *parser = pool_malloc(sn->pool, sizeof(WSParser));
+    if(!parser) {
+        return NULL;
+    }
+    ZERO(parser, sizeof(WSParser));
+    parser->pool = sn->pool;
+    return parser;
+}
+
+void websocket_input(WSParser *parser, const char *data, size_t length) {
+    parser->inbuf = data;
+    parser->length = length;
+    parser->pos = 0;
+}
+
+WSMessage* websocket_get_message(WSParser *parser, int *error) {
+    WSFrame rframe;
+    WSMessage *retmsg = NULL;
+    
+    while(parser->pos < parser->length) {
+        const char *inbuf = parser->inbuf + parser->pos;
+        size_t length = parser->length - parser->pos;
+        
+        if(parser->state == 0) {
+            WSFrame frame;
+            ZERO(&frame, sizeof(WSFrame));
+            
+            /*
+             * small buffer for a websocket frame without payload data
+             * I know using so many buffers it not zero copy but
+             * it makes things a little bit easier :)
+             */
+            char frame_data[WS_FRAMEHEADER_BUFLEN];
+            size_t flen = 0;
+            
+            /*
+             * when the last call of websocket_get_message didn't completed
+             * a frame header, the tmpbuf contains the remaining bytes
+             * in this case we combine tmpbuf and inputbuf
+             */
+            if(parser->tmplen > 0) {
+                memcpy(parser->tmpbuf, frame_data, parser->tmplen);
+                flen = parser->tmplen;
+            }
+            size_t cp_remaining = length < WS_FRAMEHEADER_BUFLEN-flen ?
+                length : WS_FRAMEHEADER_BUFLEN-flen;
+            memcpy(&frame_data[flen], inbuf, cp_remaining);
+            flen += cp_remaining;
+            
+            // ready to parse the frame
+            ssize_t frame_hlen = websocket_get_frameheader(
+                    &frame,
+                    frame_data,
+                    flen);
+            
+            if(frame_hlen == -1) {
+                // protocol error, abort
+                *error = 1;
+                return NULL;
+            }
+            if(frame_hlen == 0) {
+                memcpy(parser->tmpbuf, frame_data, flen);
+            } else {
+                inbuf += frame_hlen;
+                length -= frame_hlen;
+                parser->pos += frame_hlen;
+                
+                // frame complete, create a message object
+                if(frame.payload_length > 0) {
+                    WSMessage *msg = pool_malloc(parser->pool, sizeof(WSMessage));
+                    msg->data = pool_malloc(parser->pool, frame.payload_length);
+                    msg->length = frame.payload_length;
+                    msg->next = NULL;
+                    msg->type = frame.opcode;
+                    
+                    if(frame.payload_length >= length) {
+                        // message complete
+                        memcpy(msg->data, inbuf, frame.payload_length);
+                        parser->pos += frame.payload_length;
+                        
+                        rframe = frame;
+                        retmsg = msg;
+                        break;
+                    } else {
+                        memcpy(msg->data, inbuf, length);
+                        parser->state = 1;
+                        parser->current = msg;
+                        parser->cur_plen = length;
+                        parser->frame = frame;
+                        return NULL;
+                    }
+                }
+            }
+        } else {
+            WSMessage *msg = parser->current;
+            if(msg->length >= parser->cur_plen + length) {
+                // still incomplete message
+                memcpy(msg->data + parser->cur_plen, inbuf, length);
+                parser->cur_plen += length;
+                return NULL;
+            } else {
+                size_t cplen = msg->length - parser->cur_plen;
+                memcpy(msg->data + parser->cur_plen, inbuf, cplen);
+                parser->pos += cplen;
+                parser->state = 0;
+                parser->current = NULL;
+                
+                rframe = parser->frame;
+                retmsg = msg;
+                break;
+            }
+        }
+    }
+    
+    if(retmsg && rframe.mask) {
+        websocket_mask_data(retmsg->data, retmsg->length, rframe.masking_key);
+    }
+    return retmsg;
+}
+
+
+ssize_t websocket_get_frameheader(WSFrame *frame, const char *buf, size_t len) {
+    if(len < 2) {
+        return 0; // too small for anything
+    }
+    
+/*
+    printf("websocket_get_frameheader: ");
+    for(int i=0;i<len;i++) {
+        printf("%x ", buf[i]);
+        if(len > 15) {
+            break;
+        }
+    }
+    printf("\n");
+*/
+    
+    size_t msglen = 2; // minimal length
+    
+    uint8_t fin = (buf[0] & 0x80) != 0;
+    uint8_t opcode = buf[0] & 0xf;
+    
+    uint8_t mask = (buf[1] & 0x80) != 0;
+    uint8_t payload_len = buf[1] & 0x7f;
+    
+    uint64_t payload_length = payload_len;
+    if(payload_len == 126) {
+        msglen += 2;
+        if(len < msglen) {
+            return 0;
+        }
+        payload_length = *((uint16_t*)(buf+2));
+    } else if(payload_len == 127) {
+        msglen += 8;
+        if(len < msglen) {
+            return 0;
+        }
+        payload_length = *((uint64_t*)(buf+2));
+    } else if(payload_len > 127) {
+        return -1;
+    }
+    
+    uint32_t masking_key = 0;
+    if(mask) {
+        msglen += 4;
+        if(len < msglen) {
+            return 0;
+        }
+        masking_key = *((uint32_t*)(buf+msglen-4));
+    }
+    
+    frame->header_complete = TRUE;
+    frame->fin = fin;
+    frame->opcode = opcode;
+    frame->mask = mask;
+    frame->masking_key = masking_key;
+    frame->payload_length = payload_length;
+    
+    return msglen;
+}
+
+void websocket_mask_data(char *buf, size_t len, uint32_t mask) {
+    size_t m = len % 4;
+    size_t alen = (len - m) / 4;
+    
+    uint32_t *data = (uint32_t*)buf;
+    for(int i=0;i<alen;i++) {
+        data[i] = data[i] ^ mask;
+    }
+    
+    int j = 0;
+    char *cmask = (char*)&mask;
+    for(int i=len-m;i<len;i++) {
+        buf[i] = buf[i] ^ cmask[j];
+        j++;
+    }
+}
+
+/* ------------------------------ public API ------------------------------*/
+
+NSAPI_PUBLIC int websocket_send_text(SYS_NETFD csd, char *msg, size_t len) {
+    char frame[WS_FRAMEHEADER_BUFLEN];
+    frame[0] = 0b10000001;
+    size_t hlen;
+    if(len < 126) {
+        frame[1] = (char)len;
+        hlen = 2;
+    } else if(len < 65536) {
+        frame[1] = 126;
+        uint16_t plen = htons(len);
+        memcpy(frame + 2, &plen, 2);
+        hlen = 4;
+    } else {
+        frame[1] = 127;
+        // TODO
+        hlen = 10;
+    }
+    
+    struct iovec iov[2];
+    iov[0].iov_base = frame;
+    iov[0].iov_len = hlen;
+    
+    iov[1].iov_base = msg;
+    iov[1].iov_len = len;
+    
+    ssize_t w = net_writev(csd, iov, 2);
+    if(w > 0) {
+        return 0;
+    } else {
+        return 1;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/daemon/websocket.h	Mon Dec 26 14:00:10 2016 +0100
@@ -0,0 +1,115 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WEBSOCKET_H
+#define	WEBSOCKET_H
+
+#include "../public/nsapi.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+    
+#define WS_FRAMEHEADER_BUFLEN 16
+
+typedef struct WSParser  WSParser;
+typedef struct WSFrame   WSFrame;
+
+// WSMessage is defined in nsapi.h
+
+struct WSFrame {
+    uint8_t  header_complete;
+    uint8_t  fin;
+    uint8_t  mask;
+    uint8_t  opcode;
+    uint32_t masking_key;
+    uint64_t payload_length;
+};
+
+struct WSParser {
+    // input buffer size
+    const char *inbuf; 
+    // inbuf data length
+    size_t     length;
+    // current buffer position
+    size_t     pos;
+    
+    // current message or NULL
+    WSMessage  *current;
+    // parser state: 0: parse frame, 1: get payload data
+    int        state;
+    // current payload length
+    size_t     cur_plen;
+    // current message frame
+    WSFrame    frame;
+    
+    // tmp buffer for incomplete frame header
+    char       tmpbuf[WS_FRAMEHEADER_BUFLEN];
+    // length of tmpbuf
+    size_t     tmplen;
+    
+    pool_handle_t *pool;
+};
+
+/*
+ * creates a new websocket parser
+ */
+WSParser* websocket_parser(Session *sn);
+
+/*
+ * puts input data into the websocket parser
+ * you can use websocket_get_message() after that
+ */
+void websocket_input(WSParser *parser, const char *data, size_t length);
+
+/*
+ * tries to read a complete websocket message
+ * returns a pointer to an allocated WSMessage or NULL
+ * sets error to 1 if an error occurs, otherwise to 0
+ */
+WSMessage* websocket_get_message(WSParser *parser, int *error);
+
+/*
+ * reads a websocket frame util begin of the payload data
+ * returns position of payload data or 0 if the frame header is incomplete
+ */
+ssize_t websocket_get_frameheader(WSFrame *frame, const char *buf, size_t len);
+
+/*
+ * masks or unmasks data
+ * this is done by a simple xor with each element in buf and mask
+ */
+void websocket_mask_data(char *buf, size_t len, uint32_t mask);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* WEBSOCKET_H */
+
--- a/src/server/daemon/ws-fn.c	Sun Oct 30 11:44:04 2016 +0100
+++ b/src/server/daemon/ws-fn.c	Mon Dec 26 14:00:10 2016 +0100
@@ -51,6 +51,7 @@
     { "send-file", send_file, NULL, NULL, 0},
     { "common-index", service_index, NULL, NULL, 0},
     { "service-hello", service_hello, NULL, NULL, 0},
+    { "service-ws-hello", service_ws_hello, NULL, NULL, 0},
     { "send-options", send_options, NULL, NULL, 0},
     { "admin-init", admin_init, NULL, NULL, 0},
     { "admin-service", admin_service, NULL, NULL, 0},
--- a/src/server/public/nsapi.h	Sun Oct 30 11:44:04 2016 +0100
+++ b/src/server/public/nsapi.h	Mon Dec 26 14:00:10 2016 +0100
@@ -1110,6 +1110,48 @@
 typedef struct  _threadpool_job   threadpool_job;
 typedef void*(*job_callback_f)(void *data);
 
+
+typedef struct WebSocket WebSocket;
+typedef struct WSMessage WSMessage;
+
+struct WebSocket {
+    int (*on_open)(WebSocket *);
+    int (*on_error)(WebSocket *);
+    int (*on_message)(WebSocket *, WSMessage *msg);
+    int (*on_close)(WebSocket *);
+    void *userdata;
+};
+
+struct WSMessage {
+    /*
+     * message data (text or binary)
+     */
+    char      *data;
+    
+    /*
+     * data length
+     */
+    size_t    length;
+    
+    /*
+     * message type (opcode)
+     * 0x0: continuation
+     * 0x1: text
+     * 0x2: binary
+     * 0x3-7: reserved non-control frame
+     * 0x8: close
+     * 0x9: ping
+     * 0xa: pong
+     * 0xb-f: reserved control frame
+     */
+    int       type;
+    
+    /*
+     * if the message is incomplete, next points to the continuation message
+     */
+    WSMessage *next;
+};
+
 /* --- End type definitions --- */
 
 /* --- Begin dispatch vector table definition --- */
@@ -1394,6 +1436,15 @@
 
 NSAPI_PUBLIC char **http_hdrs2env(pblock *pb);
 
+// new websocket API begin
+
+NSAPI_PUBLIC int http_handle_websocket(Session *sn, Request *rq, WebSocket *websocket);
+
+NSAPI_PUBLIC int websocket_send_text(SYS_NETFD csd, char *msg, size_t len);
+
+// websocket API end
+
+
 typedef void (*thrstartfunc)(void *);
 SYS_THREAD INTsysthread_start(int prio, int stksz, thrstartfunc fn, void *arg);
 NSAPI_PUBLIC void INTsysthread_sleep(int milliseconds);
--- a/src/server/safs/service.c	Sun Oct 30 11:44:04 2016 +0100
+++ b/src/server/safs/service.c	Mon Dec 26 14:00:10 2016 +0100
@@ -37,7 +37,6 @@
 #include "../daemon/protocol.h"
 #include "../daemon/vfs.h"
 
-//include <sys/sendfile.h>
 #include "../util/strbuf.h"
 #include <ucx/string.h>
 #include <ucx/utils.h>
@@ -449,6 +448,25 @@
     return REQ_PROCEED;
 }
 
+static int ws_msghandler(WebSocket *ws, WSMessage *msg) {
+    if(msg->type == 1) {
+        printf("Message(text): %.*s\n", (int)msg->length, msg->data);
+        websocket_send_text(ws->userdata, "hello", 5);
+    } else {
+        printf("Message: opcode: %d | length: %d\n", msg->type, (int)msg->length);
+    }
+    return 0;
+}
+
+int service_ws_hello(pblock *pb, Session *sn, Request *rq) {
+    WebSocket ws;
+    ZERO(&ws, sizeof(WebSocket));
+    ws.userdata = sn->csd;
+    
+    ws.on_message = ws_msghandler;
+    return http_handle_websocket(sn, rq, &ws);
+}
+
 int service_index(pblock *pb, Session *sn, Request *rq) {
     //printf("service_index\n");
 
--- a/src/server/safs/service.h	Sun Oct 30 11:44:04 2016 +0100
+++ b/src/server/safs/service.h	Mon Dec 26 14:00:10 2016 +0100
@@ -47,6 +47,7 @@
 int send_file(pblock *pb, Session *sn, Request *rq);
 
 int service_hello(pblock *pb, Session *sn, Request *rq);
+int service_ws_hello(pblock *pb, Session *sn, Request *rq);
 
 int service_index(pblock *pb, Session *sn, Request *rq);
 
--- a/src/server/util/util.c	Sun Oct 30 11:44:04 2016 +0100
+++ b/src/server/util/util.c	Mon Dec 26 14:00:10 2016 +0100
@@ -59,6 +59,9 @@
 #include "pblock.h"
 #include "util.h"
 
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/evp.h>
 
 
 /* ------------------------------ _uudecode ------------------------------- */
@@ -111,6 +114,29 @@
     return nbytesdecoded;
 }
 
+char* util_base64encode(char *in, size_t len) { 
+    BIO *b;
+    BIO *e;
+    BUF_MEM *mem;
+
+    e = BIO_new(BIO_f_base64());
+    b = BIO_new(BIO_s_mem());
+    BIO_set_flags(e, BIO_FLAGS_BASE64_NO_NL);
+    
+    e = BIO_push(e, b);
+    BIO_write(e, in, len);
+    BIO_flush(e);
+    
+    BIO_get_mem_ptr(e, &mem);
+    char *out = malloc(mem->length + 1);
+    memcpy(out, mem->data, mem->length);
+    out[mem->length] = '\0';
+
+    BIO_free_all(e);
+
+    return out;
+}
+
 
 /* --------------------------- util_env_create ---------------------------- */
 
--- a/src/server/util/util.h	Sun Oct 30 11:44:04 2016 +0100
+++ b/src/server/util/util.h	Mon Dec 26 14:00:10 2016 +0100
@@ -77,7 +77,8 @@
 extern "C" {
 #endif
 
-size_t util_base64decode(char *bufcoded, size_t codedbytes, char *bufout); 
+size_t util_base64decode(char *bufcoded, size_t codedbytes, char *bufout);
+char* util_base64encode(char *in, size_t len);
 
 NSAPI_PUBLIC
 int INTutil_init_PRNetAddr(PRNetAddr * naddr, char * ipstr, int iplen, int type);

mercurial