From ea969c6e420f93f47fc7bf9b52abbffdee696a49 Mon Sep 17 00:00:00 2001 From: Sean Qureshi Date: Wed, 7 Aug 2013 00:24:37 -0700 Subject: [PATCH 1/9] Manually merged my earlier commits with upstream --- testing/toxic/main.c | 3 ++- testing/toxic/prompt.c | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/testing/toxic/main.c b/testing/toxic/main.c index 1ba8b6c9..2bf16769 100644 --- a/testing/toxic/main.c +++ b/testing/toxic/main.c @@ -22,6 +22,7 @@ extern int add_req(uint8_t *public_key); // XXX /* Holds status of chat windows */ char WINDOW_STATUS[MAX_WINDOW_SLOTS]; +#define TOXICVER "0.1.0" //Will be moved to a -D flag later static ToxWindow windows[MAX_WINDOW_SLOTS]; static ToxWindow* prompt; @@ -257,7 +258,7 @@ static void draw_bar() move(LINES - 1, 0); attron(COLOR_PAIR(4) | A_BOLD); - printw(" TOXIC 1.0 |"); + printw(" TOXIC " TOXICVER " |"); attroff(COLOR_PAIR(4) | A_BOLD); int i; diff --git a/testing/toxic/prompt.c b/testing/toxic/prompt.c index 89c87d8f..c4d7d2f1 100644 --- a/testing/toxic/prompt.c +++ b/testing/toxic/prompt.c @@ -52,6 +52,13 @@ static void execute(ToxWindow *self, char *u_cmd) cmd[i - newlines] = u_cmd[i]; } + if (cmd[0] == '/') { + int i; + for (i = i1; i < strlen(cmd); i++) { //This doesn't work when it doesn't end with a space and another word + cmd[i - 1] = cmd[i]; //Still working on why + } + } + if (!strcmp(cmd, "quit") || !strcmp(cmd, "exit") || !strcmp(cmd, "q")) { endwin(); exit(0); From c02e1a095cec0e76d500ec7f22763a928e361d00 Mon Sep 17 00:00:00 2001 From: Sean Qureshi Date: Wed, 7 Aug 2013 00:26:21 -0700 Subject: [PATCH 2/9] Fixed flash killing beep --- testing/toxic/chat.c | 1 - 1 file changed, 1 deletion(-) diff --git a/testing/toxic/chat.c b/testing/toxic/chat.c index 20c01620..344071f5 100644 --- a/testing/toxic/chat.c +++ b/testing/toxic/chat.c @@ -57,7 +57,6 @@ static void chat_onMessage(ToxWindow *self, int num, uint8_t *msg, uint16_t len) self->blink = true; beep(); - flash(); } static void chat_onNickChange(ToxWindow *self, int num, uint8_t *nick, uint16_t len) From 2b916b96bd46823afe28ff0a798a4995d0e99067 Mon Sep 17 00:00:00 2001 From: Sean Qureshi Date: Wed, 7 Aug 2013 09:35:37 -0700 Subject: [PATCH 3/9] Fixed it not compiling --- testing/toxic/prompt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/toxic/prompt.c b/testing/toxic/prompt.c index c4d7d2f1..24b728f3 100644 --- a/testing/toxic/prompt.c +++ b/testing/toxic/prompt.c @@ -54,7 +54,7 @@ static void execute(ToxWindow *self, char *u_cmd) if (cmd[0] == '/') { int i; - for (i = i1; i < strlen(cmd); i++) { //This doesn't work when it doesn't end with a space and another word + for (i = i; i < strlen(cmd); i++) { //This doesn't work when it doesn't end with a space and another word cmd[i - 1] = cmd[i]; //Still working on why } } From 105e2fa4a38de07493f9388ab23acba2b2969458 Mon Sep 17 00:00:00 2001 From: Sebastian Stal Date: Wed, 7 Aug 2013 09:53:52 -0700 Subject: [PATCH 4/9] Add read receipts using packet ID 65. --- core/Messenger.c | 52 ++++++++++++++++++++++++++++++++++++++++++------ core/Messenger.h | 14 ++++++++++--- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/core/Messenger.c b/core/Messenger.c index d8bf3413..a4195d58 100644 --- a/core/Messenger.c +++ b/core/Messenger.c @@ -37,6 +37,8 @@ typedef struct { uint8_t userstatus_sent; USERSTATUS_KIND userstatus_kind; uint16_t info_size; /* length of the info */ + uint32_t message_id; /* a semi-unique id used in read receipts */ + uint8_t receives_read_receipts; /* shall we send read receipts to this person? */ } Friend; uint8_t self_public_key[crypto_box_PUBLICKEYBYTES]; @@ -128,6 +130,8 @@ int m_addfriend(uint8_t *client_id, uint8_t *data, uint16_t length) friendlist[i].userstatus_kind = USERSTATUS_KIND_OFFLINE; memcpy(friendlist[i].info, data, length); friendlist[i].info_size = length; + friendlist[i].message_id = 0; + friendlist[i].receives_read_receipts = 1; /* default: YES */ ++numfriends; return i; @@ -150,6 +154,8 @@ int m_addfriend_norequest(uint8_t * client_id) memcpy(friendlist[i].client_id, client_id, CLIENT_ID_SIZE); friendlist[i].userstatus = calloc(1, 1); friendlist[i].userstatus_length = 1; + friendlist[i].message_id = 0; + friendlist[i].receives_read_receipts = 1; /* default: YES */ numfriends++; return i; } @@ -193,19 +199,30 @@ int m_friendstatus(int friendnumber) } /* send a text chat message to an online friend - return 1 if packet was successfully put into the send queue + return the message id if packet was successfully put into the send queue return 0 if it was not */ -int m_sendmessage(int friendnumber, uint8_t *message, uint32_t length) +uint32_t m_sendmessage(int friendnumber, uint8_t *message, uint32_t length) { if (friendnumber < 0 || friendnumber >= numfriends) return 0; - if (length >= MAX_DATA_SIZE || friendlist[friendnumber].status != FRIEND_ONLINE) + return m_sendmessage_withid(friendnumber, friendlist[friendnumber].message_id++, message, length); +} + +uint32_t m_sendmessage_withid(int friendnumber, uint32_t theid, uint8_t *message, uint32_t length) +{ + if (friendnumber < 0 || friendnumber >= numfriends) + return 0; + if (length >= (MAX_DATA_SIZE - 4) || friendlist[friendnumber].status != FRIEND_ONLINE) /* this does not mean the maximum message length is MAX_DATA_SIZE - 1, it is actually 17 bytes less. */ return 0; uint8_t temp[MAX_DATA_SIZE]; temp[0] = PACKET_ID_MESSAGE; - memcpy(temp + 1, message, length); - return write_cryptpacket(friendlist[friendnumber].crypt_connection_id, temp, length + 1); + temp[1] = theid >> 24; + temp[2] = theid >> 16; + temp[3] = theid >> 8; + temp[4] = theid; + memcpy(temp + 5, message, length); + return write_cryptpacket(friendlist[friendnumber].crypt_connection_id, temp, length + 5); } /* send a name packet to friendnumber @@ -408,6 +425,14 @@ void m_callback_userstatus(void (*function)(int, USERSTATUS_KIND, uint8_t *, uin friend_statuschange_isset = 1; } +static void (*read_receipt)(int, uint32_t); +static uint8_t read_receipt_isset = 0; +void m_callback_read_receipt(void (*function)(int, uint32_t)) +{ + read_receipt = function; + read_receipt_isset = 1; +} + #define PORT 33445 /* run this at startup */ int initMessenger(void) @@ -499,8 +524,23 @@ static void doFriends(void) break; } case PACKET_ID_MESSAGE: { + if (friendlist[i].receives_read_receipts) { + uint8_t *thepacket = malloc(5); + thepacket[0] = PACKET_ID_RECEIPT; + memcpy(thepacket + 1, temp + 1, 4); + write_cryptpacket(friendlist[i].crypt_connection_id, thepacket, 5); + free(thepacket); + } if (friend_message_isset) - (*friend_message)(i, temp + 1, len - 1); + (*friend_message)(i, temp + 5, len - 5); + break; + } + case PACKET_ID_RECEIPT: { + if (len < 5) + break; + uint32_t msgid = (temp[1] << 24) | (temp[2] << 16) | (temp[3] << 8) | temp[4]; + if (read_receipt_isset) + (*read_receipt)(i, msgid); break; } } diff --git a/core/Messenger.h b/core/Messenger.h index 8940aadd..0e4eabe0 100644 --- a/core/Messenger.h +++ b/core/Messenger.h @@ -40,6 +40,7 @@ extern "C" { #define PACKET_ID_NICKNAME 48 #define PACKET_ID_USERSTATUS 49 +#define PACKET_ID_RECEIPT 65 #define PACKET_ID_MESSAGE 64 /* status definitions */ @@ -117,9 +118,14 @@ int m_delfriend(int friendnumber); int m_friendstatus(int friendnumber); /* send a text chat message to an online friend - returns 1 if packet was successfully put into the send queue - return 0 if it was not */ -int m_sendmessage(int friendnumber, uint8_t *message, uint32_t length); + returns the message id if packet was successfully put into the send queue + return 0 if it was not + you will want to retain the return value, it will be passed to your read receipt callback + if one is received. + m_sendmessage_withid will send a message with the id of your choosing, + however we can generate an id for you by calling plain m_sendmessage. */ +uint32_t m_sendmessage(int friendnumber, uint8_t *message, uint32_t length); +uint32_t m_sendmessage_withid(int friendnumber, uint32_t theid, uint8_t *message, uint32_t length); /* Set our nickname name must be a string of maximum MAX_NAME_LENGTH length. @@ -183,6 +189,8 @@ void m_callback_namechange(void (*function)(int, uint8_t *, uint16_t)); you are not responsible for freeing newstatus */ void m_callback_userstatus(void (*function)(int, USERSTATUS_KIND, uint8_t *, uint16_t)); +void m_callback_read_receipt(void (*function)(int, uint32_t)); + /* run this at startup returns 0 if no connection problems returns -1 if there are problems */ From 9f6262f3ddb7be3cbb9a12c9b4a2e1f96c5dcfbf Mon Sep 17 00:00:00 2001 From: Sebastian Stal Date: Wed, 7 Aug 2013 09:57:23 -0700 Subject: [PATCH 5/9] Check for correct error value in toxic. --- testing/toxic/chat.c | 2 +- testing/toxic/prompt.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/toxic/chat.c b/testing/toxic/chat.c index 7262e722..bad4cbfa 100644 --- a/testing/toxic/chat.c +++ b/testing/toxic/chat.c @@ -127,7 +127,7 @@ static void chat_onKey(ToxWindow* self, int key) { wattroff(ctx->history, COLOR_PAIR(1)); wprintw(ctx->history, "%s\n", ctx->line); } - if(m_sendmessage(ctx->friendnum, (uint8_t*) ctx->line, strlen(ctx->line)+1) < 0) { + if(m_sendmessage(ctx->friendnum, (uint8_t*) ctx->line, strlen(ctx->line)+1) == 0) { wattron(ctx->history, COLOR_PAIR(3)); wprintw(ctx->history, " * Failed to send message.\n"); wattroff(ctx->history, COLOR_PAIR(3)); diff --git a/testing/toxic/prompt.c b/testing/toxic/prompt.c index 20f6b480..670a93a2 100644 --- a/testing/toxic/prompt.c +++ b/testing/toxic/prompt.c @@ -45,7 +45,7 @@ static void execute(ToxWindow* self, char* u_cmd) { int i; int newlines = 0; char cmd[256] = {0}; - for(i = 0; i < strlen(prompt_buf); i++) + for(i = 0; i < strlen(prompt_buf); i++) { if (u_cmd[i] == '\n') ++newlines; @@ -260,7 +260,7 @@ static void execute(ToxWindow* self, char* u_cmd) { msg[0] = 0; msg++; - if(m_sendmessage(atoi(id), (uint8_t*) msg, strlen(msg)+1) < 0) { + if(m_sendmessage(atoi(id), (uint8_t*) msg, strlen(msg)+1) == 0) { wprintw(self->window, "Error occurred while sending message.\n"); } else { @@ -338,7 +338,7 @@ static void print_usage(ToxWindow* self) { wprintw(self->window, " myid : Print your ID\n"); wprintw(self->window, " quit/exit : Exit program\n"); wprintw(self->window, " help : Print this message again\n"); - wprintw(self->window, " clear : Clear this window\n"); + wprintw(self->window, " clear : Clear this window\n"); wattron(self->window, A_BOLD); wprintw(self->window, "TIP: Use the TAB key to navigate through the tabs.\n\n"); From c0828667e70fdd92fd01c581ce04dfc451e01860 Mon Sep 17 00:00:00 2001 From: Sebastian Stal Date: Wed, 7 Aug 2013 10:06:07 -0700 Subject: [PATCH 6/9] Document functions, and fix bug. --- core/Messenger.c | 15 ++++++++++++++- core/Messenger.h | 11 +++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/core/Messenger.c b/core/Messenger.c index a4195d58..353ce603 100644 --- a/core/Messenger.c +++ b/core/Messenger.c @@ -205,7 +205,10 @@ uint32_t m_sendmessage(int friendnumber, uint8_t *message, uint32_t length) { if (friendnumber < 0 || friendnumber >= numfriends) return 0; - return m_sendmessage_withid(friendnumber, friendlist[friendnumber].message_id++, message, length); + uint32_t msgid = ++friendlist[friendnumber].message_id; + if (msgid == 0) + msgid = 1; /* otherwise, false error */ + return m_sendmessage_withid(friendnumber, msgid, message, length); } uint32_t m_sendmessage_withid(int friendnumber, uint32_t theid, uint8_t *message, uint32_t length) @@ -391,6 +394,16 @@ static void set_friend_userstatus_kind(int friendnumber, USERSTATUS_KIND k) friendlist[friendnumber].userstatus_kind = k; } +/* Sets whether we send read receipts for friendnumber. */ +void m_set_sends_receipts(int friendnumber, int yesno) +{ + if (yesno < 0 || yesno > 1) + return; + if (friendnumber >= numfriends || friendnumber < 0) + return; + friendlist[friendnumber].receives_read_receipts = yesno; +} + /* static void (*friend_request)(uint8_t *, uint8_t *, uint16_t); static uint8_t friend_request_isset = 0; */ /* set the function that will be executed when a friend request is received. */ diff --git a/core/Messenger.h b/core/Messenger.h index 0e4eabe0..f0444b91 100644 --- a/core/Messenger.h +++ b/core/Messenger.h @@ -171,6 +171,10 @@ int m_copy_self_userstatus(uint8_t *buf, uint32_t maxlen); USERSTATUS_KIND m_get_userstatus_kind(int friendnumber); USERSTATUS_KIND m_get_self_userstatus_kind(void); +/* Sets whether we send read receipts for friendnumber. + * This function is not lazy, and it will fail if yesno is not (0 or 1).*/ +void m_set_sends_receipts(int friendnumber, int yesno); + /* set the function that will be executed when a friend request is received. function format is function(uint8_t * public_key, uint8_t * data, uint16_t length) */ void m_callback_friendrequest(void (*function)(uint8_t *, uint8_t *, uint16_t)); @@ -189,6 +193,13 @@ void m_callback_namechange(void (*function)(int, uint8_t *, uint16_t)); you are not responsible for freeing newstatus */ void m_callback_userstatus(void (*function)(int, USERSTATUS_KIND, uint8_t *, uint16_t)); +/* set the callback for read receipts + function(int friendnumber, uint32_t receipt) + if you are keeping a record of returns from m_sendmessage, + receipt might be one of those values, and that means the message + has been received on the other side. since core doesn't + track ids for you, receipt may not correspond to any message + in that case, you should discard it. */ void m_callback_read_receipt(void (*function)(int, uint32_t)); /* run this at startup From ece3407c55c4616401e06272c06ae253cec3f615 Mon Sep 17 00:00:00 2001 From: Sean Qureshi Date: Wed, 7 Aug 2013 10:36:32 -0700 Subject: [PATCH 7/9] Moved main.c to maaster --- testing/toxic/main.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/testing/toxic/main.c b/testing/toxic/main.c index 2bf16769..27e3d858 100644 --- a/testing/toxic/main.c +++ b/testing/toxic/main.c @@ -22,7 +22,7 @@ extern int add_req(uint8_t *public_key); // XXX /* Holds status of chat windows */ char WINDOW_STATUS[MAX_WINDOW_SLOTS]; -#define TOXICVER "0.1.0" //Will be moved to a -D flag later +#define TOXICVER "0.1.0" //Will be moved to a -D flag later static ToxWindow windows[MAX_WINDOW_SLOTS]; static ToxWindow* prompt; @@ -250,6 +250,7 @@ static void load_data(char *path) static void draw_bar() { static int odd = 0; + int blinkrate = 30; attron(COLOR_PAIR(4)); mvhline(LINES - 2, 0, '_', COLS); @@ -258,7 +259,7 @@ static void draw_bar() move(LINES - 1, 0); attron(COLOR_PAIR(4) | A_BOLD); - printw(" TOXIC " TOXICVER " |"); + printw(" TOXIC " TOXICVER " |"); attroff(COLOR_PAIR(4) | A_BOLD); int i; @@ -267,14 +268,13 @@ static void draw_bar() if (i == active_window) attron(A_BOLD); - odd = (odd+1) % 10; - if (windows[i].blink && (odd < 5)) { + odd = (odd+1) % blinkrate; + if (windows[i].blink && (odd < (blinkrate/2))) { attron(COLOR_PAIR(3)); } - printw(" %s", windows[i].title); - if (windows[i].blink && (odd < 5)) { - attron(COLOR_PAIR(3)); + if (windows[i].blink && (odd < (blinkrate/2))) { + attroff(COLOR_PAIR(3)); } if (i == active_window) { attroff(A_BOLD); @@ -376,9 +376,8 @@ int main(int argc, char *argv[]) ch = getch(); if (ch == '\t' || ch == KEY_BTAB) set_active_window(ch); - else if (ch != ERR) { + else if (ch != ERR) a->onKey(a, ch); - } } return 0; } From 3622a5c04c27d28ac7c83d629fea77248f8e709e Mon Sep 17 00:00:00 2001 From: Sean Qureshi Date: Wed, 7 Aug 2013 10:57:17 -0700 Subject: [PATCH 8/9] Manually merged prompt.c to upstream --- testing/toxic/prompt.c | 65 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/testing/toxic/prompt.c b/testing/toxic/prompt.c index 24b728f3..b6827ce2 100644 --- a/testing/toxic/prompt.c +++ b/testing/toxic/prompt.c @@ -52,9 +52,21 @@ static void execute(ToxWindow *self, char *u_cmd) cmd[i - newlines] = u_cmd[i]; } + int leading_spc = 0; + for (i = 0; i < 256 && isspace(cmd[i]); ++i) + leading_spc++; + memmove(cmd, cmd + leading_spc, 256 - leading_spc); + + int cmd_end = strlen(cmd); + while (cmd_end > 0 && cmd_end--) + if (!isspace(cmd[cmd_end])) + break; + cmd[cmd_end + 1] = '\0'; + if (cmd[0] == '/') { + wprintw(self->window,"Warning: Run your command without the /, this may not work\n"); int i; - for (i = i; i < strlen(cmd); i++) { //This doesn't work when it doesn't end with a space and another word + for (i = 1; i < strlen(cmd); i++) { //This doesn't work when it doesn't end with a space and another word cmd[i - 1] = cmd[i]; //Still working on why } } @@ -171,6 +183,54 @@ static void execute(ToxWindow *self, char *u_cmd) } else if (!strncmp(cmd, "status ", strlen("status "))) { + char *status = strchr(cmd, ' '); + char *msg; + char *status_text; + if (status == NULL) { + wprintw(self->window, "Invalid syntax.\n"); + return; + } + status++; + USERSTATUS_KIND status_kind; + if (!strncmp(status, "online", strlen("online"))) { + status_kind = USERSTATUS_KIND_ONLINE; + status_text = "ONLINE"; + } + + else if (!strncmp(status, "away", strlen("away"))) { + status_kind = USERSTATUS_KIND_AWAY; + status_text = "AWAY"; + } + + else if (!strncmp(status, "busy", strlen("busy"))) { + status_kind = USERSTATUS_KIND_BUSY; + status_text = "BUSY"; + } + + else if (!strncmp(status, "offline", strlen("offline"))) { + status_kind = USERSTATUS_KIND_OFFLINE; + status_text = "OFFLINE"; + } + + else + { + wprintw(self->window, "Invalid status.\n"); + return; + } + + msg = strchr(status, ' '); + if (msg == NULL) { + m_set_userstatus_kind(status_kind); + wprintw(self->window, "Status set to: %s\n", status_text); + } + else { + msg++; + m_set_userstatus(status_kind, (uint8_t*) msg, strlen(msg)+1); + wprintw(self->window, "Status set to: %s, %s\n", status_text, msg); + } + } + + else if (!strncmp(cmd, "statusmsg ", strlen("statumsg "))) { char *msg = strchr(cmd, ' '); if (msg == NULL) { wprintw(self->window, "Invalid syntax.\n"); @@ -313,7 +373,8 @@ static void print_usage(ToxWindow *self) wprintw(self->window, " connect : Connect to DHT server\n"); wprintw(self->window, " add : Add friend\n"); - wprintw(self->window, " status : Set your status\n"); + wprintw(self->window, " status : Set your status\n"); + wprintw(self->window, " statusmsg : Set your status\n"); wprintw(self->window, " nick : Set your nickname\n"); wprintw(self->window, " accept : Accept friend request\n"); wprintw(self->window, " myid : Print your ID\n"); From 4dbcfdf05dfc9127d2356d68399f91366db8b645 Mon Sep 17 00:00:00 2001 From: irungentoo Date: Wed, 7 Aug 2013 18:14:35 -0400 Subject: [PATCH 9/9] Some cosmetic improvements. --- core/Messenger.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/Messenger.c b/core/Messenger.c index 33af599d..b29bd1f1 100644 --- a/core/Messenger.c +++ b/core/Messenger.c @@ -215,17 +215,15 @@ uint32_t m_sendmessage_withid(int friendnumber, uint32_t theid, uint8_t *message { if (friendnumber < 0 || friendnumber >= numfriends) return 0; - if (length >= (MAX_DATA_SIZE - 4) || friendlist[friendnumber].status != FRIEND_ONLINE) + if (length >= (MAX_DATA_SIZE - sizeof(theid)) || friendlist[friendnumber].status != FRIEND_ONLINE) /* this does not mean the maximum message length is MAX_DATA_SIZE - 1, it is actually 17 bytes less. */ return 0; uint8_t temp[MAX_DATA_SIZE]; temp[0] = PACKET_ID_MESSAGE; - temp[1] = theid >> 24; - temp[2] = theid >> 16; - temp[3] = theid >> 8; - temp[4] = theid; - memcpy(temp + 5, message, length); - return write_cryptpacket(friendlist[friendnumber].crypt_connection_id, temp, length + 5); + theid = htonl(theid); + memcpy(temp + 1, &theid, sizeof(theid)); + memcpy(temp + 1 + sizeof(theid), message, length); + return write_cryptpacket(friendlist[friendnumber].crypt_connection_id, temp, length + 1 + sizeof(theid)); } /* send a name packet to friendnumber @@ -397,7 +395,7 @@ static void set_friend_userstatus_kind(int friendnumber, USERSTATUS_KIND k) /* Sets whether we send read receipts for friendnumber. */ void m_set_sends_receipts(int friendnumber, int yesno) { - if (yesno < 0 || yesno > 1) + if (yesno != 0 || yesno != 1) return; if (friendnumber >= numfriends || friendnumber < 0) return; @@ -549,9 +547,11 @@ static void doFriends(void) break; } case PACKET_ID_RECEIPT: { - if (len < 5) + uint32_t msgid; + if (len < 1 + sizeof(msgid)) break; - uint32_t msgid = (temp[1] << 24) | (temp[2] << 16) | (temp[3] << 8) | temp[4]; + memcpy(&msgid, temp + 1, sizeof(msgid)); + msgid = ntohl(msgid); if (read_receipt_isset) (*read_receipt)(i, msgid); break;