diff options
author | Nikos Mavrogiannopoulos <nmav@gnutls.org> | 2010-06-03 11:05:06 +0200 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@gnutls.org> | 2010-06-17 20:47:39 +0200 |
commit | f3146e2631f23c80e9ce43cbff33b294cab9f535 (patch) | |
tree | 57b7a51b687c4b720bb9ad38cfac7aee33338b2e | |
parent | f72b0d07a071d20cf27ed1cc160de5bd675dc992 (diff) | |
download | kernel-crypto-f3146e2631f23c80e9ce43cbff33b294cab9f535.tar.gz kernel-crypto-f3146e2631f23c80e9ce43cbff33b294cab9f535.tar.xz kernel-crypto-f3146e2631f23c80e9ce43cbff33b294cab9f535.zip |
Added some initial support for a userspace server to receive requests.
-rw-r--r-- | ncr-storage-low.c | 118 | ||||
-rw-r--r-- | ncr-storage-low.h | 54 | ||||
-rw-r--r-- | ncr-storage.h | 20 | ||||
-rw-r--r-- | userspace/ncr-server/Makefile | 7 | ||||
-rw-r--r-- | userspace/ncr-server/list.h | 244 | ||||
-rw-r--r-- | userspace/ncr-server/ncr-server.c | 210 | ||||
-rw-r--r-- | userspace/ncr-server/ncr-server.h | 11 |
7 files changed, 597 insertions, 67 deletions
diff --git a/ncr-storage-low.c b/ncr-storage-low.c index 47cd8e84d8e..418bc8ff969 100644 --- a/ncr-storage-low.c +++ b/ncr-storage-low.c @@ -41,6 +41,7 @@ struct event_item_st { struct completion completed; void* reply; size_t reply_size; + uint32_t ireply; uint32_t id; }; @@ -68,10 +69,11 @@ struct event_item_st* item; return 0; } -static void event_wait(uint32_t id) +static int event_wait(uint32_t id) { struct event_item_st* item; struct completion* completed = NULL; +int ret = 0; down(&event_list.sem); @@ -82,10 +84,13 @@ struct completion* completed = NULL; } } up(&event_list.sem); - if (completed) - wait_for_completion(completed); + if (completed) { + ret = wait_for_completion_interruptible(completed); + } + return 0; } + static void event_complete(uint32_t id) { struct event_item_st* item; @@ -119,6 +124,23 @@ struct event_item_st* item; return; } +static void event_set_idata(uint32_t id, uint32_t data) +{ +struct event_item_st* item; + + down(&event_list.sem); + + list_for_each_entry(item, &event_list.list, list) { + if (id == item->id) { + item->ireply = data; + break; + } + } + up(&event_list.sem); + + return; +} + static void* event_get_data(uint32_t id, size_t *reply_size) { struct event_item_st* item; @@ -138,6 +160,24 @@ void* reply = NULL; return reply; } +static uint32_t event_get_idata(uint32_t id) +{ +struct event_item_st* item; +uint32_t reply = -1; + + down(&event_list.sem); + + list_for_each_entry(item, &event_list.list, list) { + if (id == item->id) { + reply = item->ireply; + break; + } + } + up(&event_list.sem); + + return reply; +} + static void event_remove(uint32_t id) { struct event_item_st* item, *tmp; @@ -157,22 +197,14 @@ struct event_item_st* item, *tmp; } -/* attributes (variables): the index in this enum is used as a reference for the type, - * userspace application has to indicate the corresponding type - * the policy is used for security considerations - */ -enum { - ATTR_UNSPEC, - ATTR_DATA, - __ATTR_MAX, -}; -#define ATTR_MAX (__ATTR_MAX - 1) - /* attribute policy: defines which attribute has which type (e.g int, char * etc) * possible values defined in net/netlink.h */ static struct nla_policy ncr_genl_policy[ATTR_MAX + 1] = { - [ATTR_DATA] = { .type = NLA_BINARY }, + [ATTR_STRUCT_LOAD] = { .type = NLA_BINARY }, + [ATTR_STRUCT_LOADED] = { .type = NLA_BINARY }, + [ATTR_STORE_ACK] = { .type = NLA_BINARY }, + [ATTR_STRUCT_STORE] = { .type = NLA_BINARY }, }; static atomic_t ncr_event_sr; @@ -183,31 +215,19 @@ static uint32_t listener_pid = -1; static struct genl_family ncr_gnl_family = { .id = GENL_ID_GENERATE, //genetlink should generate an id .hdrsize = 0, - .name = "KEY_STORAGE", //the name of this family, used by userspace application + .name = NCR_NL_STORAGE_NAME, //the name of this family, used by userspace application .version = VERSION_NR, //version number .maxattr = ATTR_MAX, }; -/* commands: enumeration of all commands (functions), - * used by userspace application to identify command to be ececuted - */ -enum { - CMD_LISTENING, - CMD_STORE, - CMD_LOAD, - CMD_STORE_ACK, - CMD_LOADED_DATA, - __CMD_MAX, -}; -#define CMD_MAX (__CMD_MAX - 1) /* an echo command, receives a message, prints it and sends another message back */ int _ncr_store(const struct storage_item_st * tostore) { struct sk_buff *skb; int ret; - uint8_t *reply; - size_t size, reply_size; + uint32_t reply; + size_t size; struct nlattr *attr; void* msg, *msg_head; uint32_t id; @@ -238,7 +258,7 @@ int _ncr_store(const struct storage_item_st * tostore) } /* fill the data */ - attr = nla_reserve(skb, ATTR_DATA, + attr = nla_reserve(skb, ATTR_STRUCT_STORE, sizeof(struct storage_item_st)); if (!attr) { err(); @@ -264,13 +284,16 @@ int _ncr_store(const struct storage_item_st * tostore) goto out; /* wait for an acknowledgment */ - event_wait(id); + ret = event_wait(id); + if (ret) { + goto out; + } - reply = event_get_data(id, &reply_size); - if (reply_size < 1) + reply = event_get_idata(id, &reply_size); + if (reply == (uint32_t)-1) BUG(); - if (reply[0] != 0) { + if (reply != 0) { /* write failed */ ret = -EIO; } else { @@ -325,7 +348,7 @@ int _ncr_load(struct storage_item_st * toload) } /* fill the data */ - attr = nla_reserve(skb, ATTR_DATA, + attr = nla_reserve(skb, ATTR_STRUCT_LOAD, sizeof(struct ncr_gnl_load_cmd_st)); if (!attr) { err(); @@ -361,7 +384,10 @@ int _ncr_load(struct storage_item_st * toload) goto out; /* wait for an answer */ - event_wait(id); + ret = event_wait(id); + if (ret) { + goto out; + } reply = event_get_data(id, &reply_size); if (reply_size != sizeof(struct storage_item_st)) @@ -406,7 +432,7 @@ int ncr_gnl_store_ack(struct sk_buff *skb, struct genl_info *info) /*for each attribute there is an index in info->attrs which points to a nlattr structure *in this structure the data is given */ - na = info->attrs[ATTR_DATA]; + na = info->attrs[ATTR_STORE_ACK]; if (na) { len = nla_len(na); data = (void *)nla_data(na); @@ -415,17 +441,13 @@ int ncr_gnl_store_ack(struct sk_buff *skb, struct genl_info *info) else { memcpy( &reply, data, sizeof(reply)); - event_reply = kmalloc(1, GFP_KERNEL); - if (event_reply != NULL) { - if (reply.reply == 0) - event_reply[0] = 0; - else event_reply[0] = 1; /* fail */ - event_set_data(reply.id, event_reply, 1); - } + + event_set_idata(reply.id, (uint32_t)reply.reply[0]); + event_complete(reply.id); } } else - printk(KERN_DEBUG"no info->attrs %i\n", ATTR_DATA); + printk(KERN_DEBUG"no info->attrs %i\n", ATTR_STORE_ACK); return 0; } @@ -444,7 +466,7 @@ int ncr_gnl_loaded_data(struct sk_buff *skb, struct genl_info *info) /*for each attribute there is an index in info->attrs which points to a nlattr structure *in this structure the data is given */ - na = info->attrs[ATTR_DATA]; + na = info->attrs[ATTR_STRUCT_LOADED]; if (na) { len = nla_len(na); data = (void *)nla_data(na); @@ -461,7 +483,7 @@ int ncr_gnl_loaded_data(struct sk_buff *skb, struct genl_info *info) event_complete(reply.id); } } else - printk(KERN_DEBUG"no info->attrs %i\n", ATTR_DATA); + printk(KERN_DEBUG"no info->attrs %i\n", ATTR_STRUCT_LOADED); return 0; } diff --git a/ncr-storage-low.h b/ncr-storage-low.h index 2578ac9bf7b..814b0df2893 100644 --- a/ncr-storage-low.h +++ b/ncr-storage-low.h @@ -1,6 +1,60 @@ #ifndef _STORAGE_LOW #define _STORAGE_LOW +#include <ncr.h> + +#define NCR_NL_STORAGE_NAME "KEY_STORAGE" + +#define NCR_NL_STORAGE_VERSION 1 + +/* commands: enumeration of all commands (functions), + * used by userspace application to identify command to be ececuted + */ +enum { + CMD_LISTENING, /* sent by server */ + CMD_STORE, /* sent by kernel */ + CMD_LOAD, /* sent by kernel */ + CMD_STORE_ACK, /* sent by server */ + CMD_LOADED_DATA, /* sent by server */ + __CMD_MAX, +}; +#define CMD_MAX (__CMD_MAX - 1) + +/* attributes (variables): the index in this enum is used as a reference for the type, + * userspace application has to indicate the corresponding type + * the policy is used for security considerations + */ +enum { + ATTR_UNSPEC, + ATTR_STRUCT_LOAD, + ATTR_STRUCT_LOADED, + ATTR_STRUCT_STORE, + ATTR_STORE_ACK, /* u8*/ + __ATTR_MAX, +}; +#define ATTR_MAX (__ATTR_MAX - 1) + +#define MAX_DATA_SIZE 10*1024 +#define MAX_RAW_KEY_SIZE 4096 + +struct storage_item_st { + /* metadata */ + uint8_t label[MAX_LABEL_SIZE]; + uint32_t owner; + uint32_t group; + mode_t mode; + + uint16_t algorithm; + uint8_t type; + + uint8_t key_id[MAX_KEY_ID_SIZE]; + uint8_t key_id_size; + + /* data */ + uint8_t raw_key[MAX_RAW_KEY_SIZE]; + uint16_t raw_key_size; +} __attribute__ ((__packed__)); + struct ncr_gnl_load_cmd_st { uint8_t label[MAX_LABEL_SIZE]; uint32_t owner; diff --git a/ncr-storage.h b/ncr-storage.h index e1016e82b1a..9384b6ef6f5 100644 --- a/ncr-storage.h +++ b/ncr-storage.h @@ -1,25 +1,7 @@ #ifndef NCR_STORAGE_H # define NCR_STORAGE_H -#define MAX_RAW_KEY_SIZE 4096 - -struct storage_item_st { - /* metadata */ - uint8_t label[MAX_LABEL_SIZE]; - uint32_t owner; - uint32_t group; - mode_t mode; - - uint16_t algorithm; - uint8_t type; - - uint8_t key_id[MAX_KEY_ID_SIZE]; - uint8_t key_id_size; - - /* data */ - uint8_t raw_key[MAX_RAW_KEY_SIZE]; - uint16_t raw_key_size; -} __attribute__ ((__packed__)); +#include "ncr-storage-low.h" /* for struct storage_item_st */ int ncr_storage_store(struct list_sem_st* key_lst, void __user* arg); int ncr_storage_load(struct list_sem_st* key_lst, void __user* arg); diff --git a/userspace/ncr-server/Makefile b/userspace/ncr-server/Makefile new file mode 100644 index 00000000000..ee46dd6ae46 --- /dev/null +++ b/userspace/ncr-server/Makefile @@ -0,0 +1,7 @@ +all: ncr-server + +CFLAGS=-O2 -Wall -g -I.. -I. +CC=gcc + +ncr-server: ncr-server.c ncr-server.h + $(CC) $(CFLAGS) ncr-server.c -o ncr-server diff --git a/userspace/ncr-server/list.h b/userspace/ncr-server/list.h new file mode 100644 index 00000000000..3a7688566a6 --- /dev/null +++ b/userspace/ncr-server/list.h @@ -0,0 +1,244 @@ +#ifndef __LIST_H +#define __LIST_H + +/* This file is from Linux Kernel (include/linux/list.h) + * and modified by simply removing hardware prefetching of list items. + * Here by copyright, credits attributed to wherever they belong. + * Kulesh Shanmugasundaram (kulesh [squiggly] isis.poly.edu) + */ + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head *prev, struct list_head *next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = (void *) 0; + entry->prev = (void *) 0; +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(struct list_head *head) +{ + return head->next == head; +} + +static inline void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +/** + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); \ + pos = pos->next) +/** + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; pos != (head); \ + pos = pos->prev) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + + +#endif diff --git a/userspace/ncr-server/ncr-server.c b/userspace/ncr-server/ncr-server.c new file mode 100644 index 00000000000..3b3c72ad145 --- /dev/null +++ b/userspace/ncr-server/ncr-server.c @@ -0,0 +1,210 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <netlink/netlink.h> +#include <netlink/socket.h> +#include <netlink/genl/genl.h> +#include <netlink/genl/ctrl.h> +#include "../ncr-storage-low.h" +#include "ncr-server.h" +#include <list.h> + +static int _notify_listening(struct nl_sock * sock); + +static struct nla_policy def_policy[ATTR_MAX+1] = +{ + [ATTR_STRUCT_STORE] = { .type = NLA_UNSPEC, + .minlen = sizeof(struct storage_item_st) }, + [ATTR_STRUCT_LOAD] = { .type = NLA_UNSPEC, + .minlen = sizeof(struct ncr_gnl_load_cmd_st) }, +}; + +static struct actions { + list_head list; + int cmd; /* CMD_* */ + union { + struct storage_item_st tostore; + struct ncr_gnl_load_cmd_st toload; + } data; +} todo; + +static int add_store_cmd(struct storage_item_st* tostore) +{ + struct actions* item; + + item = malloc(sizeof(*item)); + if (item == NULL) { + err(); + return -ERR_MEM; + } + item->cmd = CMD_STORE; + memcpy(item->data.tostore, tostore, sizeof(item->data.tostore)); + + list_add(item, &todo.list); + return 0; +} + +static int add_load_cmd(struct ncr_gnl_load_cmd_st* toload) +{ + struct actions* item; + + item = malloc(sizeof(*item)); + if (item == NULL) { + err(); + return -ERR_MEM; + } + item->cmd = CMD_LOAD; + memcpy(item->data.toload, toload, sizeof(item->data.toload)); + + list_add(item, &todo.list); + return 0; +} + +static int msg_cb(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *nlh = nlmsg_hdr(msg); + struct nlattr *attrs[ATTR_MAX+1]; + + // Validate message and parse attributes + genlmsg_parse(nlh, 0, attrs, ATTR_MAX, def_policy); + + if (attrs[ATTR_STRUCT_STORE]) { + struct storage_item_st *item = nla_get(attrs[ATTR_STRUCT_STORE]); + fprintf(stderr, "asked to store: %s\n", item->label); + + add_store_cmd(item); + } + + if (attrs[ATTR_STRUCT_LOAD]) { + struct ncr_gnl_load_cmd_st *load = nla_get(attrs[ATTR_STRUCT_LOAD]); + fprintf(stderr, "asked to load: %s\n", load->label); + + add_load_cmd(item); + } + + return NL_STOP; +} + + +int main() +{ +struct nl_sock *sock; +int ret; + + memset(&todo, 0, sizeof(todo)); + LIST_HEAD_INIT(&todo.list); + + // Allocate a new netlink socket + sock = nl_socket_alloc(); + if (sock == NULL) { + err(); + return ERR_CONNECT; + } + + // Connect to generic netlink socket on kernel side + ret = genl_connect(sock); + if (ret < 0) { + err(); + return ERR_CONNECT; + } + + // Ask kernel to resolve family name to family id + family = genl_ctrl_resolve(sock, NCR_NL_STORAGE_NAME); + if (family < 0) { + err(); + return ERR_CONNECT; + } + + /* set our callback to receive messages */ + ret = nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, msg_cb, NULL); + if (ret < 0) { + fprintf(stderr, "Could not set listening callback.\n"); + exit(1); + } + + ret = _notify_listening(sock); + if (ret < 0) { + fprintf(stderr, "Could not notify kernel subsystem.\n"); + exit(1); + } + + // Wait for the answer and receive it + do { + ret = nl_recvmsgs_default(sock); + + /* we have to consume the todo list */ + if (ret == 0) { + //store_if_needed(sock); + //load_if_needed(sock); + } + } while (ret == 0); + fprintf(stderr, "received: %d\n", ret); + +} + +static int _notify_listening(struct nl_sock * sock) +{ +struct nl_msg *msg; +int family, ret; + + // Construct a generic netlink by allocating a new message, fill in + // the header and append a simple integer attribute. + msg = nlmsg_alloc(); + if (msg == NULL) { + err(); + return ERR_MEM; + } + + genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_REQUEST, + CMD_LISTENING, NCR_NL_STORAGE_VERSION); + + // Send message over netlink socket + ret = nl_send_auto_complete(sock, msg); + nlmsg_free(msg); + + if (ret < 0) { + err(); + return ERR_CONNECT; + } + + return 0; +} + + +static int send_store_ack(struct nl_sock * sock, uint8_t val) +{ +struct nl_msg *msg; +int family, ret; + + // Construct a generic netlink by allocating a new message, fill in + // the header and append a simple integer attribute. + msg = nlmsg_alloc(); + if (msg == NULL) { + err(); + return ERR_MEM; + } + + genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_REQUEST, + CMD_STORE_ACK, NCR_NL_STORAGE_VERSION); + + ret = nla_put_u8(msg, ATTR_STORE_ACK, val); + if (ret < 0) { + err(); + ret = ERR_SEND; + goto fail; + } + + // Send message over netlink socket + ret = nl_send_auto_complete(sock, msg); + nlmsg_free(msg); + + if (ret < 0) { + err(); + return ERR_SEND; + } + + return 0; +fail: + nlmsg_free(msg); + return ret; +} diff --git a/userspace/ncr-server/ncr-server.h b/userspace/ncr-server/ncr-server.h new file mode 100644 index 00000000000..dae0757ed09 --- /dev/null +++ b/userspace/ncr-server/ncr-server.h @@ -0,0 +1,11 @@ +#ifndef NCR_SERVER_H +#define NCR_SERVER_H + +#define err() fprintf(stderr, "Error in %s:%d\n", __FILE__, __LINE__); + +#define ERR_CONNECT -1 +#define ERR_MEM -2 +#define ERR_SEND -3 + + +#endif |