#include #include #include #include #include static bool write_char(RdbClient *self, char out) { if (self->len >= RDB_CLIENT_BUFLEN) { return false; } if (out == '#' || out == '$' || out == '}' || out == '*') { self->full_buf[self->len++] = '}'; self->chk += '}'; if (self->len >= RDB_CLIENT_BUFLEN) { return false; } out ^= 0x20; } self->full_buf[self->len++] = out; self->chk += out; return true; } bool char_to_hex_digit(char in, char *out) { if (in & 0xf0) { return false; } in &= 0x0f; if (in > 9) { *out = 'a' + in - 10; } else { *out = '0' + in; } return true; } bool hex_digit_to_char(char in, char *out) { if (in >= '0' && in <= '9') { *out = in - '0'; return true; } if (in >= 'a' && in <= 'f') { *out = in - 'a' + 10; return true; } if (in >= 'A' && in <= 'F') { *out = in - 'A' + 10; return true; } return false; } bool char_to_hex_digits(char in, char *hi, char *lo) { return char_to_hex_digit((in & 0xf0) >> 4, hi) && char_to_hex_digit(in & 0x0f, lo); } void rdb_client_init(RdbClient *self, int connfd) { self->connfd = connfd; self->len = 0; self->chk = 0; self->should_ack = true; } #define BUFFER_LEN 8096 typedef struct Buffer { char buf[BUFFER_LEN]; size_t len; size_t index; } Buffer; static Buffer INBUF = { .len = 0, .index = 0, }; static ssize_t read_next_char(int connfd, char *in) { if (INBUF.index >= INBUF.len) { ssize_t inlen = read(connfd, INBUF.buf, BUFFER_LEN); if (inlen < 1) { // either we got an error (-1) or the connection closed (0) return inlen; } INBUF.index = 0; INBUF.len = inlen; } *in = INBUF.buf[INBUF.index++]; return 1; } ssize_t rdb_client_read(RdbClient *self, char *buf, size_t len) { // read any acknowledgements and continue char in; do { ssize_t res = read_next_char(self->connfd, &in); if (res < 1) return res; } while (in == '+'); if (in == '\x03') { // interrupt from the server *buf = in; return 1; } if (in == '-') { // we don't handle resending right now return -1; } // now, expect to be at the start of a packet if (in != '$') { fprintf(stderr, "unexpected packet start \"%c\"", in); return -1; } size_t outlen = 0; char chk = 0; while (1) { ssize_t res = read_next_char(self->connfd, &in); if (res < 1) return res; if (in == '#') { // end of packet, checksum next break; } if (outlen >= len) { // ran out of room in the buffer fprintf(stderr, "packet too big for buffer\n"); return -1; } if (in == '}') { // escape sequence chk += in; res = read_next_char(self->connfd, &in); if (res < 1) return res; chk += in; buf[outlen++] = in ^ 0x20; } else { chk += in; buf[outlen++] = in; } }; // validate the checksum char hi, lo; ssize_t res = read_next_char(self->connfd, &in); if (res < 1) return res; if (!hex_digit_to_char(in, &hi)) return -1; res = read_next_char(self->connfd, &in); if (res < 1) return res; if (!hex_digit_to_char(in, &lo)) return -1; char real_chk = (hi << 4) | lo; if (real_chk != chk) { fprintf(stderr, "invalid checksum\n"); return -1; } return outlen; } void rdb_client_begin_packet(RdbClient *self) { self->len = 0; self->chk = 0; if (self->should_ack) { self->full_buf[self->len++] = '+'; } self->full_buf[self->len++] = '$'; } bool rdb_client_write_str(RdbClient *self, const char *str) { size_t len = strlen(str); for (size_t i = 0; i < len; ++i) { if (!write_char(self, str[i])) { return false; } } return true; } bool rdb_client_write_str_hex(RdbClient *self, const char *str) { size_t len = strlen(str); for (size_t i = 0; i < len; ++i) { char hi, lo; if (!char_to_hex_digits(str[i], &hi, &lo) || !write_char(self, hi) || !write_char(self, lo)) { return false; } } return true; } bool rdb_client_write_i8_hex(RdbClient *self, uint8_t value) { char hi, lo; return char_to_hex_digits(value, &hi, &lo) && write_char(self, hi) && write_char(self, lo); } bool rdb_client_write_i32_hex(RdbClient *self, uint32_t value) { return rdb_client_write_i8_hex(self, (uint8_t) value) && rdb_client_write_i8_hex(self, (uint8_t) (value >> 8)) && rdb_client_write_i8_hex(self, (uint8_t) (value >> 16)) && rdb_client_write_i8_hex(self, (uint8_t) (value >> 24)); } int rdb_client_send_packet(RdbClient *self) { if (self->len + 3 > RDB_CLIENT_BUFLEN) { return -1; } self->full_buf[self->len++] = '#'; char hi, lo; if (!char_to_hex_digits(self->chk, &hi, &lo)) { return -1; } self->full_buf[self->len++] = hi; self->full_buf[self->len++] = lo; printf("sending command \"%.*s\" %d\n", (int) self->len, self->full_buf, (int)self->len); ssize_t rwrite = write(self->connfd, self->full_buf, self->len); if (rwrite == -1) { return -1; } return 0; }