#include #include #include #include #include #include #include #if 0 #include #define DEBUG(...) fprintf(stderr, "[znc2] " __VA_ARGS__) #else #define DEBUG(...) #endif struct scanner { const char *buf; size_t len; size_t cur; // cursor }; enum event_kind { PARSEERROR = 0, JOIN = 1, PART = 2, QUIT = 3, RENICK = 4, TALK = 5, NOTICE = 6, ACT = 7, KICK = 8, MODE = 9, TOPIC = 10, }; #pragma pack(1) struct event { uint8_t H, M, S; uint8_t kind; // event_kind uint32_t ptr1, ptr2, ptr3; uint16_t len1, len2, len3; }; static inline bool isdigit_char(char c) { return c >= '0' && c <= '9'; } // Exists to avoid UB by strict aliasing rule static inline uint32_t load_uint32(const char *ptr) { uint32_t val; memcpy(&val, ptr, 4); return val; } // Exists to avoid UB by strict aliasing rule static inline uint64_t load_uint64(const char *ptr) { uint64_t val; memcpy(&val, ptr, 8); return val; } // parses "[HH:MM:SS] " including the trailing space static bool parse_hms(struct scanner *s, struct event *dest) { const char *const loc = &s->buf[s->cur]; const size_t len = s->len - s->cur; if (len < 11) return false; if (loc[0] != '[' || loc[3] != ':' || loc[6] != ':' || loc[9] != ']' || loc[10] != ' ') return false; if (!isdigit(loc[1]) || !isdigit(loc[2]) || !isdigit(loc[4]) || !isdigit(loc[5]) || !isdigit(loc[7]) || !isdigit(loc[8])) return false; dest->H = 10 * (loc[1] - '0') + loc[2] - '0'; dest->M = 10 * (loc[4] - '0') + loc[5] - '0'; dest->S = 10 * (loc[7] - '0') + loc[8] - '0'; s->cur += 11; return true; } static inline bool eat_bytes(struct scanner *s, const char *tok, size_t toklen) { const char *const loc = &s->buf[s->cur]; const size_t len = s->len - s->cur; if (len >= toklen && memcmp(loc, tok, toklen) == 0) { s->cur += toklen; return true; } return false; } #define EAT_BYTES(scanner_, tok_) eat_bytes(scanner_, tok_, strlen(tok_)) static void skip_until(struct scanner *s, char delim) { const char *const buf = s->buf; const size_t len = s->len; const size_t cur = s->cur; const char *p = memchr(buf + cur, delim, len - cur); if (p == NULL) s->cur = len; else s->cur = p - buf; } static bool parse_nick(struct scanner *s, uint32_t *destptr, uint16_t *destlen) { // Delimiter bytes: // \t \n \v \r SP < > // 09 0a 0b 0d 20 3c 3e // // None of the delimiter bytes have the bit 0x40 set, and the majority of // IRC nicks consist entirely of bytes with 0x40 set (which include // a-zA-Z[]_`). "Guest12" style nicks do have a numeric suffix, but since the // fallback loop is at the end, most of it is done with the fast check anyway. const char *const buf = s->buf; const size_t len = s->len; const size_t startcur = s->cur; size_t cur = startcur; while (cur < len && (buf[cur] & 0x40) == 0x40) cur++; while (cur < len && buf[cur] != '\t' && buf[cur] != '\n' && buf[cur] != '\v' && buf[cur] != '\r' && buf[cur] != ' ' && buf[cur] != '<' && buf[cur] != '>') cur++; if (cur == startcur) return false; if ((uint16_t)(cur - startcur) != cur - startcur) return false; *destptr = startcur; *destlen = cur - startcur; s->cur = cur; return true; } static bool parse_inside_parens(struct scanner *s, uint32_t *destptr, uint16_t *destlen) { const char *const buf = s->buf; const size_t len = s->len; size_t cur = s->cur; if (cur + 1 >= len) return false; if (buf[cur] != '(') return false; cur++; const size_t startcur = cur; if (len - cur < 16) goto tail_loop; while (cur + 16 <= len) { const __m128i vec = _mm_loadu_si128((const __m128i*)(buf + cur)); if (_mm_movemask_epi8(_mm_cmpeq_epi8(vec, _mm_set1_epi8(')' ))) != 0) break; if (_mm_movemask_epi8(_mm_cmpeq_epi8(vec, _mm_set1_epi8('\n'))) != 0) return false; cur += 16; } tail_loop: while (cur < len) { if (buf[cur] == ')') goto close_found; if (buf[cur] == '\n') return false; cur++; } close_found: if ((uint16_t)(cur - startcur) != cur - startcur) return false; *destptr = startcur; *destlen = cur - startcur; s->cur = cur + 1; // skip the closing paren return true; } static bool parse_enclose_tail( struct scanner *s, uint32_t *destptr, uint16_t *destlen, char leftdelim, char rightdelim ) { const char *const buf = s->buf; const size_t len = s->len; size_t cur = s->cur; if (cur + 1 >= len) return false; if (buf[cur] != leftdelim) return false; cur++; const size_t startcur = cur; const char *p = memchr(buf + cur, '\n', len - cur); if (p == NULL) cur = len; else cur = p - buf; if (cur == startcur) return false; const size_t rightdelim_cur = cur - 1; if (buf[rightdelim_cur] != rightdelim) return false; if ((uint16_t)(rightdelim_cur - startcur) != rightdelim_cur - startcur) return false; *destptr = startcur; *destlen = rightdelim_cur - startcur; s->cur = cur; // skip the rightdelim, point to the newline return true; } static bool parse_remaining(struct scanner *s, uint32_t *destptr, uint16_t *destlen) { const char *const buf = s->buf; const size_t len = s->len; size_t cur = s->cur; if (cur >= len) return false; const size_t startcur = cur; const char *p = memchr(buf + cur, '\n', len - cur); if (p == NULL) return false; cur = p - buf; if ((uint16_t)(cur - startcur) != cur - startcur) return false; *destptr = startcur; *destlen = cur - startcur; s->cur = cur; return true; } // Assumes the "Joins: " etc. prefix has already been consumed static bool parse_useract(struct scanner *s, struct event *dest, bool withtail) { if (!parse_nick(s, &dest->ptr1, &dest->len1)) return false; DEBUG(" parse_useract nick\n"); if (!EAT_BYTES(s, " ")) return false; DEBUG(" parse_useract space\n"); if (!parse_inside_parens(s, &dest->ptr2, &dest->len2)) return false; DEBUG(" parse_useract parens\n"); if (withtail) { if (!EAT_BYTES(s, " ")) return false; DEBUG(" parse_useract space2\n"); if (!parse_enclose_tail(s, &dest->ptr3, &dest->len3, '(', ')')) return false; DEBUG(" parse_useract tail\n"); } return true; } static bool parse_eventdata(struct scanner *s, struct event *dest) { DEBUG(" parse_eventdata cur=%zu\n", s->cur); if (EAT_BYTES(s, "*** ")) { if (EAT_BYTES(s, "Joins: ")) { dest->kind = JOIN; if (!parse_useract(s, dest, false)) return false; // znc prints nickserv account name after parentheses since znc 1.9.0; skip it skip_until(s, '\n'); return true; } if (EAT_BYTES(s, "Parts: ")) { dest->kind = PART; return parse_useract(s, dest, true); } if (EAT_BYTES(s, "Quits: ")) { dest->kind = QUIT; return parse_useract(s, dest, true); } if (!parse_nick(s, &dest->ptr1, &dest->len1)) return false; if (EAT_BYTES(s, " is now known as ")) { dest->kind = RENICK; return parse_nick(s, &dest->ptr2, &dest->len2); } if (EAT_BYTES(s, " sets mode: ")) { dest->kind = MODE; return parse_remaining(s, &dest->ptr2, &dest->len2); } if (EAT_BYTES(s, " was kicked by ")) { dest->kind = KICK; if (!parse_nick(s, &dest->ptr2, &dest->len2)) return false; if (!EAT_BYTES(s, " ")) return false; return parse_enclose_tail(s, &dest->ptr3, &dest->len3, '(', ')'); } if (EAT_BYTES(s, " changes topic to ")) { dest->kind = TOPIC; return parse_enclose_tail(s, &dest->ptr2, &dest->len2, '\'', '\''); } return false; } if (EAT_BYTES(s, "<")) { // DEBUG(" Ate '<'\n"); dest->kind = TALK; if (!parse_nick(s, &dest->ptr1, &dest->len1)) return false; // DEBUG(" Nick %u %hu\n", dest->ptr1, dest->len1); if (!EAT_BYTES(s, "> ")) return false; // DEBUG(" Ate '> '\n"); return parse_remaining(s, &dest->ptr2, &dest->len2); } if (EAT_BYTES(s, "-")) { dest->kind = NOTICE; uint32_t ptr1; uint16_t len1; if (!parse_nick(s, &ptr1, &len1)) return false; if (len1 > 0 && s->buf[ptr1 + len1 - 1] == '-') len1--; if (len1 == 0) return false; dest->ptr1 = ptr1; dest->len1 = len1; if (!EAT_BYTES(s, " ")) return false; return parse_remaining(s, &dest->ptr2, &dest->len2); } if (EAT_BYTES(s, "* ")) { dest->kind = ACT; if (!parse_nick(s, &dest->ptr1, &dest->len1)) return false; if (!EAT_BYTES(s, " ")) return false; return parse_remaining(s, &dest->ptr2, &dest->len2); } return false; } static size_t tirclogv_parse_znc_loop( uint8_t *events_, size_t max_events, const char *buf, size_t len ) { // offsets are stored in a uint32_t, so the buffer can't be too large if ((uint32_t)len != len) return 0; assert(sizeof(struct event) == 22); struct event *events = (struct event*)events_; struct event dummyevent; struct scanner s = (struct scanner){.buf = buf, .len = len, .cur = 0}; size_t numev = 0; while (s.cur < s.len && numev < max_events) { DEBUG("cur=%zu len=%zu\n", s.cur, s.len); if (!parse_hms(&s, events ? &events[numev] : &dummyevent)) goto parseerror; DEBUG(" hms\n"); if (!parse_eventdata(&s, events ? &events[numev] : &dummyevent)) goto parseerror; DEBUG(" evdata cur=%zu len=%zu buf[cur]=%u\n", s.cur, s.len, (unsigned)s.buf[s.cur]); if (s.cur != s.len && s.buf[s.cur] != '\n') goto parseerror; s.cur++; // skip the newline numev++; continue; parseerror: assert(PARSEERROR == 0); if (events) memset(&events[numev], 0, sizeof(struct event)); numev++; const char *p = memchr(buf + s.cur, '\n', len - s.cur); if (p == NULL) break; s.cur += (p + 1) - (buf + s.cur); } DEBUG("ret numev=%zu\n", numev); return numev; } // Assumes there is space for evs_allocated * sizeof(struct event) items in events. size_t tirclogv_parse_znc(uint8_t *events, size_t evs_allocated, const char *buf, size_t len) { return tirclogv_parse_znc_loop(events, evs_allocated, buf, len); } size_t tirclogv_count_lines(const char *buf, size_t len) { size_t numln = 0; if (len == 0) return 0; if (buf[len - 1] == '\n') len--; // we actually count the number of '\n' below const int vecwidth = 16; const int unrollcount = 2; while (len >= vecwidth * unrollcount) { const int iter_size = vecwidth * unrollcount; size_t niters = len / iter_size; if (niters > 256) niters = 256; // make sure we're not overflowing the bytes assert(unrollcount == 2); __m128i acc0, acc1; acc0 = acc1 = _mm_set1_epi8(0); for (size_t i = 0; i < niters; i++) { const __m128i vec0 = _mm_loadu_si128((const __m128i*)(buf + vecwidth * (unrollcount * i + 0))); const __m128i vec1 = _mm_loadu_si128((const __m128i*)(buf + vecwidth * (unrollcount * i + 1))); // since cmpeq returns 0xff/0x00 for true/false, "+= (eq == 0xff)" == "-= eq" // (GCC is able to see that "+= (cmpeq(...) == 0xff)" can be optimised to // "-= cmpeq(...)", but then proceeds to mess things up and generates // bad, quirky code anyway, so we do the optimisation manually) acc0 = _mm_sub_epi8(acc0, _mm_cmpeq_epi8(vec0, _mm_set1_epi8('\n'))); acc1 = _mm_sub_epi8(acc1, _mm_cmpeq_epi8(vec1, _mm_set1_epi8('\n'))); } acc0 = _mm_add_epi8(acc0, acc1); uint8_t bytes[vecwidth]; memcpy(bytes, &acc0, vecwidth); #pragma GCC novector // the generated vector code is rather dumb (lots of punpck{l,h}) for (int i = 0; i < vecwidth; i++) numln += bytes[i]; buf += niters * iter_size; len -= niters * iter_size; } #pragma GCC novector for (size_t i = 0; i < len; i++) numln += buf[i] == '\n'; return numln; }