| /* libs/cutils/record_stream.c |
| ** |
| ** Copyright 2006, The Android Open Source Project |
| ** |
| ** Licensed under the Apache License, Version 2.0 (the "License"); |
| ** you may not use this file except in compliance with the License. |
| ** You may obtain a copy of the License at |
| ** |
| ** http://www.apache.org/licenses/LICENSE-2.0 |
| ** |
| ** Unless required by applicable law or agreed to in writing, software |
| ** distributed under the License is distributed on an "AS IS" BASIS, |
| ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| ** See the License for the specific language governing permissions and |
| ** limitations under the License. |
| */ |
| |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <cutils/record_stream.h> |
| #include <string.h> |
| #include <stdint.h> |
| #ifdef HAVE_WINSOCK |
| #include <winsock2.h> /* for ntohl */ |
| #else |
| #include <netinet/in.h> |
| #endif |
| |
| #define HEADER_SIZE 4 |
| |
| struct RecordStream { |
| int fd; |
| size_t maxRecordLen; |
| |
| unsigned char *buffer; |
| |
| unsigned char *unconsumed; |
| unsigned char *read_end; |
| unsigned char *buffer_end; |
| }; |
| |
| |
| extern RecordStream *record_stream_new(int fd, size_t maxRecordLen) |
| { |
| RecordStream *ret; |
| |
| assert (maxRecordLen <= 0xffff); |
| |
| ret = (RecordStream *)calloc(1, sizeof(RecordStream)); |
| |
| ret->fd = fd; |
| ret->maxRecordLen = maxRecordLen; |
| ret->buffer = (unsigned char *)malloc (maxRecordLen + HEADER_SIZE); |
| |
| ret->unconsumed = ret->buffer; |
| ret->read_end = ret->buffer; |
| ret->buffer_end = ret->buffer + maxRecordLen + HEADER_SIZE; |
| |
| return ret; |
| } |
| |
| |
| extern void record_stream_free(RecordStream *rs) |
| { |
| free(rs->buffer); |
| free(rs); |
| } |
| |
| |
| /* returns NULL; if there isn't a full record in the buffer */ |
| static unsigned char * getEndOfRecord (unsigned char *p_begin, |
| unsigned char *p_end) |
| { |
| size_t len; |
| unsigned char * p_ret; |
| |
| if (p_end < p_begin + HEADER_SIZE) { |
| return NULL; |
| } |
| |
| //First four bytes are length |
| len = ntohl(*((uint32_t *)p_begin)); |
| |
| p_ret = p_begin + HEADER_SIZE + len; |
| |
| if (p_end < p_ret) { |
| return NULL; |
| } |
| |
| return p_ret; |
| } |
| |
| static void *getNextRecord (RecordStream *p_rs, size_t *p_outRecordLen) |
| { |
| unsigned char *record_start, *record_end; |
| |
| record_end = getEndOfRecord (p_rs->unconsumed, p_rs->read_end); |
| |
| if (record_end != NULL) { |
| /* one full line in the buffer */ |
| record_start = p_rs->unconsumed + HEADER_SIZE; |
| p_rs->unconsumed = record_end; |
| |
| *p_outRecordLen = record_end - record_start; |
| |
| return record_start; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * Reads the next record from stream fd |
| * Records are prefixed by a 16-bit big endian length value |
| * Records may not be larger than maxRecordLen |
| * |
| * Doesn't guard against EINTR |
| * |
| * p_outRecord and p_outRecordLen may not be NULL |
| * |
| * Return 0 on success, -1 on fail |
| * Returns 0 with *p_outRecord set to NULL on end of stream |
| * Returns -1 / errno = EAGAIN if it needs to read again |
| */ |
| int record_stream_get_next (RecordStream *p_rs, void ** p_outRecord, |
| size_t *p_outRecordLen) |
| { |
| void *ret; |
| |
| ssize_t countRead; |
| |
| /* is there one record already in the buffer? */ |
| ret = getNextRecord (p_rs, p_outRecordLen); |
| |
| if (ret != NULL) { |
| *p_outRecord = ret; |
| return 0; |
| } |
| |
| // if the buffer is full and we don't have a full record |
| if (p_rs->unconsumed == p_rs->buffer |
| && p_rs->read_end == p_rs->buffer_end |
| ) { |
| // this should never happen |
| //LOGE("max record length exceeded\n"); |
| assert (0); |
| errno = EFBIG; |
| return -1; |
| } |
| |
| if (p_rs->unconsumed != p_rs->buffer) { |
| // move remainder to the beginning of the buffer |
| size_t toMove; |
| |
| toMove = p_rs->read_end - p_rs->unconsumed; |
| if (toMove) { |
| memmove(p_rs->buffer, p_rs->unconsumed, toMove); |
| } |
| |
| p_rs->read_end = p_rs->buffer + toMove; |
| p_rs->unconsumed = p_rs->buffer; |
| } |
| |
| countRead = read (p_rs->fd, p_rs->read_end, p_rs->buffer_end - p_rs->read_end); |
| |
| if (countRead <= 0) { |
| /* note: end-of-stream drops through here too */ |
| *p_outRecord = NULL; |
| return countRead; |
| } |
| |
| p_rs->read_end += countRead; |
| |
| ret = getNextRecord (p_rs, p_outRecordLen); |
| |
| if (ret == NULL) { |
| /* not enough of a buffer to for a whole command */ |
| errno = EAGAIN; |
| return -1; |
| } |
| |
| *p_outRecord = ret; |
| return 0; |
| } |