libpurple/protocols/gg/lib/tvbuff.c

branch
release-2.x.y
changeset 35617
c9069e0e3c36
child 35627
fd11790cc4d6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/tvbuff.c	Fri Feb 28 17:29:00 2014 +0100
@@ -0,0 +1,601 @@
+/* $Id$ */
+
+/*
+ *  (C) Copyright 2012 Tomek Wasilczyk <www.wasilczyk.pl>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License Version
+ *  2.1 as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  USA.
+ */
+
+/**
+ * \file tvbuff.c
+ *
+ * \brief Bufor wspierający obsługę pakietów typu Type-Value(s)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "tvbuff.h"
+
+#include "internal.h"
+
+struct gg_tvbuff
+{
+	const char *buffer;
+	size_t length;
+	size_t offset;
+	int valid;
+};
+
+/**
+ * \internal Tworzy nową instancję bufora.
+ *
+ * \param buffer Bufor źródłowy; nie może być modyfikowany (w szczególności
+ *        zwalniany) przez cały okres korzystania z jego opakowanej wersji.
+ * \param length Długość bufora źródłowego.
+ *
+ * \return Zaalokowane opakowanie bufora - musi być zwolnione przez free lub
+ *         gg_tvbuff_close.
+ */
+gg_tvbuff_t *gg_tvbuff_new(const char *buffer, size_t length)
+{
+	gg_tvbuff_t *tvb;
+
+	tvb = malloc(sizeof(gg_tvbuff_t));
+	if (tvb == NULL)
+		return NULL;
+	memset(tvb, 0, sizeof(gg_tvbuff_t));
+
+	if (buffer == NULL && length > 0) {
+		gg_debug(GG_DEBUG_ERROR, "// gg_tvbuff_new() "
+			"invalid arguments\n");
+		tvb->valid = 0;
+		return tvb;
+	}
+
+	tvb->buffer = buffer;
+	tvb->length = length;
+	tvb->offset = 0;
+	tvb->valid = 1;
+
+	return tvb;
+}
+
+/**
+ * \internal Zwalnia opakowanie bufora. Przed zwolnieniem sprawdza, czy
+ * przeczytano go do końca.
+ *
+ * \param tvb Bufor.
+ *
+ * \return Wartość różna od 0, jeżeli bufor tuż przed zwolnieniem był oznaczony
+ *         jako prawidłowy
+ */
+int gg_tvbuff_close(gg_tvbuff_t *tvb)
+{
+	int valid;
+
+	gg_tvbuff_expected_eob(tvb);
+	valid = gg_tvbuff_is_valid(tvb);
+	free(tvb);
+
+	return valid;
+}
+
+/**
+ * \internal Sprawdza, czy wszystkie odczyty z bufora były prawidłowe.
+ *
+ * \param tvb Bufor.
+ *
+ * \return Wartość różna od 0, jeżeli wszystkie odczyty były prawidłowe.
+ */
+int gg_tvbuff_is_valid(const gg_tvbuff_t *tvb)
+{
+	if (tvb == NULL)
+		return 0;
+	return tvb->valid;
+}
+
+/**
+ * \internal Zwraca pozostałą do odczytania liczbę bajtów w buforze.
+ *
+ * \param tvb Bufor.
+ *
+ * \return Pozostała liczba bajtów do odczytania.
+ */
+size_t gg_tvbuff_get_remaining(const gg_tvbuff_t *tvb)
+{
+	if (!gg_tvbuff_is_valid(tvb))
+		return 0;
+
+	return tvb->length - tvb->offset;
+}
+
+/**
+ * \internal Sprawdza, czy w buforze pozostała określona liczba bajtów do
+ * odczytania. Jeżeli nie została - oznacza bufor jako nieprawidłowy.
+ *
+ * \param tvb    Bufor.
+ * \param length Ilość bajtów do odczytania.
+ *
+ * \return Wartość różna od 0, jeżeli można odczytać podaną liczbę bajtów.
+ */
+int gg_tvbuff_have_remaining(gg_tvbuff_t *tvb, size_t length)
+{
+	if (!gg_tvbuff_is_valid(tvb))
+		return 0;
+
+	if (gg_tvbuff_get_remaining(tvb) >= length)
+		return 1;
+
+	gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_have_remaining() failed "
+		"(%d < %d)\n", gg_tvbuff_get_remaining(tvb), length);
+	tvb->valid = 0;
+	return 0;
+}
+
+/**
+ * \internal Pomija określoną liczbę bajtów w buforze. Jeżeli w wyniku ich
+ * pominięcia wyjdzie poza zakres, oznacza bufor jako nieprawidłowy.
+ *
+ * \param tvb    Bufor
+ * \param amount Liczba bajtów do pominięcia
+ */
+void gg_tvbuff_skip(gg_tvbuff_t *tvb, size_t amount)
+{
+	if (!gg_tvbuff_is_valid(tvb))
+		return;
+
+	if (tvb->offset + amount > tvb->length) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_skip() failed\n");
+		tvb->valid = 0;
+		return;
+	}
+
+	tvb->offset += amount;
+}
+
+/**
+ * \internal Cofa się o określoną liczbę bajtów w buforze. Jeżeli cofnie przed
+ * pierwszy znak, oznacza bufor jako nieprawidłowy.
+ *
+ * \param tvb    Bufor
+ * \param amount Liczba bajtów do cofnięcia
+ */
+void gg_tvbuff_rewind(gg_tvbuff_t *tvb, size_t amount)
+{
+	if (!gg_tvbuff_is_valid(tvb))
+		return;
+
+	if (tvb->offset < amount) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_rewind() failed\n");
+		tvb->valid = 0;
+		return;
+	}
+
+	tvb->offset -= amount;
+}
+
+/**
+ * \internal Sprawdza, czy pod aktualną pozycją w buforze występuje podana
+ * wartość. Jeżeli tak, przesuwa aktualną pozycję do przodu.
+ *
+ * \param tvb   Bufor.
+ * \param value Wartość do sprawdzenia
+ *
+ * \return Wartość różna od 0, jeżeli znaleziono podaną wartość.
+ */
+int gg_tvbuff_match(gg_tvbuff_t *tvb, uint8_t value)
+{
+	if (!gg_tvbuff_is_valid(tvb))
+		return 0;
+
+	if (!gg_tvbuff_have_remaining(tvb, 1)) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_match() failed\n");
+		return 0;
+	}
+
+	if (tvb->buffer[tvb->offset] != value)
+		return 0;
+
+	tvb->offset++;
+	return 1;
+}
+
+/**
+ * \internal Odczytuje z bufora liczbę 8-bitową.
+ *
+ * \param tvb Bufor
+ *
+ * \return Odczytana liczba
+ */
+uint8_t gg_tvbuff_read_uint8(gg_tvbuff_t *tvb)
+{
+	if (!gg_tvbuff_is_valid(tvb))
+		return 0;
+
+	if (!gg_tvbuff_have_remaining(tvb, 1)) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_uint8() "
+			"failed at %d\n", tvb->offset);
+		return 0;
+	}
+
+	return tvb->buffer[tvb->offset++];
+}
+
+/**
+ * \internal Odczytuje z bufora liczbę 32-bitową.
+ *
+ * \param tvb Bufor
+ *
+ * \return Odczytana liczba
+ */
+uint32_t gg_tvbuff_read_uint32(gg_tvbuff_t *tvb)
+{
+	uint32_t val;
+
+	if (!gg_tvbuff_is_valid(tvb))
+		return 0;
+
+	if (!gg_tvbuff_have_remaining(tvb, 4)) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_uint32() "
+			"failed at %d\n", tvb->offset);
+		return 0;
+	}
+
+	memcpy(&val, tvb->buffer + tvb->offset, 4);
+	tvb->offset += 4;
+
+	return gg_fix32(val);
+}
+
+/**
+ * \internal Odczytuje z bufora liczbę 64-bitową.
+ *
+ * \param tvb Bufor
+ *
+ * \return Odczytana liczba
+ */
+uint64_t gg_tvbuff_read_uint64(gg_tvbuff_t *tvb)
+{
+	uint64_t val;
+
+	if (!gg_tvbuff_is_valid(tvb))
+		return 0;
+
+	if (!gg_tvbuff_have_remaining(tvb, 8)) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_uint64() "
+			"failed at %d\n", tvb->offset);
+		return 0;
+	}
+
+	memcpy(&val, tvb->buffer + tvb->offset, 8);
+	tvb->offset += 8;
+
+	return gg_fix64(val);
+}
+
+/**
+ * \internal Odczytuje z bufora skompresowaną liczbę całkowitą.
+ * Liczba taka może być zapisana w buforze na 1-9 bajtach, w zależności
+ * od jej wartości.
+ *
+ * Skompresowana liczba jest zapisywana od najmłodszego bajtu do najstarszego
+ * niezerowego. W każdym bajcie zapisuje się bit sterujący (równy 0, jeżeli jest
+ * to ostatni bajt do przeczytania, lub 1 w p.p.) oraz 7 kolejnych bitów z
+ * kompresowanej liczby.
+ *
+ * Przykładowo, liczby mniejsze od 128 (1000.0000b) są zapisywane dokładnie tak,
+ * jak uint8_t; a np. 12345 (0011.0000.0011.1001b) zostanie zapisana jako 0x60B9
+ * (0110.0000.1011.1001b).
+ *
+ * \param tvb Bufor.
+ *
+ * \return Odczytana liczba.
+ */
+uint64_t gg_tvbuff_read_packed_uint(gg_tvbuff_t *tvb)
+{
+	uint64_t val = 0;
+	int i, val_len = 0;
+
+	if (!gg_tvbuff_is_valid(tvb))
+		return 0;
+
+	while (gg_tvbuff_have_remaining(tvb, 1)) {
+		val_len++;
+		if (!(gg_tvbuff_read_uint8(tvb) & 0x80))
+			break;
+	}
+
+	if (!gg_tvbuff_is_valid(tvb)) {
+		gg_debug(GG_DEBUG_WARNING,
+			"// gg_tvbuff_read_packed_uint() failed\n");
+		return 0;
+	}
+
+	if (val_len > 9) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_packed_uint() "
+			"packed uint size too big: %d\n", val_len);
+		tvb->valid = 0;
+		return 0;
+	}
+
+	for (i = 1; i <= val_len; i++) {
+		uint64_t old_val = val;
+		val <<= 7;
+		if (old_val != (val >> 7)) {
+			gg_debug(GG_DEBUG_WARNING,
+				"// gg_tvbuff_read_packed_uint() overflow\n");
+			tvb->valid = 0;
+			return 0;
+		}
+		val |= (uint8_t)(tvb->buffer[tvb->offset - i] & ~0x80);
+	}
+
+	return val;
+}
+
+/**
+ * \internal Odczytuje z bufora podciąg bez kopiowania danych.
+ *
+ * \param tvb    Bufor źródłowy
+ * \param length Ilość bajtów do odczytania
+ *
+ * \return Wskaźnik na początek odczytanych danych, lub NULL w przypadku
+ *         niepowodzenia
+ */
+const char *gg_tvbuff_read_buff(gg_tvbuff_t *tvb, size_t length)
+{
+	const char *buff;
+
+	if (!gg_tvbuff_is_valid(tvb))
+		return NULL;
+
+	if (!gg_tvbuff_have_remaining(tvb, length)) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_get_buff() "
+			"failed at %d:%d\n", tvb->offset, length);
+		return NULL;
+	}
+
+	buff = tvb->buffer + tvb->offset;
+	tvb->offset += length;
+	return buff;
+}
+
+/**
+ * \internal Odczytuje z bufora podciąg kopiując go do nowego obszaru pamięci.
+ *
+ * \param tvb    Bufor źródłowy
+ * \param buffer Bufor docelowy
+ * \param length Ilość bajtów do odczytania
+ */
+void gg_tvbuff_read_buff_cpy(gg_tvbuff_t *tvb, char *buffer, size_t length)
+{
+	if (!gg_tvbuff_is_valid(tvb))
+		return;
+
+	if (!gg_tvbuff_have_remaining(tvb, length)) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_buff() "
+			"failed at %d:%d\n", tvb->offset, length);
+		return;
+	}
+
+	if (buffer == NULL && length > 0) {
+		gg_debug(GG_DEBUG_ERROR, "// gg_tvbuff_new() "
+			"invalid arguments\n");
+		tvb->valid = 0;
+		return;
+	}
+
+	memcpy(buffer, tvb->buffer + tvb->offset, length);
+	tvb->offset += length;
+}
+
+/**
+ * \internal Odczytuje z bufora ciąg tekstowy (mogący zawierać dowolne znaki,
+ * również \0) bez kopiowania danych.
+ *
+ * \param tvb    Bufor źródłowy
+ * \param length Zmienna, do której zostanie zapisana długość odczytanego ciągu
+ *
+ * \return Wskaźnik na początek odczytanych danych, lub NULL w przypadku
+ *         niepowodzenia
+ */
+const char *gg_tvbuff_read_str(gg_tvbuff_t *tvb, size_t *length)
+{
+	size_t offset;
+	uint32_t str_len;
+	const char *str;
+
+	if (!gg_tvbuff_is_valid(tvb))
+		return NULL;
+
+	offset = tvb->offset;
+	str_len = gg_tvbuff_read_packed_uint(tvb);
+	if (!gg_tvbuff_is_valid(tvb) ||
+		!gg_tvbuff_have_remaining(tvb, str_len)) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_str() failed at "
+			"%d:%d\n", offset, str_len);
+		return NULL;
+	}
+
+	str = gg_tvbuff_read_buff(tvb, str_len);
+
+	if (!gg_tvbuff_is_valid(tvb))
+		return NULL;
+
+	if (length != NULL)
+		*length = str_len;
+	if (str_len == 0)
+		return NULL;
+	return str;
+}
+
+/**
+ * \internal Odczytuje z bufora ciąg tekstowy (mogący zawierać dowolne znaki,
+ * również \0) kopiując go do nowego obszaru pamięci. Zwrócony ciąg będzie
+ * zawsze zakończony znakiem \0.
+ *
+ * \param tvb Bufor źródłowy
+ * \param dst Zmienna, do której zostanie zapisany wskaźnik na odczytany ciąg.
+ *            Po użyciu, blok ten powinien zostać zwolniony za pomocą \c free()
+ *
+ * \return Wskaźnik na początek odczytanych danych, lub NULL w przypadku
+ *         niepowodzenia
+ */
+void gg_tvbuff_read_str_dup(gg_tvbuff_t *tvb, char **dst)
+{
+	size_t offset;
+	uint32_t str_len;
+	char *str;
+
+	if (!gg_tvbuff_is_valid(tvb))
+		return;
+
+	offset = tvb->offset;
+	str_len = gg_tvbuff_read_packed_uint(tvb);
+	if (!gg_tvbuff_is_valid(tvb) ||
+		!gg_tvbuff_have_remaining(tvb, str_len)) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_str_dup() failed "
+			"at %d:%d\n", offset, str_len);
+		return;
+	}
+
+	str = malloc(str_len + 1);
+	if (str == NULL) {
+		gg_debug(GG_DEBUG_ERROR, "// gg_tvbuff_read_str_dup() "
+			"not enough free memory: %d + 1\n", str_len);
+		tvb->valid = 0;
+		return;
+	}
+
+	gg_tvbuff_read_buff_cpy(tvb, str, str_len);
+	str[str_len] = '\0';
+
+	if (*dst != NULL) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_str_dup() "
+			"destination already filled, freeing it...\n");
+		free(*dst);
+	}
+	*dst = str;
+}
+
+/**
+ * \internal Odczytuje z bufora identyfikator użytkownika.
+ *
+ * \param tvb Bufor
+ *
+ * \return Identyfikator użytkownika, lub 0 w przypadku niepowodzenia
+ */
+uin_t gg_tvbuff_read_uin(gg_tvbuff_t *tvb)
+{
+	uin_t uin = 0;
+	uint32_t uin_len, full_len;
+	uint8_t uin_type;
+	const char *raw;
+
+	if (!gg_tvbuff_is_valid(tvb))
+		return 0;
+
+	full_len = gg_tvbuff_read_packed_uint(tvb);
+	uin_type = gg_tvbuff_read_uint8(tvb);
+	uin_len = gg_tvbuff_read_uint8(tvb);
+
+	if (!gg_tvbuff_is_valid(tvb))
+		return 0;
+
+	if (full_len != uin_len + 2 ||
+		uin_type != 0 ||
+		uin_len > 10) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_uin() failed (1)\n");
+		tvb->valid = 0;
+		return 0;
+	}
+
+	raw = gg_tvbuff_read_buff(tvb, uin_len);
+	if (raw)
+		uin = gg_str_to_uin(raw, uin_len);
+
+	if (uin == 0) {
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_uin() failed (2)\n");
+		tvb->valid = 0;
+		return 0;
+	}
+
+	return uin;
+}
+
+/**
+ * \internal Odczytuje z bufora liczbę 8-bitową i porównuje z podaną. Jeżeli te
+ * się różnią, zostaje wygenerowane ostrzeżenie.
+ *
+ * \param tvb   Bufor
+ * \param value Oczekiwana wartość
+ */
+void gg_tvbuff_expected_uint8(gg_tvbuff_t *tvb, uint8_t value)
+{
+	uint8_t got;
+	size_t offset;
+
+	offset = tvb->offset;
+	got = gg_tvbuff_read_uint8(tvb);
+	if (!gg_tvbuff_is_valid(tvb))
+		return;
+
+	if (got != value)
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_expected_uint8() "
+			"expected %#02x, but %#02x found at %d\n",
+			value, got, offset);
+}
+
+/**
+ * \internal Odczytuje z bufora liczbę 32-bitową i porównuje z podaną. Jeżeli te
+ * się różnią, zostaje wygenerowane ostrzeżenie.
+ *
+ * \param tvb   Bufor
+ * \param value Oczekiwana wartość
+ */
+void gg_tvbuff_expected_uint32(gg_tvbuff_t *tvb, uint32_t value)
+{
+	uint32_t got;
+	size_t offset;
+
+	offset = tvb->offset;
+	got = gg_tvbuff_read_uint32(tvb);
+	if (!gg_tvbuff_is_valid(tvb))
+		return;
+
+	if (got != value)
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_expected_uint32() "
+			"expected %#08x, but %#08x found at %d\n",
+			value, got, offset);
+}
+
+/**
+ * \internal Oczekuje końca bufora. Jeżeli w buforze są jeszcze dane do
+ * przeczytania, generuje ostrzeżenie.
+ *
+ * \param tvb Bufor.
+ */
+void gg_tvbuff_expected_eob(const gg_tvbuff_t *tvb)
+{
+	if (!gg_tvbuff_is_valid(tvb))
+		return;
+
+	if (gg_tvbuff_get_remaining(tvb) != 0)
+		gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_expected_eob() "
+			"unexpected %d bytes, first=%#02x\n",
+			gg_tvbuff_get_remaining(tvb),
+			tvb->buffer[tvb->offset]);
+}

mercurial