Browse Source
- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll - stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close - utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP), add deferred drain in both Phase J and L - stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39, exchange ed25519_pubkey in both handshake directions - etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb - chat_sync.c: fix lk->conn NULL dereference for TCP links - test_stcp.c: update stcp_server_create/stcp_client_connect callsites - tools/chat_tcp_test: change transport udp→tcptopo_upd
1272 changed files with 968388 additions and 1 deletions
@ -0,0 +1,122 @@
|
||||
# Build directory (out-of-tree build) |
||||
/build/ |
||||
build-win/ |
||||
tools/chatgui/build/ |
||||
tools/chatgui/ffmpeg/ffmpeg_build/ |
||||
|
||||
# Build outputs |
||||
*.o |
||||
*.lo |
||||
*.la |
||||
*.a |
||||
*.so |
||||
*.so.* |
||||
utun |
||||
test_serialize |
||||
linux-x64-deb |
||||
|
||||
# Temp files |
||||
*.c1 |
||||
*.h2 |
||||
1 |
||||
|
||||
# HTML files |
||||
index.html |
||||
|
||||
# binary files |
||||
*.exe |
||||
|
||||
# Autotools |
||||
Makefile |
||||
Makefile.in |
||||
/aclocal.m4 |
||||
/autom4te.cache/ |
||||
/autoscan.log |
||||
/autoscan-*.log |
||||
/compile |
||||
/config.cache |
||||
/config.guess |
||||
/config.h |
||||
/config.h.in |
||||
/config.log |
||||
/config.status |
||||
/config.sub |
||||
/configure |
||||
/configure.scan |
||||
/depcomp |
||||
/install-sh |
||||
/libtool |
||||
/missing |
||||
/stamp-h1 |
||||
/build-aux/ |
||||
/m4/ |
||||
.deps/ |
||||
.dirstamp |
||||
|
||||
# Editor files |
||||
*~ |
||||
*.swp |
||||
*.swo |
||||
.vscode/ |
||||
.idea/ |
||||
.cache/ |
||||
|
||||
# Debug outputs |
||||
/core |
||||
vgcore.* |
||||
callgrind.out.* |
||||
|
||||
# Test outputs |
||||
tests/*.log |
||||
tests/*.trs |
||||
tests/logs/ |
||||
|
||||
# Mass update logs |
||||
logs/ |
||||
|
||||
# Test binaries (executables without extension), but track source/config files |
||||
tests/test_* |
||||
!tests/test_*.c |
||||
!tests/test_*.conf |
||||
!tests/test_*.json |
||||
tests/bench_* |
||||
!tests/bench_*.c |
||||
|
||||
# All log files |
||||
*.log |
||||
utun.log* |
||||
|
||||
# logreceiver artifacts |
||||
tools/logreceiver/logreceiver_output.log* |
||||
tools/logreceiver/build/ |
||||
|
||||
# Local configs |
||||
*.cfg |
||||
!utun_test.cfg |
||||
utun.conf |
||||
!utun.conf.sample |
||||
|
||||
# IDE/Editor directories |
||||
.opencode/ |
||||
|
||||
# Tool binaries |
||||
tools/bping/bping |
||||
tools/proxy/udp_proxy |
||||
|
||||
# Reference projects (not part of utun) |
||||
tools/tdesktop-dev/ |
||||
|
||||
# Debug/temp artifacts |
||||
*.bak |
||||
*.debug |
||||
*.csv |
||||
__pycache__/ |
||||
|
||||
|
||||
# MSVC temp files |
||||
cl |
||||
build_win.log |
||||
/wintun.dll |
||||
src/lwip_orig/ |
||||
lib/timeout/ |
||||
utun.pid |
||||
@ -0,0 +1,53 @@
|
||||
Архитектура проекта |
||||
|
||||
Общая идея - децентрализованный модуль коммуникации, основанный на группах создаваемых самими пользователями, который включает модули раширения такие как: |
||||
- групповой чат с поддержкай медиа и голосовых сообщений (референс - телега) |
||||
- расширение чата - рация (референс - zello) |
||||
- расширение медиа - realtime CDN / трансляции |
||||
|
||||
|
||||
1. Модуль подключения p2p (ETCP/STCP) |
||||
Позволяет подключиться двум узлам друг к другу. Весь трафик шифруется. Для подключения нужен публичный ключ удаленного узла. |
||||
Позволяет использовать несколько линков до узла параллельно и динамически их переключать (агрегация/балансировка нагрузки/failover) |
||||
|
||||
2. Логические группы узлов (topo_group) |
||||
Один сервис может работать с несколькими группами. |
||||
Группа - это изолированый от других групп список узлов, между которыми выстраивается диначическая маршрутизация и могут быть реализованы дополнительные сервисы. |
||||
Для чата сейчас реализовано: |
||||
- суперузлы - узлы, обеспечивающие связь между узлами и главные хранители индексов медиафайлов |
||||
- медиа-узлы - узлы, хранящие медиафайлы (помимо клиентов у которых есть загруенные копии файлов) |
||||
|
||||
3. Автоматический роутинг. |
||||
При подключении узла к узлу группы активируется BGP-подобный роутинг. |
||||
Тоесть узлы обмениваются информацией о соседях и каждый узел выстраивает карту связности со всеми узлами сети в этой группе через промежуточные узлы. |
||||
Эта связь используется только для сигнального трафика, не для передачи медиафайлов. |
||||
Если какой-то узел отвалился и в результате появились недоступные узлы то каждый узел который был связан напрямую с отвалившимся, пробует подключить отвалившийся сегмент сети через другие узлы. |
||||
Таким образом, если в сети остаётся хотябы один узел с публичными адресами - связь будет восстановлена. |
||||
|
||||
4. Чат, мемберы и медиахранилище |
||||
При создании группы создаётся приватный-публичный ключ группы. Им подписываются действия суперадминов - можно задавать админов группы, суперузлы, медиаузлы итд. |
||||
Для каждой группы есть в базе: |
||||
- список мемберов (участников группы, со своими публичными ключами и адресами-кандидатами для подклчюения), и их тип который может задавать владелец группы (все сообщения подписываются) |
||||
мемберы синхронизируются между узлами используя merkle-tree сравнение таблиц. |
||||
- список сообщений чата. append-only таблица. синхронизируется только хвост - находим первое расхождение и добавляем отсутствующие записи соседей. |
||||
- список медиаблоков. |
||||
Таблица в которой указано какие узлы хранят какие медиафрагменты. |
||||
И какие мадиафрагменты скачивают. |
||||
Позволяет вытраивать цеочку раздачи контента. например: |
||||
- владелец опубликовал медиафайл порезанный на 10 кусков. |
||||
Далее 100 человек в чате увидели сообщение и захотели скачать. |
||||
Обратились к суперузлам с вопросом - где есть блоки. |
||||
Медиаузел отвечает: |
||||
- список узлов у кого есть нужный блок или кто уже качает нужный блок. |
||||
Далее узел который хочет скачать устанавливает оптимальную связь с узлом (напрямую или через оптимального посредника), после - скачивает нужный блок |
||||
Владелец блока имеет лимит количества подклчений. Если лимит исчерпан - он возвращает список ущлв которые скачивают у него контент (Я загружен - качай у следующих по цепочке). |
||||
Таким образом узел желающий скачать контент, дойдёт до незагруженного узла через дерево загрузок и начнёт у него скачивать. Точнее, выберет лучшего по RTT и скачает. |
||||
Подобным образом можно выстраивать раздачу Live контента, оптимизируя связность узлов по RTT. |
||||
|
||||
todo: оптимизация цепочки раздачи. |
||||
Допустим есть картина: |
||||
A (стример) -> B (посредник) -> C (потребитель) |
||||
[msk] 50ms [мухосранск] 50ms [msk] |
||||
Есть узел D(msk). у которого пинги: A-D: 5ms, B-D 50ms, C-D 5ms. |
||||
Тоесть узел заменив B может время цепочки A-C снизить с 100ms до 10ms. |
||||
Он предлагает после замеров RTT свою кандидатуру. наступает фаза probe - сравниваем несколько секунд RTT и объем у старого и нового узла. И принимаем решение - замена или отмена. |
||||
@ -0,0 +1,54 @@
|
||||
# Сборка uTun под OpenWRT |
||||
|
||||
## Быстрый старт |
||||
|
||||
```bash |
||||
cd ~/utun2 |
||||
./build_openwrt.sh # ASUS RT-AX53U (по умолчанию) |
||||
``` |
||||
|
||||
Первый запуск соберёт toolchain (30-120 минут), последующие — только пакет. |
||||
|
||||
## Другие платформы |
||||
|
||||
```bash |
||||
./build_openwrt.sh ramips/mt7621 asus_rt-ax54 # ASUS RT-AX54 |
||||
./build_openwrt.sh ramips/mt7621 xiaomi_mi-router-4a-gigabit |
||||
./build_openwrt.sh ath79/generic tplink_archer-c7-v2 |
||||
./build_openwrt.sh x86/64 # x86_64 PC |
||||
./build_openwrt.sh --list # список целей |
||||
``` |
||||
|
||||
## Ручная сборка (menuconfig) |
||||
|
||||
```bash |
||||
cd /home/user/openwrt |
||||
make menuconfig # Target System → MediaTek Ralink MIPS |
||||
# Subtarget → MT7621 based boards |
||||
# Target Profile → ASUS RT-AX53U |
||||
# Network → utun (M) |
||||
make -j$(nproc) # полная сборка прошивки |
||||
# или только пакет: |
||||
make package/utun2/compile -j$(nproc) V=s |
||||
``` |
||||
|
||||
Пакет: `bin/packages/*/base/utun_*.ipk` |
||||
|
||||
## Структура |
||||
|
||||
- `~/openwrt/package/utun2/Makefile` — OpenWRT-пакет |
||||
- `~/openwrt/` — клон buildroot (main, https://github.com/openwrt/openwrt) |
||||
- `~/utun2/build_openwrt.sh` — скрипт сборки |
||||
|
||||
## Зависимости (host) |
||||
|
||||
```bash |
||||
apt-get install -y build-essential git subversion libncurses-dev \ |
||||
libssl-dev ccache xsltproc gawk gettext unzip python3 libtool |
||||
``` |
||||
|
||||
## Примечания |
||||
|
||||
- `configure` генерируется через `autoreconf -fi` (уже сделан) |
||||
- Пакет использует `USE_SOURCE_DIR` — билд из /home/user/utun2 напрямую |
||||
- Для чистой пересборки: `make package/utun2/clean && make package/utun2/compile -j$(nproc)` |
||||
@ -0,0 +1,34 @@
|
||||
BUILDING WITH GNU AUTOTOOLS |
||||
=========================== |
||||
|
||||
Prerequisites: |
||||
- GNU Autotools (autoconf, automake, libtool) |
||||
- C compiler (gcc or clang) |
||||
- OpenSSL development libraries |
||||
- Linux kernel headers (for TUN/TAP) |
||||
|
||||
Quick Build: |
||||
./autogen.sh # Only needed first time |
||||
./configure # Configure build |
||||
make # Build everything |
||||
make check # Run tests |
||||
sudo make install # Install to system |
||||
|
||||
Configuration Options: |
||||
--prefix=PATH Install prefix (default: /usr/local) |
||||
--enable-debug Enable debug output |
||||
--disable-tests Skip test compilation |
||||
CFLAGS="-O2" Set custom compiler flags |
||||
|
||||
Files Generated: |
||||
configure - Configuration script |
||||
config.h - Configuration header |
||||
Makefile - Build rules |
||||
config.log - Configuration log |
||||
config.status - Configuration status |
||||
|
||||
Maintainers: |
||||
To update build system after changes: |
||||
autoreconf -fiv |
||||
|
||||
For more info: https://www.gnu.org/software/automake/manual/ |
||||
@ -0,0 +1,8 @@
|
||||
# Main Makefile.am for utun - root directory configuration
|
||||
|
||||
# Order matters: lib -> src -> tests (tests depend on src objects)
|
||||
SUBDIRS = lib lib/libopus src tests
|
||||
|
||||
# Clean build directory
|
||||
clean-local: |
||||
-rm -rf build/
|
||||
@ -0,0 +1,784 @@
|
||||
# uTun — Руководство пользователя |
||||
|
||||
uTun — децентрализованная VPN-система с шифрованием, надёжной доставкой данных, поддержкой mesh-сетей и встроенным TCP-прокси. Работает поверх UDP (основной транспорт) и TCP (STCP-резерв). Всё шифруется через X25519 + AES-128-CCM. Топология узлов обменивается автоматически (BGP-подобный протокол). |
||||
|
||||
--- |
||||
|
||||
## 1. Быстрый старт |
||||
|
||||
### 1.1. Сборка |
||||
|
||||
```bash |
||||
git clone <репозиторий> && cd utun3 |
||||
./build.sh --full -j4 # autoreconf + configure + make |
||||
``` |
||||
|
||||
Зависимости: `gcc`, `make`, `autotools` (autoconf/automake/libtool), `OpenSSL` (libssl-dev). |
||||
|
||||
### 1.2. Генерация ключей |
||||
|
||||
Ключи генерируются автоматически, если их нет в конфиге. Достаточно указать `my_node_name` — `config_updater` создаст пару X25519 и node_id: |
||||
|
||||
```ini |
||||
[global] |
||||
my_node_name=my-first-node |
||||
``` |
||||
|
||||
После первого запуска ключи пропишутся в конфиг автоматически. |
||||
|
||||
### 1.3. Первый запуск |
||||
|
||||
```bash |
||||
sudo ./src/utun -f -c myconfig.conf |
||||
``` |
||||
|
||||
- `-f` — foreground (без демонизации, видно логи в консоли) |
||||
- `-c myconfig.conf` — путь к конфигу |
||||
- `-p /var/run/utun.pid` — PID-файл |
||||
- `-l /var/log/utun.log` — лог-файл |
||||
|
||||
Без `-f` процесс уходит в демон (`fork` + `setsid`). |
||||
|
||||
### 1.4. Проверка работы |
||||
|
||||
```bash |
||||
ip addr show tun3 # TUN-интерфейс создан? |
||||
ping 10.23.1.1 # пингуем TUN-IP узла |
||||
ss -uln | grep 1333 # UDP-сокет слушает? |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## 2. Архитектура (кратко) |
||||
|
||||
uTun — **однопоточная асинхронная** система. Один event loop (`epoll`/`poll`) на поток. Никаких `sleep()`, никакого блокирующего I/O. |
||||
|
||||
**Стек протоколов (5 уровней):** |
||||
|
||||
| Уровень | Назначение | |
||||
|---------|------------| |
||||
| 1. UDP/TUN/STCP | Физический транспорт: UDP-сокеты, TUN-интерфейс, TCP-резерв | |
||||
| 2. secure_channel | X25519 ECDH + AES-128-CCM шифрование + Ed25519 подписи | |
||||
| 3. ETCP | Надёжный транспорт (inflight, ACK, ретрансмиссии, BBR congestion control) | |
||||
| 4. LoadBalancer | Multi-link балансировка (min inflight + round-robin) | |
||||
| 5. etcp_router | Сервисная маршрутизация (seq, dedup, reorder, transit forwarding) | |
||||
|
||||
Два уровня надёжности: |
||||
- **ETCP** — per-connection, RTT-based ретрансмиссии |
||||
- **etcp_router** — per-service, 300ms ретрансмиссии с 17 попытками (~5 сек) |
||||
|
||||
Node identity: `SHA256(private_key)` → первые 8 байт → 63-bit `node_id`. |
||||
|
||||
--- |
||||
|
||||
## 3. Конфигурация |
||||
|
||||
Формат: INI-style. Секции `[section]`, строки `key=value`, комментарии `#`. |
||||
|
||||
### 3.1. `[global]` — Идентичность и базовые настройки |
||||
|
||||
```ini |
||||
[global] |
||||
my_node_name=home-server # имя узла (человекочитаемое) |
||||
my_node_id=5f75c7445af88e1f # ID узла (авто-генерируется) |
||||
my_private_key=d065b784...4fe441 # X25519 приватный ключ (hex, 64 символа) |
||||
my_public_key=86b51a8b...6a2d19 # X25519 публичный ключ (hex, 64 символа) |
||||
|
||||
tun_enabled=yes # создавать TUN-интерфейс (по умолчанию yes) |
||||
tun_ifname=tun3 # имя TUN (по умолчанию tun0) |
||||
tun_ip=10.23.1.1 # IP на TUN-интерфейсе |
||||
mtu=1500 # MTU для всех соединений |
||||
|
||||
debug_level=error # глобальный уровень отладки (none/error/warn/info/debug/trace) |
||||
log_file=/var/log/utun.log # путь к лог-файлу |
||||
``` |
||||
|
||||
### 3.2. `[server:NAME]` — Локальные UDP-сокеты |
||||
|
||||
```ini |
||||
[server: lan1] |
||||
addr=192.168.1.10:1333 # IP:порт для приёма входящих |
||||
type=public # public / nat / private / local |
||||
transport=udp # udp (по умолчанию) или tcp (STCP) |
||||
mtu=1400 # MTU для этого канала |
||||
so_mark=100 # SO_MARK для policy routing |
||||
netif=eth0 # привязка к интерфейсу |
||||
only_local=0 # 1 = только локальные подключения |
||||
``` |
||||
|
||||
Типы адресов: |
||||
- `public` — узел на публичном IP (прямая связь) |
||||
- `nat` — узел за NAT (нужен NAT traversal) |
||||
- `private` — локальная сеть (только соседи в том же сегменте) |
||||
- `local` — только loopback/локальные подключения |
||||
|
||||
### 3.3. `[client:NAME]` — Подключение к удалённым узлам |
||||
|
||||
```ini |
||||
[client: aeza] |
||||
link=lan1:85.192.42.96:1333 # server_name:ip:port (можно несколько link=) |
||||
link=lan1:85.192.42.96:1433 # второй канал к тому же узлу |
||||
peer_public_key=0206dbee...fbcc16 # публичный ключ удалённого узла |
||||
keepalive=1 # интервал keepalive (сек) |
||||
``` |
||||
|
||||
Формат `link`: `локальный_сервер:удалённый_ip:удалённый_порт`. Серверная секция должна быть объявлена выше по файлу. |
||||
|
||||
### 3.4. `[routing]` — Маршруты |
||||
|
||||
```ini |
||||
[routing] |
||||
my_subnet=10.23.1.0/24 # своя подсеть — анонсируется соседям через BGP |
||||
route_subnet=10.23.0.0/16 # добавляется в системную таблицу маршрутов через TUN |
||||
route_subnet=fd00:1234::/32 # IPv6-подсеть |
||||
``` |
||||
|
||||
### 3.5. `[debug]` — Настройка отладки |
||||
|
||||
Формат: `категория=уровень`. Уровни: `none`, `error`, `warn`, `info`, `debug`, `trace`. |
||||
|
||||
```ini |
||||
[debug] |
||||
etcp=trace # всё про ETCP |
||||
crypto=info # криптографические операции |
||||
bgp=info # обмен топологией |
||||
connection=info # сокеты и линки |
||||
general=info # общая информация |
||||
``` |
||||
|
||||
Основные категории (всего 26): `uasync`, `ll_queue`, `connection`, `etcp`, `crypto`, `memory`, `timing`, `config`, `tun`, `routing`, `timers`, `normalizer`, `bgp`, `socket`, `control`, `dump`, `traffic`, `debug`, `general`, `nat`, `keepalive`, `etcпroute`, `bbr`, `etcp_dump`. |
||||
|
||||
### 3.6. `[firewall]` — Правила фильтрации |
||||
|
||||
```ini |
||||
[firewall] |
||||
allow=all # разрешить всё (bypass) |
||||
allow=192.168.1.0/24 # разрешить подсеть |
||||
allow=10.0.0.1:80 # разрешить конкретный IP:порт |
||||
``` |
||||
|
||||
### 3.7. `[nat]` — EIM NAT |
||||
|
||||
```ini |
||||
[nat] |
||||
nat_enabled=yes |
||||
nat_tun_ifname=tun_external # внешний TUN (выход в интернет) |
||||
nat_via=gateway_ip # шлюз |
||||
port_start=10000 |
||||
port_end=20000 |
||||
forward=tcp:10.0.0.5:80:8080 # проброс: протокол:ip:внутр_порт:внеш_порт |
||||
``` |
||||
|
||||
### 3.8. `[tcp_proxy_client]` / `[tcp_proxy_server]` — TCP-прокси |
||||
|
||||
**Клиент (выход через exit-узел):** |
||||
|
||||
```ini |
||||
[tcp_proxy_client] |
||||
enabled=yes |
||||
tun_name=tun_proxy # имя TUN для прокси-трафика |
||||
tun_ip=10.200.30.1 # IP клиентского TUN |
||||
via_node=50e9e88658e91977 # node_id exit-узла |
||||
|
||||
socks_enabled=yes # встроенный SOCKS5-прокси |
||||
socks_addr=127.0.0.1:1082 # слушать SOCKS5 здесь |
||||
|
||||
http_proxy_enabled=yes # встроенный HTTP CONNECT-прокси |
||||
http_proxy_addr=127.0.0.1:8080 |
||||
``` |
||||
|
||||
**Exit-узел (сервер):** |
||||
|
||||
```ini |
||||
[tcp_proxy_server] |
||||
enabled=yes |
||||
``` |
||||
|
||||
Клиент настраивает браузер на SOCKS5 `127.0.0.1:1082` — весь TCP-трафик идёт через exit-узел. |
||||
|
||||
### 3.9. `[control]` — Сервер мониторинга |
||||
|
||||
```ini |
||||
[control] |
||||
control_ip=192.168.29.117 # IP для приёма подключений etcpmon |
||||
control_port=9090 |
||||
control_allow=192.168.0.0/16 # разрешённые подсети (можно несколько) |
||||
``` |
||||
|
||||
### 3.10. `[allowed_keys]` — Ограничение подключений |
||||
|
||||
```ini |
||||
[allowed_keys] |
||||
allow_all=1 # разрешить все ключи |
||||
# key=86b51a8b...6a2d19 # разрешить конкретный pubkey |
||||
``` |
||||
|
||||
### 3.11. `[ntp]` — Синхронизация времени |
||||
|
||||
```ini |
||||
[ntp] |
||||
enabled=yes |
||||
server=pool.ntp.org |
||||
server=time.google.com |
||||
interval=3600 # интервал синхронизации (сек) |
||||
``` |
||||
|
||||
### 3.12. `[network:NAME]` — Именованные сети |
||||
|
||||
```ini |
||||
[network: mynet] |
||||
id=5f75c7445af88e1f # 56-bit ID сети |
||||
pubkey=86b51a8b...6a2d19 # публичный ключ сети |
||||
signing_key=d065b784...4fe441 # ключ подписи (Ed25519) |
||||
``` |
||||
|
||||
### 3.13. Серверный vs клиентский конфиг |
||||
|
||||
| Параметр | Сервер | Клиент | |
||||
|----------|--------|--------| |
||||
| `[server]` секция | Обязательна (свои сокеты) | Обязательна (свои сокеты) | |
||||
| `[client]` секция | Нет | Обязательна (к кому подключаться) | |
||||
| `peer_public_key` | Нет | Обязателен в `[client]` | |
||||
| `my_private_key` | Свой | Свой | |
||||
| `my_public_key` | Свой | Свой | |
||||
|
||||
Важно: `[server]` обязателен в **обоих** случаях — это рабочий сокет узла. Клиент дополнительно указывает `[client]` с `peer_public_key` и `link`. |
||||
|
||||
--- |
||||
|
||||
## 4. Сценарии использования |
||||
|
||||
### 4.1. VPN между двумя узлами |
||||
|
||||
**Сервер** (публичный IP `85.192.42.96`): |
||||
|
||||
```ini |
||||
[global] |
||||
my_node_name=server |
||||
my_private_key=<hex> |
||||
my_public_key=<hex> |
||||
tun_ip=10.23.1.1 |
||||
|
||||
[server: main] |
||||
addr=85.192.42.96:1333 |
||||
type=public |
||||
|
||||
[routing] |
||||
my_subnet=10.23.1.0/24 |
||||
``` |
||||
|
||||
**Клиент** (за NAT, подключается к серверу): |
||||
|
||||
```ini |
||||
[global] |
||||
my_node_name=client |
||||
my_private_key=<hex> |
||||
my_public_key=<hex> |
||||
tun_ip=10.23.2.1 |
||||
|
||||
[server: uplink] |
||||
addr=0.0.0.0:1333 # можно 0.0.0.0 — клиенту не нужен фиксированный порт |
||||
type=nat |
||||
|
||||
[client: server] |
||||
link=uplink:85.192.42.96:1333 |
||||
peer_public_key=<pubkey_сервера> |
||||
keepalive=1 |
||||
|
||||
[routing] |
||||
my_subnet=10.23.2.0/24 |
||||
``` |
||||
|
||||
**Проверка связности:** |
||||
|
||||
```bash |
||||
# На сервере |
||||
ping 10.23.2.1 |
||||
|
||||
# На клиенте |
||||
ping 10.23.1.1 |
||||
``` |
||||
|
||||
### 4.2. Mesh-сеть из нескольких узлов |
||||
|
||||
Каждый узел = сервер (свои сокеты) + клиент (подключения к соседям). Пример для узла B, который подключается к A и C: |
||||
|
||||
```ini |
||||
[global] |
||||
my_node_name=node-b |
||||
my_private_key=<hex> |
||||
my_public_key=<hex> |
||||
tun_ip=10.23.2.1 |
||||
|
||||
[server: main] |
||||
addr=0.0.0.0:1333 |
||||
type=public |
||||
|
||||
[client: node-a] |
||||
link=main:85.192.42.96:1333 |
||||
peer_public_key=<pubkey_A> |
||||
|
||||
[client: node-c] |
||||
link=main:203.0.113.10:1333 |
||||
peer_public_key=<pubkey_C> |
||||
|
||||
[routing] |
||||
my_subnet=10.23.2.0/24 |
||||
``` |
||||
|
||||
Что происходит автоматически: |
||||
1. B устанавливает ETCP-соединения с A и C |
||||
2. Через BGP A узнаёт о подсетях C (и наоборот) |
||||
3. Узел A может отправлять пакеты к подсети C через B (transit forwarding) |
||||
4. `etcp_router` обеспечивает надёжную маршрутизацию через промежуточные узлы |
||||
|
||||
### 4.3. Exit-прокси (выход в интернет через удалённый узел) |
||||
|
||||
**Exit-узел** (с прямым выходом в интернет): |
||||
|
||||
```ini |
||||
[global] |
||||
my_node_name=exit-node |
||||
my_private_key=<hex> |
||||
my_public_key=<hex> |
||||
|
||||
[server: main] |
||||
addr=85.192.42.96:1333 |
||||
type=public |
||||
|
||||
[tcp_proxy_server] |
||||
enabled=yes |
||||
``` |
||||
|
||||
**Клиент** (выходит в интернет через exit-узел): |
||||
|
||||
```ini |
||||
[global] |
||||
my_node_name=client |
||||
my_private_key=<hex> |
||||
my_public_key=<hex> |
||||
|
||||
[server: main] |
||||
addr=0.0.0.0:1333 |
||||
|
||||
[client: exit] |
||||
link=main:85.192.42.96:1333 |
||||
peer_public_key=<pubkey_exit> |
||||
|
||||
[tcp_proxy_client] |
||||
enabled=yes |
||||
tun_name=tun_proxy |
||||
tun_ip=10.200.30.1 |
||||
via_node=<node_id_exit> |
||||
socks_enabled=yes |
||||
socks_addr=127.0.0.1:1082 |
||||
``` |
||||
|
||||
Настройка браузера: SOCKS5 proxy `127.0.0.1:1082`. TCP-трафик идёт: браузер → SOCKS5 → lwIP → ETCP → exit-узел → интернет. |
||||
|
||||
### 4.4. EIM NAT (раздача интернета через NAT) |
||||
|
||||
```ini |
||||
[nat] |
||||
nat_enabled=yes |
||||
nat_tun_ifname=eth0 # внешний интерфейс |
||||
port_start=20000 |
||||
port_end=30000 |
||||
forward=tcp:192.168.1.100:80:8080 # проброс порта 8080 → 192.168.1.100:80 |
||||
forward=udp:192.168.1.100:53:53 # проброс DNS |
||||
``` |
||||
|
||||
### 4.5. Чат (chatgui) |
||||
|
||||
Chatgui — десктопный GUI-чат (Qt 6) со встроенным uTun-узлом. Вся связь — напрямую между узлами (P2P), без центральных серверов. |
||||
|
||||
**Сборка:** |
||||
|
||||
```bash |
||||
sudo apt install librlottie-dev zlib1g-dev qt6-base-dev |
||||
cd tools/chatgui && mkdir -p build && cd build |
||||
cmake .. && make -j4 |
||||
./chatgui |
||||
``` |
||||
|
||||
**Запуск:** |
||||
|
||||
```bash |
||||
setsid env DISPLAY=:1.0 QT_ACCESSIBILITY=1 ./chatgui & disown |
||||
``` |
||||
|
||||
Chatgui работает в двух потоках: |
||||
- **GUI-поток** — Qt, отрисовка, чтение из SQLite |
||||
- **uasync-поток** — uTun-узел, сеть, криптография, ETCP, запись в SQLite |
||||
|
||||
**Основные возможности:** |
||||
- Создание каналов и групп |
||||
- Обмен сообщениями с анимированными эмодзи (TGS) |
||||
- Invite-ссылки (`utun://...`) с QR-кодами |
||||
- P2P синхронизация через Merkle-деревья |
||||
- Авто-подключение к известным узлам из БД |
||||
|
||||
--- |
||||
|
||||
## 5. Топология сети |
||||
|
||||
### 5.1. Типы адресов |
||||
|
||||
| Тип | Описание | Пример | |
||||
|-----|----------|--------| |
||||
| `INTERFACE` | Адрес на сетевом интерфейсе | `eth0: 192.168.1.10` | |
||||
| `NAT` | Внешний адрес после NAT (детектируется) | `85.192.42.96:54321` | |
||||
| `REAL` | Публичный адрес (совпадает interface == NAT) | `85.192.42.96:1333` | |
||||
|
||||
### 5.2. Как узлы находят друг друга |
||||
|
||||
1. **Прямое подключение** — через `[client]` секцию с явным IP:портом |
||||
2. **BGP-обмен** — при подключении к одному узлу, узнаёшь о всех его соседях |
||||
3. **Локальное сканирование** — `conn_mgr` периодически сканирует локальную сеть |
||||
4. **Invite-ссылки** — `utun://` ссылки с закодированными адресами и ключами |
||||
|
||||
### 5.3. NAT traversal |
||||
|
||||
`conn_mgr` реализует трёхфазное подключение: |
||||
|
||||
1. **DIRECT** (5 сек) — прямое INIT-рукопожатие со всеми известными адресами |
||||
2. **REVERSE** (15 сек) — если клиент за NAT, сервер с прямым IP: клиент шлёт свои адреса через BGP, сервер подключается сам |
||||
3. **INDIRECT** (15 сек) — оба за NAT: выбираются общие кандидаты-посредники по min RTT, трафик идёт через `etcp_router` |
||||
|
||||
Типы NAT, которые определяет uTun: |
||||
- **EIM** (Endpoint-Independent Mapping) — один внешний порт для всех назначений |
||||
- **Strict** (Address/Restricted/Symmetric) — разные порты |
||||
|
||||
### 5.4. Группы |
||||
|
||||
Два типа групп: |
||||
- **UTUN** (`group_id=0x8000000000000000`) — VPN-сеть: обмен подсетями, маршруты |
||||
- **CHAT** — чат-группа: только узлы, без подсетей, персистентность в SQLite |
||||
|
||||
Узлы разных групп изолированы. |
||||
|
||||
--- |
||||
|
||||
## 6. Мониторинг и отладка |
||||
|
||||
### 6.1. Уровни отладки |
||||
|
||||
``` |
||||
none < error < warn < info < debug < trace |
||||
``` |
||||
|
||||
Настройка через `-d` в CLI (имеет приоритет) или секцию `[debug]` в конфиге: |
||||
|
||||
```bash |
||||
./src/utun -f -c myconfig.conf -d "etcp=trace,crypto=info" |
||||
``` |
||||
|
||||
Или эквивалент в конфиге: |
||||
|
||||
```ini |
||||
[debug] |
||||
etcp=trace |
||||
crypto=info |
||||
bgp=info |
||||
``` |
||||
|
||||
### 6.2. Категории отладки (26 шт) |
||||
|
||||
| Категория | Что логирует | |
||||
|-----------|-------------| |
||||
| `etcp` | Протокол ETCP: очереди, retrans, ACK, RTT | |
||||
| `crypto` | Шифрование/дешифрование, ключи, nonce | |
||||
| `connection` | Сокеты, линки, INIT handshake, keepalive | |
||||
| `bgp` | Обмен топологией (NODEINFO, WITHDRAW) | |
||||
| `routing` | Таблица маршрутов, поиск путей | |
||||
| `tun` | TUN-интерфейс: чтение/запись | |
||||
| `traffic` | Данные трафика | |
||||
| `general` | Общая информация | |
||||
| `bbr` | BBR congestion control | |
||||
| `keepalive` | Адаптивный keepalive | |
||||
| `memory` | Выделение/освобождение памяти | |
||||
| `dump` | Hex-дампы пакетов | |
||||
| `etcp_dump` | Декодирование ETCP-пакетов | |
||||
|
||||
### 6.3. etcpmon — Windows GUI монитор |
||||
|
||||
Подключается к `control_server` узла по TCP (порт из `control_port`). Показывает: |
||||
- Список соединений и их состояние |
||||
- RTT, потери, inflight |
||||
- Графики метрик |
||||
- Дамп пакетов |
||||
|
||||
### 6.4. control_server — TCP API |
||||
|
||||
```ini |
||||
[control] |
||||
control_ip=0.0.0.0 |
||||
control_port=9090 |
||||
control_allow=192.168.0.0/16 |
||||
``` |
||||
|
||||
Любой TCP-клиент может подключиться и получать события в реальном времени (JSON-подобный формат). |
||||
|
||||
### 6.5. Логи и дампы |
||||
|
||||
Лог-файл указывается через `-l`: |
||||
|
||||
```bash |
||||
./src/utun -f -c myconfig.conf -l /tmp/utun.log |
||||
``` |
||||
|
||||
Hex-дамп пакетов включается категорией `dump=trace`: |
||||
|
||||
```ini |
||||
[debug] |
||||
dump=trace |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## 7. NAT и Firewall |
||||
|
||||
### 7.1. Как uTun определяет тип NAT |
||||
|
||||
1. Клиент подключается к серверу |
||||
2. Сервер видит внешний IP:порт клиента (из UDP-пакета) |
||||
3. Сервер через **третий узел** (посредник) пингует клиента на этот внешний IP:порт |
||||
4. Если клиент ответил — NAT EIM (один mapping для всех) |
||||
5. Если нет — NAT Strict |
||||
|
||||
Результат сохраняется в `link->nat_type` и broadcast-ится всем соседям через BGP. |
||||
|
||||
### 7.2. Firewall |
||||
|
||||
```ini |
||||
[firewall] |
||||
allow=all # всё разрешено |
||||
allow=10.0.0.0/8 # разрешить подсеть |
||||
allow=192.168.1.5:22 # разрешить IP:порт |
||||
``` |
||||
|
||||
По умолчанию без секции `[firewall]` — фильтрация выключена. |
||||
|
||||
### 7.3. Проброс портов |
||||
|
||||
```ini |
||||
[nat] |
||||
forward=tcp:192.168.1.100:80:8080 # входящий порт 8080 → 192.168.1.100:80 |
||||
forward=udp:10.0.0.53:53:53 # DNS |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## 8. Устранение неполадок |
||||
|
||||
### 8.1. Нет связи — проверка INIT handshake |
||||
|
||||
Включите: |
||||
|
||||
```ini |
||||
[debug] |
||||
connection=debug |
||||
crypto=debug |
||||
``` |
||||
|
||||
В логах ищите: |
||||
- `INIT_REQUEST sent to ...` / `INIT_RESPONSE received from ...` — handshake |
||||
- `session_ready=1` — ключи согласованы, шифрование работает |
||||
- `ETCP_KEEPALIVE` — keepalive-пакеты идут |
||||
- Если нет — проверьте firewall на портах, доступность IP |
||||
|
||||
### 8.2. Пакеты теряются — проверка метрик |
||||
|
||||
```bash |
||||
# В логах с etcp=debug смотрите: |
||||
grep "retrans" /tmp/utun.log # ретрансмиссии |
||||
grep "rtt=" /tmp/utun.log # задержки |
||||
grep "lost" /tmp/utun.log # потери |
||||
``` |
||||
|
||||
Каждые 10 минут пишутся гистограммы метрик с RTT-бакетами и loss-бакетами. |
||||
|
||||
### 8.3. Высокая задержка — keepalive |
||||
|
||||
Адаптивный keepalive: период растёт от 200ms до 10s при отсутствии трафика, сбрасывается к 200ms при появлении. Таймаут = период × 10. |
||||
|
||||
Проверьте: |
||||
|
||||
```ini |
||||
[debug] |
||||
keepalive=debug |
||||
``` |
||||
|
||||
### 8.4. Утечки памяти — проверка пулов |
||||
|
||||
```ini |
||||
[debug] |
||||
memory=debug |
||||
``` |
||||
|
||||
При завершении `u_report_unfreed_blocks()` показывает все неосвобождённые блоки. |
||||
|
||||
### 8.5. Дамп состояния |
||||
|
||||
ETCP предоставляет функцию `etcp_dump_all_conns()` для вывода полного состояния всех соединений (очереди, inflight, счётчики). Включается через `etcp_dump=trace`. |
||||
|
||||
### 8.6. ASAN-сборка (поиск повреждений памяти) |
||||
|
||||
```bash |
||||
./build.sh --asan -j4 |
||||
ASAN_OPTIONS=detect_leaks=0:halt_on_error=0 ./src/utun -f -c myconfig.conf |
||||
``` |
||||
|
||||
Краш-логи ASAN пишутся в `/tmp/utun_asan.<pid>`. |
||||
|
||||
--- |
||||
|
||||
## 9. Производительность |
||||
|
||||
### 9.1. BBR congestion control |
||||
|
||||
BBR работает per-link. Основные настройки: |
||||
|
||||
```ini |
||||
[global] |
||||
bbr_max_cwnd=1048576 # максимум окна перегрузки (1MB по умолчанию) |
||||
``` |
||||
|
||||
BBR автоматически измеряет пропускную способность через burst-измерения (12 пакетов пачкой, замер inter-packet gap). Периодичность burst — не чаще 500ms. |
||||
|
||||
### 9.2. Memory pools |
||||
|
||||
Три пула в `UTUN_INSTANCE`: |
||||
- `data_pool` — данные пакетов (payload) |
||||
- `pkt_pool` — `struct ETCP_DGRAM` (сетевые пакеты) |
||||
- `ack_pool` — `struct ACK_PACKET` (подтверждения) |
||||
|
||||
Пулы аллоцируются upfront при старте. Статистику можно посмотреть через control_server. |
||||
|
||||
### 9.3. Multi-link балансировка |
||||
|
||||
LoadBalancer выбирает линк с минимальным inflight, с round-robin для равных. Traffic shaper ограничивает отправку до `optimal_inflight`: |
||||
|
||||
``` |
||||
timeout = (rtt_avg_10 * 32 + jitter * 32) / 16 |
||||
``` |
||||
|
||||
Оптимальное окно: `optimal_inflight = sum(inflight_lim_bytes)` |
||||
|
||||
### 9.4. Keepalive |
||||
|
||||
Адаптивный режим: |
||||
- Начальный период = `keepalive` из `[client]` (сек) |
||||
- При трафике → сброс к начальному |
||||
- Без трафика → растёт ×1.05 до 10 секунд |
||||
- Таймаут = период × 10 |
||||
|
||||
--- |
||||
|
||||
## 10. CLI-справка |
||||
|
||||
``` |
||||
utun [опции] |
||||
|
||||
Опции: |
||||
-f Foreground (без демонизации) |
||||
-c FILE Конфигурационный файл (по умолчанию: utun.conf) |
||||
-p FILE PID-файл (по умолчанию: /var/run/utun.pid) |
||||
-l FILE Лог-файл (по умолчанию: utun.log) |
||||
-d CONFIG Отладка: категория=уровень,... (переопределяет конфиг) |
||||
-h Справка |
||||
``` |
||||
|
||||
**Сигналы:** |
||||
- `SIGTERM` / `SIGINT` — graceful shutdown (закрытие соединений, освобождение ресурсов) |
||||
- `SIGHUP` — reload конфига (селективный: меняются только изменённые сокеты/клиенты; полный — при изменении ключей или TUN) |
||||
|
||||
--- |
||||
|
||||
## 11. Типовые конфигурации |
||||
|
||||
### Минимальный сервер |
||||
|
||||
```ini |
||||
[global] |
||||
my_node_name=server |
||||
tun_ip=10.23.1.1 |
||||
|
||||
[server: main] |
||||
addr=0.0.0.0:1333 |
||||
``` |
||||
|
||||
### Минимальный клиент |
||||
|
||||
```ini |
||||
[global] |
||||
my_node_name=client |
||||
tun_ip=10.23.2.1 |
||||
|
||||
[server: main] |
||||
addr=0.0.0.0:1333 |
||||
|
||||
[client: server] |
||||
link=main:85.192.42.96:1333 |
||||
peer_public_key=<pubkey_сервера> |
||||
``` |
||||
|
||||
### Mesh-узел с несколькими пирами |
||||
|
||||
```ini |
||||
[global] |
||||
my_node_name=mesh-node-3 |
||||
tun_ip=10.23.3.1 |
||||
|
||||
[server: main] |
||||
addr=0.0.0.0:1333 |
||||
|
||||
[client: node-1] |
||||
link=main:10.0.0.1:1333 |
||||
peer_public_key=<pubkey_1> |
||||
|
||||
[client: node-2] |
||||
link=main:10.0.0.2:1333 |
||||
peer_public_key=<pubkey_2> |
||||
|
||||
[routing] |
||||
my_subnet=10.23.3.0/24 |
||||
``` |
||||
|
||||
### Exit-узел с TCP-прокси |
||||
|
||||
```ini |
||||
[global] |
||||
my_node_name=exit-eu |
||||
tun_ip=10.23.100.1 |
||||
|
||||
[server: main] |
||||
addr=85.192.42.96:1333 |
||||
type=public |
||||
|
||||
[tcp_proxy_server] |
||||
enabled=yes |
||||
|
||||
[routing] |
||||
my_subnet=10.23.100.0/24 |
||||
``` |
||||
|
||||
### Клиент с SOCKS5-прокси |
||||
|
||||
```ini |
||||
[global] |
||||
my_node_name=home-laptop |
||||
|
||||
[server: main] |
||||
addr=0.0.0.0:1333 |
||||
|
||||
[client: exit] |
||||
link=main:85.192.42.96:1333 |
||||
peer_public_key=<pubkey_exit> |
||||
|
||||
[tcp_proxy_client] |
||||
enabled=yes |
||||
socks_enabled=yes |
||||
socks_addr=127.0.0.1:1082 |
||||
via_node=<node_id_exit> |
||||
``` |
||||
@ -0,0 +1,25 @@
|
||||
Задача сделать полное архитектурное описание проекта. |
||||
Как делаем |
||||
|
||||
1. определяем список проектов. продумываем последовательность разбора проектов - зависимости - что от чего зависит, что лучше разбирать в начале. |
||||
|
||||
Далее по каждому проекту: |
||||
|
||||
2. разбиваешь весь проект на логические модули. |
||||
поручаешь субагентам (параллельно запускаешь до 8 потоков), балансируй нагрузку на агентов (по объему кода): если модули короткие - можно 2-3 простых одному субагенту. |
||||
- проанализировать логику работы модуля |
||||
- понять как этим пользоваться с позиции пользователя (ты - архитектор и пользователь - типовой сценарий использования модуля с точки зрения удобства и лёгкости использования). |
||||
какие есть потенциальные проблемные места, можно ли пользоваться в многопоточной архитектуре, можно ли в асинхронной архитектуре, можно ли делать close из callback (если есть коллбэки и модулю асинхронный). какие есть еще нюансы и ограничения. |
||||
- создать (или обновить если есть) описание работы модуля - файл рядом с исходниками называется [module_name]_doc.md |
||||
описание должно быть с точки зрения пользователя - в первую очередь как пользоваться и какие нюансы. понятным языком (если используется терминология специфичная для модуля - то должна быть понятна или пояснения). |
||||
Схема описания: |
||||
1. назначение модуля, зачем нужен, что делает |
||||
2. как пользоваться, лучше типовой сценарий (с нюансами если есть), |
||||
3. кратко пояснения по апи и нюансы если есть |
||||
|
||||
далее читаешь полностью все эти описания и формируешь итоговое описание проекта. |
||||
Продумай какая структура документации лучше подходит, разбей на главы. |
||||
И сделай общее описние проекта (user manual). |
||||
|
||||
твоя задача на основе сформированной документации разобраться как работает utun. ключевое слово =- хорошо. хорошо понять все модули, их взаимодействое, возможности, облести применимости и |
||||
ограничения. понять архитектурно как работает проект собранный из этих модулей. и только после этого написать документацию. |
||||
@ -0,0 +1,31 @@
|
||||
@echo off |
||||
echo uTun Build Script for MSYS2 UCRT64 |
||||
echo. |
||||
|
||||
pushd "%~dp0" |
||||
|
||||
|
||||
rem export http_proxy="http://192.168.29.1:9999" |
||||
rem export https_proxy="http://192.168.29.1:9999" |
||||
|
||||
|
||||
REM Default MSYS2 path |
||||
set MSYS2_PATH=C:\msys64 |
||||
|
||||
REM Use environment variable if set |
||||
if not "%MSYS2_ROOT%"=="" set MSYS2_PATH=%MSYS2_ROOT% |
||||
if not "%MSYS2%"=="" set MSYS2_PATH=%MSYS2% |
||||
|
||||
REM Check if msys2_shell.cmd exists |
||||
if not exist "%MSYS2_PATH%\msys2_shell.cmd" ( |
||||
echo MSYS2 not found at %MSYS2_PATH% |
||||
echo Please install MSYS2 from https://www.msys2.org/ |
||||
echo or set MSYS2_ROOT environment variable. |
||||
pause |
||||
exit /b 1 |
||||
) |
||||
|
||||
REM Launch MSYS2 UCRT64 shell and run build script |
||||
echo Starting MSYS2 UCRT64 environment... |
||||
rem call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -c "bash build.sh > build_win.log 2>&1" |
||||
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "set -o pipefail; bash build.sh 2>&1 | tee build_win.log" |
||||
@ -0,0 +1,197 @@
|
||||
#!/bin/bash |
||||
# Build script for uTun (supports both Linux and Windows/MSYS2) |
||||
# Usage: ./build.sh [options] |
||||
# Options: |
||||
# -h, --help Show this help |
||||
# --clean Clean before build |
||||
# --full Full rebuild (autoreconf + configure + make) |
||||
# --asan Build with AddressSanitizer (debug) |
||||
# --hardened Build with compiler security hardening flags |
||||
# -j[N] Build with N parallel jobs (default: 4) |
||||
|
||||
set -e # Exit on error |
||||
|
||||
# Detect platform |
||||
if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ -n "$MSYSTEM" ]]; then |
||||
IS_WINDOWS=1 |
||||
echo "Detected Windows/MSYS2 environment" |
||||
else |
||||
IS_WINDOWS=0 |
||||
echo "Detected Linux environment" |
||||
fi |
||||
|
||||
# Parse arguments |
||||
CLEAN=0 |
||||
FULL_REBUILD=0 |
||||
USE_ASAN=0 |
||||
USE_HARDENED=0 |
||||
JOBS="-j4" |
||||
MAKE_ARGS="" |
||||
HARDEN_FLAGS="" |
||||
|
||||
while [[ $# -gt 0 ]]; do |
||||
case $1 in |
||||
-h|--help) |
||||
echo "Usage: ./build.sh [options]" |
||||
echo "Options:" |
||||
echo " -h, --help Show this help" |
||||
echo " --clean Clean before build" |
||||
echo " --full Full rebuild (autoreconf + configure + make)" |
||||
echo " --asan Build with AddressSanitizer" |
||||
echo " --hardened Build with compiler security hardening flags" |
||||
echo " -j[N] Build with N parallel jobs (default: 4)" |
||||
exit 0 |
||||
;; |
||||
--clean) |
||||
CLEAN=1 |
||||
shift |
||||
;; |
||||
--full) |
||||
FULL_REBUILD=1 |
||||
shift |
||||
;; |
||||
--asan) |
||||
USE_ASAN=1 |
||||
shift |
||||
;; |
||||
--hardened) |
||||
USE_HARDENED=1 |
||||
shift |
||||
;; |
||||
-j*) |
||||
JOBS="$1" |
||||
shift |
||||
;; |
||||
*) |
||||
MAKE_ARGS="$MAKE_ARGS $1" |
||||
shift |
||||
;; |
||||
esac |
||||
done |
||||
|
||||
# Check required tools |
||||
if ! command -v gcc &> /dev/null; then |
||||
echo "Error: gcc not found." |
||||
if [ "$IS_WINDOWS" -eq 1 ]; then |
||||
echo "Please install: pacman -S mingw-w64-ucrt-x86_64-toolchain" |
||||
else |
||||
echo "Please install build-essential or gcc" |
||||
fi |
||||
exit 1 |
||||
fi |
||||
|
||||
# Clean if requested |
||||
if [ "$CLEAN" -eq 1 ]; then |
||||
echo "Cleaning build..." |
||||
make clean 2>/dev/null || true |
||||
fi |
||||
|
||||
# Full rebuild: regenerate build system |
||||
if [ "$FULL_REBUILD" -eq 1 ]; then |
||||
echo "" |
||||
echo "======================================" |
||||
echo "FULL REBUILD: autoreconf" |
||||
echo "======================================" |
||||
echo "" |
||||
|
||||
# Check for autotools |
||||
if ! command -v autoreconf &> /dev/null; then |
||||
echo "Error: autoreconf not found." |
||||
if [ "$IS_WINDOWS" -eq 1 ]; then |
||||
echo "Install autotools: pacman -S mingw-w64-ucrt-x86_64-autotools" |
||||
else |
||||
echo "Install autotools: apt-get install autoconf automake libtool" |
||||
fi |
||||
exit 1 |
||||
fi |
||||
|
||||
# Clean generated files |
||||
echo "Cleaning generated files..." |
||||
make distclean 2>/dev/null || true |
||||
rm -rf build/ |
||||
|
||||
# Regenerate build system (root Makefile is hand-written, not generated) |
||||
echo "" |
||||
echo "Running autoreconf -fi..." |
||||
autoreconf -fi 2>&1 | tee autoreconf.log |
||||
|
||||
echo "" |
||||
echo "======================================" |
||||
echo "Build system regenerated successfully" |
||||
echo "======================================" |
||||
echo "" |
||||
else |
||||
# Quick/incremental build: generate configure if needed |
||||
if [ ! -f ./configure ]; then |
||||
echo "Configure script not found. Generating..." |
||||
if ! command -v autoreconf &> /dev/null; then |
||||
echo "Error: autoreconf not found." |
||||
if [ "$IS_WINDOWS" -eq 1 ]; then |
||||
echo "Install autotools: pacman -S mingw-w64-ucrt-x86_64-autotools" |
||||
else |
||||
echo "Install autotools: apt-get install autoconf automake libtool" |
||||
fi |
||||
exit 1 |
||||
fi |
||||
autoreconf -fiv |
||||
fi |
||||
fi |
||||
|
||||
# Generate Makefiles if needed |
||||
if [ ! -f Makefile ]; then |
||||
echo "" |
||||
echo "Running ./configure..." |
||||
if [ "$USE_ASAN" -eq 1 ]; then |
||||
./configure CFLAGS="-g -Og -fsanitize=address -fno-omit-frame-pointer -fno-pie" LDFLAGS="-no-pie" |
||||
elif [ "$USE_HARDENED" -eq 1 ]; then |
||||
./configure --enable-hardening |
||||
else |
||||
./configure |
||||
fi |
||||
fi |
||||
|
||||
# Build (all .o files go to build/ directory, binary copied to project root) |
||||
echo "" |
||||
echo "Compiling with $JOBS..." |
||||
BUILD_SUCCESS=0 |
||||
if [ "$USE_ASAN" -eq 1 ]; then |
||||
echo "(AddressSanitizer enabled — lib/ and src/ only, skipping tests)" |
||||
AF="-fsanitize=address -fno-omit-frame-pointer -g -Og" |
||||
AL="-fsanitize=address" |
||||
(cd lib && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$AF" "LDFLAGS+=$AL" $JOBS $MAKE_ARGS 2>&1) || { echo "lib build failed"; exit 1; } |
||||
(cd src && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$AF" "LDFLAGS+=$AL" $JOBS $MAKE_ARGS 2>&1) || { echo "src build failed"; exit 1; } |
||||
BUILD_SUCCESS=1 |
||||
elif [ "$USE_HARDENED" -eq 1 ]; then |
||||
echo "(Hardening flags enabled)" |
||||
HF="-D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -Wformat=2 -Wformat-overflow=2 -fPIE" |
||||
HL="-Wl,-z,relro -Wl,-z,now -pie" |
||||
(cd lib && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$HF" "LDFLAGS+=$HL" $JOBS $MAKE_ARGS 2>&1) || { echo "lib build failed"; exit 1; } |
||||
(cd src && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$HF" "LDFLAGS+=$HL" $JOBS $MAKE_ARGS 2>&1) || { echo "src build failed"; exit 1; } |
||||
(cd tests && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$HF" "LDFLAGS+=$HL" $JOBS $MAKE_ARGS 2>&1) || { echo "tests build failed"; exit 1; } |
||||
BUILD_SUCCESS=1 |
||||
else |
||||
make $JOBS $MAKE_ARGS 2>&1 | tee build.log |
||||
if [ ${PIPESTATUS[0]} -eq 0 ]; then |
||||
BUILD_SUCCESS=1 |
||||
fi |
||||
fi |
||||
|
||||
if [ $BUILD_SUCCESS -eq 1 ]; then |
||||
echo "" |
||||
echo "Build completed successfully!" |
||||
echo "Binary: utun" |
||||
|
||||
# Rebuild chatgui (CMake project) if transport sources changed |
||||
if [ -f tools/chatgui/build/CMakeCache.txt ] && command -v cmake &> /dev/null; then |
||||
echo "" |
||||
echo "======================================" |
||||
echo "Rebuilding chatgui (CMake)" |
||||
echo "======================================" |
||||
(cd tools/chatgui/build && cmake --build . -j4 2>&1) || echo "WARNING: chatgui build failed" |
||||
fi |
||||
else |
||||
echo "" |
||||
echo "ERROR: Build failed!" |
||||
echo "Check build.log for details" |
||||
exit 1 |
||||
fi |
||||
@ -0,0 +1,60 @@
|
||||
#!/bin/sh |
||||
|
||||
set -e |
||||
|
||||
# Проверка и установка зависимостей |
||||
need_install="" |
||||
|
||||
for cmd in autoreconf aclocal automake libtoolize; do |
||||
if ! command -v "$cmd" >/dev/null 2>&1; then |
||||
need_install=1 |
||||
break |
||||
fi |
||||
done |
||||
|
||||
if [ -n "$need_install" ]; then |
||||
echo "Устанавливаются необходимые пакеты..." |
||||
pkg update |
||||
pkg install -y autoconf automake libtool pkgconf openssl |
||||
fi |
||||
|
||||
mkdir -p m4 |
||||
|
||||
echo "=== Полная пересборка uTun ===" |
||||
|
||||
# 1. Очистка |
||||
echo "[1/4] Очистка..." |
||||
make distclean 2>/dev/null || true |
||||
rm -f config.h config.status config.log |
||||
rm -f configure.log autoreconf.log build.log |
||||
|
||||
# 2. Генерация configure |
||||
echo "[2/4] Автогенерация..." |
||||
autoreconf -fi 2>&1 | tee autoreconf.log |
||||
|
||||
# Проверяем, появился ли configure |
||||
if [ ! -f configure ]; then |
||||
echo "Ошибка: autoreconf не создал configure." |
||||
exit 1 |
||||
fi |
||||
|
||||
chmod +x configure |
||||
|
||||
# 3. Конфигурация |
||||
echo "[3/4] Конфигурация..." |
||||
./configure --prefix=/usr/local 2>&1 | tee configure.log |
||||
|
||||
# Определяем количество ядер |
||||
if command -v nproc >/dev/null 2>&1; then |
||||
JOBS=$(nproc) |
||||
else |
||||
JOBS=$(sysctl -n hw.ncpu) |
||||
fi |
||||
|
||||
# 4. Сборка |
||||
echo "[4/4] Сборка..." |
||||
make -j"$JOBS" 2>&1 | tee build.log |
||||
|
||||
echo |
||||
echo "=== Готово ===" |
||||
echo "Бинарный файл: src/utun" |
||||
@ -0,0 +1,126 @@
|
||||
#!/bin/bash |
||||
# Direct build script for uTun on Windows with OpenSSL |
||||
# Инкрементальная сборка (не пересобирает неизменённые файлы) |
||||
|
||||
set -e |
||||
|
||||
echo "uTun Direct Build Script for Windows with OpenSSL (Incremental)" |
||||
echo "==============================================================" |
||||
echo "" |
||||
|
||||
# ====================== OpenSSL paths ====================== |
||||
if [ -d "/c/msys64/ucrt64/include/openssl" ]; then |
||||
export CFLAGS="$CFLAGS -I/c/msys64/ucrt64/include" |
||||
export LDFLAGS="$LDFLAGS -L/c/msys64/ucrt64/lib" |
||||
echo "Using MSYS2 UCRT64 OpenSSL" |
||||
elif [ -d "/ucrt64/include/openssl" ]; then |
||||
export CFLAGS="$CFLAGS -I/ucrt64/include" |
||||
export LDFLAGS="$LDFLAGS -L/ucrt64/lib" |
||||
echo "Using MSYS2 UCRT64 OpenSSL" |
||||
elif [ -d "/mingw64/include/openssl" ]; then |
||||
export CFLAGS="$CFLAGS -I/mingw64/include" |
||||
export LDFLAGS="$LDFLAGS -L/mingw64/lib" |
||||
echo "Using MSYS2 MINGW64 OpenSSL" |
||||
fi |
||||
|
||||
# ====================== Configure (только если нужно) ====================== |
||||
if [ ! -f config.status ] || [ config.status -ot configure ]; then |
||||
echo "" |
||||
echo "Running configure..." |
||||
./configure --prefix=/usr/local --host=x86_64-w64-mingw32 --disable-dependency-tracking 2>&1 | tee configure.log |
||||
fi |
||||
|
||||
# ====================== Directories ====================== |
||||
mkdir -p src/.deps lib/.deps tests/.deps |
||||
|
||||
# ====================== Build libuasync.a (incremental) ====================== |
||||
echo "" |
||||
echo "Building libuasync.a..." |
||||
|
||||
cd lib |
||||
LIB_NEEDS_REBUILD=0 |
||||
|
||||
for src in u_async.c ll_queue.c memory_pool.c debug_config.c timeout_heap.c sha256.c platform_compat.c socket_compat.c; do |
||||
if [ -f "$src" ]; then |
||||
obj="${src%.c}.o" |
||||
if [ ! -f "$obj" ] || [ "$src" -nt "$obj" ]; then |
||||
echo " CC $src" |
||||
x86_64-w64-mingw32-gcc -DHAVE_CONFIG_H -I. -I.. -g -O2 -c "$src" -o "$obj" 2>&1 | grep -v "warning:" || true |
||||
LIB_NEEDS_REBUILD=1 |
||||
else |
||||
echo " SKIPPED $src (up to date)" |
||||
fi |
||||
fi |
||||
done |
||||
|
||||
# Пересобираем библиотеку только если что-то изменилось |
||||
if [ ! -f "libuasync.a" ] || [ $LIB_NEEDS_REBUILD -eq 1 ] || [ -n "$(find . -name '*.o' -newer libuasync.a 2>/dev/null)" ]; then |
||||
echo " AR libuasync.a" |
||||
ar cru libuasync.a *.o |
||||
ranlib libuasync.a |
||||
else |
||||
echo " SKIPPED libuasync.a (up to date)" |
||||
fi |
||||
cd .. |
||||
|
||||
# ====================== Build utun.exe (incremental) ====================== |
||||
echo "" |
||||
echo "Building utun.exe..." |
||||
|
||||
cd src |
||||
EXE_NEEDS_REBUILD=0 |
||||
|
||||
CORE_SOURCES="utun.c utun_instance.c config_parser.c config_updater.c routing_layer/route_lib.c topo_bgp.c routing_layer/routing.c tun_if.c tun_route.c transport_layer/etcp.c transport_layer/etcp_connections.c transport_layer/etcp_loadbalancer.c transport_layer/secure_channel.c transport_layer/crc32.c transport_layer/pkt_normalizer.c transport_layer/etcp_api.c tun_windows.c" |
||||
|
||||
for src in $CORE_SOURCES; do |
||||
if [ -f "$src" ]; then |
||||
obj="utun-${src%.c}.o" |
||||
if [ ! -f "$obj" ] || [ "$src" -nt "$obj" ]; then |
||||
echo " CC $src" |
||||
x86_64-w64-mingw32-gcc -DHAVE_CONFIG_H -I. -I.. -I../lib -I../src/transport_layer -I../src/routing_layer -I../tinycrypt/lib/include -I../tinycrypt/lib/source $CFLAGS -g -O2 -c "$src" -o "$obj" 2>&1 | grep -E "error:|warning:.*deprecated" || true |
||||
EXE_NEEDS_REBUILD=1 |
||||
else |
||||
echo " SKIPPED $src (up to date)" |
||||
fi |
||||
else |
||||
echo " WARNING: $src not found!" |
||||
fi |
||||
done |
||||
|
||||
# Проверяем, нужно ли линковать |
||||
LINK_NEEDED=0 |
||||
if [ ! -f "utun.exe" ] || [ $EXE_NEEDS_REBUILD -eq 1 ]; then |
||||
LINK_NEEDED=1 |
||||
else |
||||
# Проверяем все объектники и библиотеку |
||||
for obj in utun-*.o; do |
||||
if [ -f "$obj" ] && [ "$obj" -nt "utun.exe" ]; then |
||||
LINK_NEEDED=1 |
||||
break |
||||
fi |
||||
done |
||||
if [ -f "../lib/libuasync.a" ] && [ "../lib/libuasync.a" -nt "utun.exe" ]; then |
||||
LINK_NEEDED=1 |
||||
fi |
||||
fi |
||||
|
||||
if [ $LINK_NEEDED -eq 1 ]; then |
||||
echo " LINK utun.exe" |
||||
x86_64-w64-mingw32-gcc -o utun.exe utun-*.o ../lib/libuasync.a -lpthread -lm -lws2_32 -liphlpapi -lbcrypt $LDFLAGS -lcrypto 2>&1 | grep -E "error:|undefined reference" || true |
||||
else |
||||
echo " SKIPPED LINK utun.exe (up to date)" |
||||
fi |
||||
|
||||
# Копируем в корень проекта |
||||
if [ -f "utun.exe" ]; then |
||||
cp utun.exe ../utun.exe |
||||
echo "Binary updated: ../utun.exe" |
||||
fi |
||||
|
||||
cd .. |
||||
|
||||
echo "" |
||||
echo "Build completed successfully! ✅" |
||||
echo "Binary: utun.exe" |
||||
echo "" |
||||
echo "Make sure wintun.dll is in the same directory as utun.exe" |
||||
@ -0,0 +1,33 @@
|
||||
@echo off |
||||
echo uTun Full Build Script for MSYS2 UCRT64 |
||||
echo ========================================= |
||||
echo This will regenerate build system with autoreconf |
||||
echo. |
||||
|
||||
pushd "%~dp0" |
||||
|
||||
rem export http_proxy="http://192.168.29.1:9999" |
||||
rem export https_proxy="http://192.168.29.1:9999" |
||||
|
||||
|
||||
REM Default MSYS2 path |
||||
set MSYS2_PATH=C:\msys64 |
||||
|
||||
REM Use environment variable if set |
||||
if not "%MSYS2_ROOT%"=="" set MSYS2_PATH=%MSYS2_ROOT% |
||||
if not "%MSYS2%"=="" set MSYS2_PATH=%MSYS2% |
||||
|
||||
REM Check if msys2_shell.cmd exists |
||||
if not exist "%MSYS2_PATH%\msys2_shell.cmd" ( |
||||
echo MSYS2 not found at %MSYS2_PATH% |
||||
echo Please install MSYS2 from https://www.msys2.org/ |
||||
echo or set MSYS2_ROOT environment variable. |
||||
pause |
||||
exit /b 1 |
||||
) |
||||
|
||||
REM Launch MSYS2 UCRT64 shell and run build script with --full flag |
||||
echo Starting MSYS2 UCRT64 environment for FULL REBUILD... |
||||
echo Running: bash build.sh --full |
||||
echo. |
||||
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "set -o pipefail; bash build.sh --full 2>&1 | tee build_full.log" |
||||
@ -0,0 +1,27 @@
|
||||
#!/bin/sh |
||||
mkdir -p m4 |
||||
set -e |
||||
|
||||
echo "=== Полная пересборка uTun ===" |
||||
|
||||
# 1. Очистка |
||||
echo "[1/4] Очистка..." |
||||
make distclean 2>/dev/null || true |
||||
rm -f config.h config.status config.log |
||||
rm -f configure.log autoreconf.log build.log |
||||
|
||||
# 2. Автогенерация (autoreconf) |
||||
echo "[2/4] Автогенерация (autoreconf -fi)..." |
||||
autoreconf -fi 2>&1 | tee autoreconf.log |
||||
|
||||
# 3. Конфигурация |
||||
echo "[3/4] Конфигурация..." |
||||
./configure --prefix=/usr/local 2>&1 | tee configure.log |
||||
|
||||
# 4. Сборка |
||||
echo "[4/4] Сборка..." |
||||
make -j$(nproc) 2>&1 | tee build.log |
||||
|
||||
echo "" |
||||
echo "=== Готово ===" |
||||
echo "Бинарный файл: src/utun" |
||||
@ -0,0 +1,124 @@
|
||||
#!/bin/bash |
||||
# OpenWRT build script for uTun |
||||
# Usage: ./build_openwrt.sh [TARGET] [PROFILE] |
||||
# |
||||
# Examples: |
||||
# ./build_openwrt.sh # ASUS RT-AX53U (default) |
||||
# ./build_openwrt.sh ramips/mt7621 asus_rt-ax53u |
||||
# ./build_openwrt.sh ath79/generic tplink_archer-c7-v2 |
||||
# ./build_openwrt.sh x86/64 |
||||
# ./build_openwrt.sh --list # list available targets |
||||
# |
||||
# First run builds the full toolchain (30-120 min). |
||||
# Subsequent runs only rebuild the utun package. |
||||
|
||||
set -e |
||||
|
||||
OPENWRT_ROOT=/home/user/openwrt |
||||
|
||||
# --- Default target: ASUS RT-AX53U --- |
||||
TARGET="${1:-ramips/mt7621}" |
||||
PROFILE="${2:-asus_rt-ax53u}" |
||||
J="${J:-$(nproc)}" |
||||
|
||||
# --- Help / list --- |
||||
if [ "$1" = "--list" ] || [ "$1" = "-l" ]; then |
||||
echo "=== Predefined targets ===" |
||||
echo " ramips/mt7621 asus_rt-ax53u ASUS RT-AX53U (default)" |
||||
echo " ramips/mt7621 asus_rt-ax54 ASUS RT-AX54" |
||||
echo " ramips/mt7621 xiaomi_mi-router-4a-gigabit Xiaomi Mi Router 4A" |
||||
echo " ath79/generic tplink_archer-c7-v2 TP-Link Archer C7 v2" |
||||
echo " x86/64 Generic x86_64 PC" |
||||
echo " x86/generic Generic x86 PC" |
||||
echo "" |
||||
echo "Full target list: grep 'TARGET_DEVICES' $OPENWRT_ROOT/target/linux/*/image/*.mk" |
||||
exit 0 |
||||
fi |
||||
|
||||
# --- Check OpenWRT exists --- |
||||
if [ ! -d "$OPENWRT_ROOT" ]; then |
||||
echo "ERROR: OpenWRT not found at $OPENWRT_ROOT" |
||||
echo "Clone: git clone --depth 1 https://github.com/openwrt/openwrt.git $OPENWRT_ROOT" |
||||
exit 1 |
||||
fi |
||||
|
||||
# --- Check utun package exists --- |
||||
if [ ! -f "$OPENWRT_ROOT/package/utun2/Makefile" ]; then |
||||
echo "ERROR: utun package not found in OpenWRT" |
||||
echo "Expected: $OPENWRT_ROOT/package/utun2/Makefile" |
||||
exit 1 |
||||
fi |
||||
|
||||
echo "============================================" |
||||
echo " uTun OpenWRT Build" |
||||
echo " Target: $TARGET" |
||||
echo " Profile: $PROFILE" |
||||
echo " Jobs: $J" |
||||
echo "============================================" |
||||
|
||||
cd "$OPENWRT_ROOT" |
||||
|
||||
# --- Select target --- |
||||
TARGET_NAME=$(echo "$TARGET" | cut -d/ -f1) |
||||
SUBTARGET_NAME=$(echo "$TARGET" | cut -d/ -f2) |
||||
|
||||
echo "" |
||||
echo "[1/3] Configuring target..." |
||||
|
||||
# Write .config |
||||
cat > .config << EOF |
||||
CONFIG_TARGET_${TARGET_NAME}=y |
||||
CONFIG_TARGET_${TARGET_NAME}_${SUBTARGET_NAME}=y |
||||
CONFIG_TARGET_${TARGET_NAME}_${SUBTARGET_NAME}_DEVICE_${PROFILE}=y |
||||
CONFIG_PACKAGE_utun=m |
||||
# Basic system |
||||
CONFIG_PACKAGE_libc=y |
||||
CONFIG_PACKAGE_libopenssl=y |
||||
CONFIG_PACKAGE_libpthread=y |
||||
CONFIG_ALL_NONSHARED=n |
||||
CONFIG_ALL_KMODS=n |
||||
CONFIG_ALL=n |
||||
CONFIG_DEVEL=y |
||||
CONFIG_CCACHE=y |
||||
# Build options |
||||
CONFIG_BUILD_LOG=y |
||||
EOF |
||||
|
||||
echo " Target: ${TARGET_NAME}/${SUBTARGET_NAME} Device: ${PROFILE}" |
||||
|
||||
# Expand config |
||||
make defconfig V=s 2>/dev/null || true |
||||
|
||||
echo "" |
||||
echo "[2/3] Building toolchain (if needed)..." |
||||
if [ ! -f "staging_dir/toolchain-*/bin/mips-openwrt-linux-gcc" ] && \ |
||||
[ ! -f "staging_dir/toolchain-*/bin/mipsel-openwrt-linux-gcc" ] && \ |
||||
[ ! -f "staging_dir/toolchain-*/bin/x86_64-openwrt-linux-gcc" ]; then |
||||
echo " Toolchain not found. Building... (this may take 30-120 min)" |
||||
make tools/install -j$J V=s 2>&1 | tail -5 |
||||
make toolchain/install -j$J V=s 2>&1 | tail -5 |
||||
echo " Toolchain built." |
||||
else |
||||
echo " Toolchain already built. Skipping." |
||||
fi |
||||
|
||||
echo "" |
||||
echo "[3/3] Building utun package..." |
||||
make package/utun2/compile -j$J V=s 2>&1 | tail -10 |
||||
|
||||
# --- Show result --- |
||||
IPK=$(find bin/packages -name "utun_*.ipk" 2>/dev/null | head -1) |
||||
if [ -n "$IPK" ]; then |
||||
echo "" |
||||
echo "============================================" |
||||
echo " BUILD SUCCESS!" |
||||
echo " Package: $IPK" |
||||
echo " Size: $(du -h "$IPK" | cut -f1)" |
||||
echo "============================================" |
||||
else |
||||
echo "" |
||||
echo "ERROR: Package not found. Check build log above." |
||||
# Try to find any built binary |
||||
find bin -name "utun*" 2>/dev/null |
||||
exit 1 |
||||
fi |
||||
@ -0,0 +1,60 @@
|
||||
@echo off |
||||
setlocal EnableDelayedExpansion |
||||
|
||||
echo ========================================== |
||||
echo uTun Test Suite - Windows |
||||
echo ========================================== |
||||
echo. |
||||
|
||||
set "TESTS_DIR=tests" |
||||
set "LOGS_DIR=tests\logs" |
||||
set "PASSED=0" |
||||
set "FAILED=0" |
||||
set "TOTAL=0" |
||||
|
||||
:: Create logs directory if not exists |
||||
if not exist "%LOGS_DIR%" mkdir "%LOGS_DIR%" |
||||
|
||||
:: List of all test executables |
||||
set "TEST_LIST=test_etcp_crypto test_crypto test_etcp_two_instances test_etcp_simple_traffic test_etcp_minimal test_etcp_100_packets test_pkt_normalizer_etcp test_pkt_normalizer_standalone test_etcp_api test_ll_queue test_ecc_encrypt test_intensive_memory_pool test_memory_pool_and_config test_packet_dump test_u_async_comprehensive test_u_async_performance test_debug_categories test_config_debug test_route_lib test_bgp_route_exchange bench_timeout_heap bench_uasync_timeouts" |
||||
|
||||
echo Running tests... |
||||
echo. |
||||
|
||||
for %%t in (%TEST_LIST%) do ( |
||||
set /a TOTAL+=1 |
||||
set "TEST_EXE=%TESTS_DIR%\%%t.exe" |
||||
set "LOG_FILE=%LOGS_DIR%\%%t.log" |
||||
|
||||
if exist "!TEST_EXE!" ( |
||||
echo [RUN] %%t.exe |
||||
"!TEST_EXE!" > "!LOG_FILE!" 2>&1 |
||||
if !ERRORLEVEL! equ 0 ( |
||||
echo [PASS] %%t.exe |
||||
set /a PASSED+=1 |
||||
) else ( |
||||
echo [FAIL] %%t.exe ^(exit code: !ERRORLEVEL!^) |
||||
set /a FAILED+=1 |
||||
) |
||||
) else ( |
||||
echo [SKIP] %%t.exe ^(not found^) |
||||
) |
||||
) |
||||
|
||||
echo. |
||||
echo ========================================== |
||||
echo Test Summary |
||||
echo ========================================== |
||||
echo Total: %TOTAL% |
||||
echo Passed: %PASSED% |
||||
echo Failed: %FAILED% |
||||
echo. |
||||
|
||||
if %FAILED% equ 0 ( |
||||
echo All tests PASSED! |
||||
exit /b 0 |
||||
) else ( |
||||
echo Some tests FAILED! |
||||
echo Check logs in %LOGS_DIR% for details |
||||
exit /b 1 |
||||
) |
||||
@ -0,0 +1,67 @@
|
||||
# cm_invite_pending — утечка указателей и corruption при destroy |
||||
|
||||
## Корень проблемы |
||||
|
||||
У `cm_invite_pending` три внешних держателя указателя в разных подсистемах: |
||||
|
||||
uasync heap (overall_timer) → node->arg = inv |
||||
NCD (node_conn_direct) → arg = inv в cm_invite_ncd_callback |
||||
STCP (stcp_link) → arg = inv в cm_tcp_ready_cb |
||||
|
||||
Когда cm_invite_cancel делает u_free(inv), коллбэки от уже запланированных |
||||
событий приходят после освобождения → пишут в переиспользованную память → |
||||
corruption (float-значения в полях mgr/node_id). |
||||
|
||||
## Решение |
||||
|
||||
1. Поле uint8_t cancelled:1 в struct cm_invite_pending |
||||
(рядом с tcp_ready:1 в conn_mgr_priv.h) |
||||
|
||||
2. cm_invite_cancel: |
||||
- inv->cancelled = 1 первой же строкой |
||||
- отмена таймера, закрытие ncd/tcp handles |
||||
- удаление из invite_list |
||||
- uasync_post(ua, cm_invite_free_deferred, inv) вместо немедленного u_free |
||||
|
||||
3. Все коллбэки — guard первой строкой: |
||||
void cm_invite_ncd_callback(..., void* arg) { |
||||
struct cm_invite_pending* inv = arg; |
||||
if (inv->cancelled) return; |
||||
... |
||||
} |
||||
|
||||
4. cm_invite_fail: |
||||
- if (inv->cancelled) return |
||||
- сохранить cb/handle/node_id/group_id ДО вызова cm_invite_cancel |
||||
- cm_invite_cancel(inv) (cancelled=1, deferred free) |
||||
- cb(handle, ...) (коллбэк после cancel, inv ещё жив) |
||||
- u_free(handle) |
||||
|
||||
## Почему корректно |
||||
|
||||
cm_invite_cancel: |
||||
inv->cancelled = 1 ← блокировка коллбэков (однопоточно, немедленно) |
||||
uasync_cancel_timeout ← таймер удалён из heap |
||||
node_conn_direct_close ← handle закрыт |
||||
uasync_post(free, inv) ← освобождение в конец очереди событий |
||||
|
||||
[уже запланированные коллбэки]: |
||||
if (inv->cancelled) return ← выход без действий |
||||
|
||||
[deferred free]: |
||||
u_free(inv) ← все коллбэки уже отработали |
||||
|
||||
## Краевые случаи |
||||
|
||||
conn_mgr_destroy → cm_invite_cancel: |
||||
Отмена таймера, закрытие handles, deferred free. Ни один коллбэк |
||||
не дёрнет освобождённую память. |
||||
|
||||
NCD/STCP коллбэк после cancel: |
||||
cancelled=1 → мгновенный return. |
||||
|
||||
Таймаут приглашения после cancel: |
||||
cancelled=1 → return, не вызывает cm_invite_fail повторно. |
||||
|
||||
uasync_destroy до deferred free: |
||||
inv утекает при shutdown (приемлемо). |
||||
@ -0,0 +1,138 @@
|
||||
AC_INIT([utun],[2.0.0],[https://github.com/anomalyco/utun3/issues]) |
||||
AC_CONFIG_SRCDIR([src/utun.c]) |
||||
AC_CONFIG_HEADERS([config.h]) |
||||
AC_CONFIG_AUX_DIR([build-aux]) |
||||
AC_CONFIG_MACRO_DIR([m4]) |
||||
|
||||
# === ОБЯЗАТЕЛЬНО для правильного определения host === |
||||
AC_CANONICAL_HOST |
||||
AC_CANONICAL_BUILD |
||||
|
||||
# Required programs |
||||
AC_PROG_CC |
||||
AC_PROG_RANLIB |
||||
AM_PROG_AR |
||||
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall]) |
||||
|
||||
AC_PROG_INSTALL |
||||
AC_PROG_MAKE_SET |
||||
|
||||
# Option to use OpenSSL |
||||
AC_ARG_WITH([openssl], |
||||
AS_HELP_STRING([--with-openssl], [Use OpenSSL for cryptography (default: yes)]), |
||||
[with_openssl=$withval], |
||||
[with_openssl=yes]) |
||||
|
||||
if test "x$with_openssl" = "xyes"; then |
||||
# MSYS2 OpenSSL paths (для Windows) |
||||
case "$host" in |
||||
*mingw* | *msys* | *cygwin* | *win*) |
||||
if test -d "/c/msys64/ucrt64/include/openssl"; then |
||||
OPENSSL_CFLAGS="-I/c/msys64/ucrt64/include" |
||||
OPENSSL_LIBS="-L/c/msys64/ucrt64/lib -lcrypto" |
||||
AC_MSG_NOTICE([Using MSYS2 UCRT64 OpenSSL]) |
||||
elif test -d "/ucrt64/include/openssl"; then |
||||
OPENSSL_CFLAGS="-I/ucrt64/include" |
||||
OPENSSL_LIBS="-L/ucrt64/lib -lcrypto" |
||||
AC_MSG_NOTICE([Using MSYS2 UCRT64 OpenSSL]) |
||||
elif test -d "/mingw64/include/openssl"; then |
||||
OPENSSL_CFLAGS="-I/mingw64/include" |
||||
OPENSSL_LIBS="-L/mingw64/lib -lcrypto" |
||||
AC_MSG_NOTICE([Using MSYS2 MINGW64 OpenSSL]) |
||||
fi |
||||
CFLAGS="$CFLAGS $OPENSSL_CFLAGS" |
||||
LDFLAGS="$LDFLAGS $OPENSSL_LIBS" |
||||
;; |
||||
esac |
||||
|
||||
save_LIBS="$LIBS" |
||||
LIBS="-lcrypto $LIBS" |
||||
AC_CHECK_LIB([crypto], [SHA256_Init], |
||||
[AC_DEFINE([USE_OPENSSL], [1], [Define to use OpenSSL for cryptography])], |
||||
[AC_MSG_ERROR([OpenSSL crypto library required. Use --without-openssl to use TinyCrypt.])]) |
||||
LIBS="$save_LIBS" |
||||
fi |
||||
|
||||
AM_CONDITIONAL([USE_OPENSSL], [test "x$with_openssl" = "xyes"]) |
||||
|
||||
# ==================== HARDENING ==================== |
||||
AC_ARG_ENABLE([hardening], |
||||
AS_HELP_STRING([--enable-hardening], [Enable compiler security hardening (FORTIFY, stack protector, PIE, RELRO)]), |
||||
[enable_hardening=$enableval], |
||||
[enable_hardening=no]) |
||||
|
||||
if test "x$enable_hardening" = "xyes"; then |
||||
HARDEN_CFLAGS="-D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -Wformat=2 -Wformat-overflow=2 -fPIE" |
||||
HARDEN_LDFLAGS="-Wl,-z,relro -Wl,-z,now -pie" |
||||
CFLAGS="$CFLAGS $HARDEN_CFLAGS" |
||||
LDFLAGS="$LDFLAGS $HARDEN_LDFLAGS" |
||||
AC_MSG_NOTICE([Hardening flags enabled: $HARDEN_CFLAGS]) |
||||
fi |
||||
|
||||
# ==================== НАДЁЖНЫЙ ДЕТЕКТ WINDOWS ==================== |
||||
AC_MSG_CHECKING([for Windows]) |
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ |
||||
#ifndef _WIN32 |
||||
#error "Not Windows" |
||||
#endif |
||||
]], [[ return 0; ]])], |
||||
[os_windows=yes], |
||||
[os_windows=no]) |
||||
AC_MSG_RESULT([$os_windows]) |
||||
|
||||
AM_CONDITIONAL([OS_WINDOWS], [test "x$os_windows" = "xyes"]) |
||||
|
||||
# ==================== DETECT FREEBSD ==================== |
||||
AC_MSG_CHECKING([for FreeBSD]) |
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ |
||||
#ifndef __FreeBSD__ |
||||
#error "Not FreeBSD" |
||||
#endif |
||||
]], [[ return 0; ]])], |
||||
[os_freebsd=yes], |
||||
[os_freebsd=no]) |
||||
AC_MSG_RESULT([$os_freebsd]) |
||||
|
||||
AM_CONDITIONAL([OS_FREEBSD], [test "x$os_freebsd" = "xyes"]) |
||||
|
||||
# Set TUN libs based on platform |
||||
if test "x$os_windows" = "xyes"; then |
||||
TUN_LIBS="-liphlpapi -lws2_32 -ladvapi32 -lbcrypt -ldbghelp" |
||||
else |
||||
TUN_LIBS="" |
||||
fi |
||||
AC_SUBST([TUN_LIBS]) |
||||
|
||||
# Checks for header files, typedefs, functions... |
||||
AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h netinet/in.h stdlib.h string.h sys/ioctl.h sys/socket.h unistd.h]) |
||||
AC_TYPE_SIZE_T |
||||
AC_TYPE_SSIZE_T |
||||
AC_TYPE_UINT16_T |
||||
AC_TYPE_UINT32_T |
||||
AC_TYPE_UINT64_T |
||||
AC_TYPE_UINT8_T |
||||
AC_C_INLINE |
||||
|
||||
AC_FUNC_MALLOC |
||||
AC_CHECK_FUNCS([gettimeofday memset socket strchr strdup strerror strstr]) |
||||
|
||||
# TUN/TAP (только для Linux) |
||||
AC_MSG_CHECKING([for TUN/TAP support]) |
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <linux/if_tun.h>]], [[int fd = open("/dev/net/tun", O_RDWR);]])], |
||||
[AC_MSG_RESULT([yes]); AC_DEFINE([HAVE_TUN_TAP], [1], [Define if TUN/TAP is available])], |
||||
[AC_MSG_RESULT([no])]) |
||||
|
||||
# Output |
||||
AC_CONFIG_FILES([Makefile src/Makefile tests/Makefile lib/Makefile lib/libopus/Makefile]) |
||||
AC_OUTPUT |
||||
|
||||
# Summary |
||||
echo " |
||||
Configuration summary: |
||||
---------------------- |
||||
Host: ${host} |
||||
Windows: ${os_windows} |
||||
Prefix: ${prefix} |
||||
Compiler: ${CC} |
||||
CFLAGS: ${CFLAGS} |
||||
" |
||||
@ -0,0 +1,285 @@
|
||||
#!/bin/bash |
||||
# cross-build-win.sh — кросс-компиляция uTun для Windows 10 из Linux |
||||
# Использование: ./cross-build-win.sh [--clean] |
||||
set -euo pipefail |
||||
|
||||
RED='\033[0;31m' |
||||
GREEN='\033[0;32m' |
||||
YELLOW='\033[1;33m' |
||||
CYAN='\033[0;36m' |
||||
NC='\033[0m' |
||||
|
||||
step() { echo -e "${CYAN}===${NC} $*"; } |
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; } |
||||
warn() { echo -e "${YELLOW}[WARNING]${NC} $*"; } |
||||
fatal() { echo -e "${RED}FATAL:${NC} $*" >&2; exit 1; } |
||||
|
||||
trap 'fatal "script failed at line $LINENO (exit code: $?)"' ERR |
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
||||
BUILD_DIR="$SCRIPT_DIR/build-win" |
||||
PREFIX="$BUILD_DIR/prefix" |
||||
|
||||
MINGW_CC="x86_64-w64-mingw32-gcc" |
||||
MINGW_AR="x86_64-w64-mingw32-ar" |
||||
MINGW_RANLIB="x86_64-w64-mingw32-ranlib" |
||||
|
||||
MINGW_PKGS="gcc-mingw-w64-x86-64-posix g++-mingw-w64-x86-64-posix" |
||||
OPENSSL_VER="3.0.16" |
||||
OPENSSL_URL="https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VER}/openssl-${OPENSSL_VER}.tar.gz" |
||||
|
||||
# ------------------------------------------------------------------- |
||||
# 0. Аргументы |
||||
# ------------------------------------------------------------------- |
||||
CLEAN=0 |
||||
while [[ $# -gt 0 ]]; do |
||||
case "$1" in |
||||
--clean) CLEAN=1; shift ;; |
||||
*) fatal "unknown option: $1" ;; |
||||
esac |
||||
done |
||||
|
||||
if [ "$CLEAN" -eq 1 ]; then |
||||
echo -e "${YELLOW}Cleaning build directory: $BUILD_DIR${NC}" |
||||
rm -rf "$BUILD_DIR" |
||||
fi |
||||
|
||||
echo -e "${CYAN}==============================================${NC}" |
||||
echo -e "${CYAN} uTun Cross-Compilation for Windows 10${NC}" |
||||
echo -e "${CYAN}==============================================${NC}" |
||||
echo "" |
||||
|
||||
# ------------------------------------------------------------------- |
||||
# 1. Проверка системы |
||||
# ------------------------------------------------------------------- |
||||
step "Checking system..." |
||||
if [ "$(uname -s)" != "Linux" ]; then |
||||
fatal "This script only runs on Linux. Detected: $(uname -s)" |
||||
fi |
||||
if [ "$(uname -m)" != "x86_64" ]; then |
||||
fatal "This script requires x86_64 architecture. Detected: $(uname -m)" |
||||
fi |
||||
|
||||
if ! command -v apt-get &>/dev/null; then |
||||
fatal "apt-get not found. Only Debian/Ubuntu are supported." |
||||
fi |
||||
ok "System: Linux x86_64 with apt" |
||||
|
||||
# ------------------------------------------------------------------- |
||||
# 2. Установка пакетов |
||||
# ------------------------------------------------------------------- |
||||
step "Installing required packages..." |
||||
|
||||
need_install=() |
||||
for pkg in $MINGW_PKGS; do |
||||
if ! dpkg -s "$pkg" &>/dev/null 2>&1; then |
||||
need_install+=("$pkg") |
||||
fi |
||||
done |
||||
if ! command -v wget &>/dev/null && ! command -v curl &>/dev/null; then |
||||
need_install+=("wget") |
||||
fi |
||||
|
||||
if [ ${#need_install[@]} -gt 0 ]; then |
||||
echo " Installing: ${need_install[*]}" |
||||
sudo apt-get update -qq || fatal "apt-get update failed" |
||||
sudo apt-get install -y -qq "${need_install[@]}" || fatal "apt-get install failed" |
||||
ok "Packages installed: ${need_install[*]}" |
||||
else |
||||
ok "All packages already installed" |
||||
fi |
||||
|
||||
# Проверка компилятора |
||||
for tool in "$MINGW_CC" "$MINGW_AR" "$MINGW_RANLIB"; do |
||||
if ! command -v "$tool" &>/dev/null; then |
||||
fatal "$tool not found after package installation" |
||||
fi |
||||
done |
||||
ok "Cross-compiler: $($MINGW_CC --version | head -1)" |
||||
|
||||
# ------------------------------------------------------------------- |
||||
# 3. Кросс-компиляция OpenSSL |
||||
# ------------------------------------------------------------------- |
||||
OPENSSL_SRC="$BUILD_DIR/openssl-${OPENSSL_VER}" |
||||
LIBCRYPTO="$PREFIX/lib/libcrypto.a" |
||||
|
||||
if [ -f "$LIBCRYPTO" ]; then |
||||
ok "OpenSSL already built: $LIBCRYPTO" |
||||
else |
||||
step "Building OpenSSL ${OPENSSL_VER} for Windows..." |
||||
|
||||
mkdir -p "$BUILD_DIR" |
||||
|
||||
TARBALL="$BUILD_DIR/openssl-${OPENSSL_VER}.tar.gz" |
||||
if [ ! -f "$TARBALL" ]; then |
||||
echo " Downloading OpenSSL ${OPENSSL_VER}..." |
||||
if command -v wget &>/dev/null; then |
||||
wget -q --show-progress -O "$TARBALL" "$OPENSSL_URL" || fatal "wget download failed for $OPENSSL_URL" |
||||
else |
||||
curl -L --progress-bar -o "$TARBALL" "$OPENSSL_URL" || fatal "curl download failed for $OPENSSL_URL" |
||||
fi |
||||
ok "Downloaded: $TARBALL" |
||||
fi |
||||
|
||||
if [ ! -d "$OPENSSL_SRC" ]; then |
||||
echo " Extracting..." |
||||
tar xzf "$TARBALL" -C "$BUILD_DIR" || fatal "tar extract failed" |
||||
fi |
||||
|
||||
echo " Configuring OpenSSL for mingw64..." |
||||
cd "$OPENSSL_SRC" |
||||
./Configure --cross-compile-prefix=x86_64-w64-mingw32- \ |
||||
mingw64 no-shared no-tests \ |
||||
--prefix="$PREFIX" --libdir="$PREFIX/lib" --openssldir="$PREFIX/ssl" \ |
||||
|| fatal "OpenSSL configure failed (see output above)" |
||||
ok "OpenSSL configured" |
||||
|
||||
echo " Compiling OpenSSL..." |
||||
make -j"$(nproc)" || fatal "OpenSSL make failed" |
||||
echo " Installing OpenSSL..." |
||||
make install_sw >/dev/null || fatal "OpenSSL make install_sw failed" |
||||
cd "$SCRIPT_DIR" |
||||
|
||||
# OpenSSL может установить в lib64/ вместо lib/ — поправим |
||||
if [ ! -f "$LIBCRYPTO" ] && [ -f "$PREFIX/lib64/libcrypto.a" ]; then |
||||
ln -sf lib64 "$PREFIX/lib" 2>/dev/null || cp -r "$PREFIX/lib64" "$PREFIX/lib" 2>/dev/null || true |
||||
fi |
||||
if [ ! -f "$LIBCRYPTO" ]; then |
||||
fatal "libcrypto.a was not found at $PREFIX/lib/ or $PREFIX/lib64/" |
||||
fi |
||||
ok "OpenSSL built: $LIBCRYPTO" |
||||
fi |
||||
|
||||
# ------------------------------------------------------------------- |
||||
# 4. Сборка libuasync.a |
||||
# ------------------------------------------------------------------- |
||||
step "Building libuasync.a..." |
||||
|
||||
LIB_OBJDIR="$BUILD_DIR/lib-objs" |
||||
mkdir -p "$LIB_OBJDIR" |
||||
|
||||
# Все .c файлы из lib/Makefile.am + myip.c (реализация get_my_ip) |
||||
LIB_SOURCES=( |
||||
u_async.c ll_queue.c memory_pool.c debug_config.c timeout_heap.c |
||||
sha256.c mem.c serialize.c swm_min.c getmyip.c radix.c tcp_io.c |
||||
myip.c platform_compat.c socket_compat.c |
||||
liblmdb/mdb.c liblmdb/midl.c |
||||
) |
||||
|
||||
LIB_CFLAGS="-D_ISOC99_SOURCE -DDEBUG_OUTPUT_STDERR -DUSE_OPENSSL" |
||||
LIB_CFLAGS+=" -I$SCRIPT_DIR/lib -I$SCRIPT_DIR/src -I$SCRIPT_DIR/lib/liblmdb" |
||||
LIB_CFLAGS+=" -I$PREFIX/include" |
||||
LIB_CFLAGS+=" -g -O2 -Wno-deprecated-declarations" |
||||
|
||||
for src in "${LIB_SOURCES[@]}"; do |
||||
obj="$LIB_OBJDIR/$(basename "${src%.c}.o")" |
||||
echo " CC lib/$src" |
||||
"$MINGW_CC" $LIB_CFLAGS -c "$SCRIPT_DIR/lib/$src" -o "$obj" \ |
||||
|| fatal "compilation failed: lib/$src" |
||||
done |
||||
|
||||
"$MINGW_AR" crs "$BUILD_DIR/libuasync.a" "$LIB_OBJDIR"/*.o \ |
||||
|| fatal "ar failed for libuasync.a" |
||||
"$MINGW_RANLIB" "$BUILD_DIR/libuasync.a" 2>/dev/null || true |
||||
ok "libuasync.a ($(wc -c < "$BUILD_DIR/libuasync.a") bytes)" |
||||
|
||||
# ------------------------------------------------------------------- |
||||
# 5. Сборка объектных файлов для utun.exe |
||||
# ------------------------------------------------------------------- |
||||
step "Compiling utun source files..." |
||||
|
||||
SRC_OBJDIR="$BUILD_DIR/src-objs" |
||||
mkdir -p "$SRC_OBJDIR" |
||||
|
||||
# Все .c файлы из src/Makefile.am (utun_CORE_SOURCES) |
||||
SRC_SOURCES=( |
||||
utun.c utun_instance.c config_parser.c config_updater.c |
||||
routing_layer/route_lib.c routing_layer/route6_lib.c topo_bgp.c routing_layer/route_ping.c |
||||
routing_layer/topo_node.c topo_node_lmdb.c routing_layer/route_connectivity.c routing_layer/conn_mgr.c |
||||
routing_layer/routing.c tun_if.c tun_route.c tun_linux.c tun_freebsd.c tun_windows.c |
||||
transport_layer/etcp.c transport_layer/etcp_connections.c transport_layer/etcp_bbr.c transport_layer/etcp_loadbalancer.c |
||||
transport_layer/etcp_debug.c transport_layer/etcp_dump.c transport_layer/secure_channel.c transport_layer/crc32.c |
||||
transport_layer/stcp_link.c transport_layer/stcp.c transport_layer/stcp_server.c transport_layer/stcp_client.c |
||||
transport_layer/pkt_normalizer.c transport_layer/packet_dump.c transport_layer/etcp_api.c |
||||
control_server.c |
||||
firewall.c eim_nat.c nat_transport.c transport_layer/dummynet.c |
||||
etcp_router.c |
||||
proxy/udp_proxy.c proxy/socks_proxy.c |
||||
lwip_tcp/lwip_pbuf.c |
||||
) |
||||
|
||||
SRC_CFLAGS="-DUSE_OPENSSL" |
||||
SRC_CFLAGS+=" -I$SCRIPT_DIR/src -I$SCRIPT_DIR/lib -I$SCRIPT_DIR/lib/liblmdb" |
||||
SRC_CFLAGS+=" -I$SCRIPT_DIR/src/transport_layer -I$SCRIPT_DIR/src/routing_layer" |
||||
SRC_CFLAGS+=" -I$SCRIPT_DIR/tinycrypt/lib/include -I$SCRIPT_DIR/tinycrypt/lib/source" |
||||
SRC_CFLAGS+=" -I$PREFIX/include" |
||||
SRC_CFLAGS+=" -g -O2 -Wno-deprecated-declarations" |
||||
|
||||
TOTAL=${#SRC_SOURCES[@]} |
||||
CURRENT=0 |
||||
FAILED=() |
||||
|
||||
for src in "${SRC_SOURCES[@]}"; do |
||||
CURRENT=$((CURRENT + 1)) |
||||
# Имя объектного файла: заменяем / на _ (для proxy/ и lwip_tcp/) |
||||
obj_name="$(echo "$src" | tr '/' '_')" |
||||
obj="$SRC_OBJDIR/${obj_name%.c}.o" |
||||
printf " [%3d/%3d] CC src/%s\n" "$CURRENT" "$TOTAL" "$src" |
||||
if ! "$MINGW_CC" $SRC_CFLAGS -c "$SCRIPT_DIR/src/$src" -o "$obj" 2>&1; then |
||||
FAILED+=("src/$src") |
||||
fi |
||||
done |
||||
|
||||
if [ ${#FAILED[@]} -gt 0 ]; then |
||||
echo "" |
||||
fatal "compilation failed for: ${FAILED[*]}" |
||||
fi |
||||
ok "All $TOTAL source files compiled" |
||||
|
||||
# ------------------------------------------------------------------- |
||||
# 6. Линковка utun.exe |
||||
# ------------------------------------------------------------------- |
||||
step "Linking utun.exe..." |
||||
|
||||
"$MINGW_CC" -o "$SCRIPT_DIR/utun.exe" \ |
||||
"$SRC_OBJDIR"/*.o \ |
||||
"$BUILD_DIR/libuasync.a" \ |
||||
"$LIBCRYPTO" \ |
||||
-lws2_32 -liphlpapi -ladvapi32 -lbcrypt -lcrypt32 -lpthread -lm \ |
||||
-static \ |
||||
|| fatal "linking failed (see undefined references above)" |
||||
|
||||
ok "utun.exe ($(wc -c < "$SCRIPT_DIR/utun.exe") bytes)" |
||||
|
||||
# ------------------------------------------------------------------- |
||||
# 7. Копирование wintun.dll и финал |
||||
# ------------------------------------------------------------------- |
||||
echo "" |
||||
if [ -f "$SCRIPT_DIR/lib/wintun.dll" ]; then |
||||
cp "$SCRIPT_DIR/lib/wintun.dll" "$SCRIPT_DIR/" |
||||
ok "wintun.dll copied to project root" |
||||
else |
||||
warn "wintun.dll not found in lib/ — place it manually alongside utun.exe" |
||||
fi |
||||
|
||||
echo "" |
||||
echo -e "${GREEN}==============================================${NC}" |
||||
echo -e "${GREEN} Build successful!${NC}" |
||||
echo -e "${GREEN} Binary: utun.exe${NC}" |
||||
echo -e "${GREEN}==============================================${NC}" |
||||
echo "" |
||||
|
||||
if [ -f "$SCRIPT_DIR/lib/wintun.dll" ]; then |
||||
echo "Run on Windows:" |
||||
echo " 1. Install Wintun driver (https://www.wintun.net)" |
||||
echo " 2. Copy utun.exe + wintun.dll to the same directory" |
||||
echo " 3. Run: utun.exe -f -p utun.pid -l utun.log" |
||||
else |
||||
echo "Run on Windows:" |
||||
echo " 1. Install Wintun driver (https://www.wintun.net)" |
||||
echo " 2. Copy utun.exe + wintun.dll to the same directory" |
||||
echo " 3. Run: utun.exe -f -p utun.pid -l utun.log" |
||||
fi |
||||
echo "" |
||||
|
||||
exit 0 |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,35 @@
|
||||
План доработок |
||||
[ ] продумать архитектуру репликации таблицы узлов группы |
||||
[ ] добавить узлы хелперы (для проксирования трафика) |
||||
|
||||
|
||||
Категории узлов: |
||||
- узлы без нат [] |
||||
- узлы с nat [restricted] |
||||
|
||||
Узлы за nat сами подключаются к прямым |
||||
|
||||
Узлы за eim nat сами подключаются и сообщают обновление адреса. |
||||
|
||||
Прямые узлы должны сами должны объединиться в сеть следующим образом: |
||||
|
||||
Инит: база содержит RTT по узлам. |
||||
выбираем n=16 узлов с минимальным rtt. запускаем подключение к ним. |
||||
синхронизируем роутинг таблицу с ними (обмен маршрутами должен инициироваться сразу после подключения). |
||||
при потере связи узлы через nat должны найти альтернативы. |
||||
|
||||
в таблице обновляется |
||||
|
||||
как обмен завершен |
||||
|
||||
Нужно собирать и обновлять информацию о том |
||||
- к каким публичным нодам подключены nat узлы. |
||||
|
||||
- метрика доступности узла - как давно не менялся ip:port - собираем с клиентов. вместе с рейтигном (каждый клиент по узлу фолрмирует свой рейтинг кандидата в суперузлы и отправляет подписанный рейтинг узлу) |
||||
рейтинг включает: |
||||
- число разных часов фигурирует в замерах |
||||
- число разных дат фигурирует в замерах |
||||
- гистограмма rtt: 0-2ms, 2-4ms, 4-6ms, 6-10ms, 10-13ms, 14-18ms, 18-25ms, 25-35ms, 35-50ms, 50-75ms, 75-100ms, 100-130ms, 130-160ms, 160+ms |
||||
- гистограмма потерь: 0-1% 1-2% 2-4% 4-8% >8% (обновляется каждый час) |
||||
|
||||
Сделать подключение к группе |
||||
@ -0,0 +1,292 @@
|
||||
# DB Sync Protocol v2 — Техническое задание |
||||
|
||||
## 1. Проблема |
||||
|
||||
chain_hash = SHA256(prev_chain_hash || id || ts || datahash) — хеш-цепочка, где хеш каждой записи зависит от предыдущей. |
||||
|
||||
`db_record_insert` (db_sync.c:341-463) при вставке записи в середину сортированного порядка делает каскадный пересчёт chain_hash всех последующих записей — O(N). При двустороннем обмене PUSH'ами получается O(N²). |
||||
|
||||
## 2. Решение |
||||
|
||||
**Разделить два концерна:** |
||||
- **Сравнение при синхронизации** — перейти на `datahash` (первые 8 байт SHA256(data), позиционно-независимый). Не требует каскада. |
||||
- **Целостность цепочки** — `chain_hash` остаётся в схеме БД, но используется только для однократной стартап-проверки и диагностики. |
||||
|
||||
**Синхронизация:** per-peer позиционная (`synced_pos`). При PUSH каскад не делается. При SEND_DATA батч-вставка + один каскад после батча. |
||||
|
||||
## 3. Модель данных |
||||
|
||||
### 3.1. SI_PEER |
||||
|
||||
```c |
||||
struct SI_PEER { |
||||
uint64_t node_id; |
||||
uint32_t synced_pos; // последняя подтверждённо общая позиция (0-based index) |
||||
uint8_t sync_state; // 0=not_synced, 1=syncing, 2=synced |
||||
}; |
||||
``` |
||||
|
||||
- `synced_pos` = индекс последней записи в глобальном порядке `ORDER BY timestamp, datahash`, для которой chain_hash (а после v2 — datahash) подтверждённо совпадает у обоих пиров. |
||||
- При PUSH-вставке записи на позицию P, для всех peer'ов у которых `synced_pos >= P`: `synced_pos = P - 1`. |
||||
- При SEND_DATA: `synced_pos = from + received_count - 1`. |
||||
|
||||
### 3.2. Схема БД (без изменений) |
||||
|
||||
```sql |
||||
CREATE TABLE "db_sync_<name>_<id>" ( |
||||
timestamp INTEGER NOT NULL, |
||||
datahash INTEGER NOT NULL, |
||||
id INTEGER NOT NULL, |
||||
chain_hash BLOB NOT NULL, |
||||
author INTEGER NOT NULL, |
||||
flags INTEGER NOT NULL DEFAULT 0, |
||||
data BLOB, |
||||
author_signature BLOB, |
||||
delivered_peers INTEGER NOT NULL DEFAULT 0, |
||||
delivery_chain TEXT NOT NULL DEFAULT '', |
||||
PRIMARY KEY (timestamp, datahash) |
||||
); |
||||
``` |
||||
|
||||
## 4. Wire-формат |
||||
|
||||
### 4.1. Сообщения, которые меняются |
||||
|
||||
#### INIT_SYNC |
||||
|
||||
``` |
||||
Было: [type:1][count:4][last_chain_hash:32] = 37 байт |
||||
Стало: [type:1][count:4] = 5 байт |
||||
``` |
||||
|
||||
#### INIT_RESP |
||||
|
||||
``` |
||||
Было: [type:1][tp:4][chain_at_tp:32][sc:1][(pos:4,chain_hash:32)*sc] |
||||
Стало: [type:1][tp:4][dh_at_tp:8] [sc:1][(pos:4,dh:8)*sc] |
||||
|
||||
dh_at_tp — datahash на позиции tp (8 байт вместо 32) |
||||
sparse — datahash вместо chain_hash (8 байт на элемент вместо 32) |
||||
|
||||
При sc=0 (exact match): длина 1+4+8+1 = 14 байт (было 37) |
||||
При sc=16 sparse: длина 14 + 16*12 = 206 байт (было 37 + 16*36 = 613) |
||||
``` |
||||
|
||||
#### SYNC_DONE |
||||
|
||||
``` |
||||
Было: [type:1][count:4][last_chain_hash:32] = 37 байт |
||||
Стало: [type:1][count:4][last_dh:8] = 13 байт |
||||
``` |
||||
|
||||
### 4.2. Сообщения, которые НЕ меняются |
||||
|
||||
- **PUSH**: `[type:1][id:8][ts:8][dh:8][dlen:4][data][sig_len:1][sig]` — без изменений. `prev_ch` НЕ добавляется. |
||||
- **ACK_PUSH**: `[type:1][dh:8][ts:8]` — без изменений. |
||||
- **REFINE**: `[type:1][from:4][to:4][hc:1][(pos:4,dh:8)*hc]` — уже использует datahash, без изменений. |
||||
- **SEND_DATA**: `[type:1][from:4][count:2][(id:8,ts:8,dh:8,dlen:4,data,sig_len:1,sig)*count]` — без изменений. |
||||
- **ERROR**: `[type:1][code:1]` — без изменений. |
||||
|
||||
## 5. Новые/изменённые функции |
||||
|
||||
### 5.1. `db_cascade_from` |
||||
|
||||
```c |
||||
static void db_cascade_from(struct DB_SYNC_INSTANCE* si, uint32_t from_pos); |
||||
``` |
||||
|
||||
Выполняет каскадный пересчёт `chain_hash` для записей с позиции `from_pos` до конца таблицы. |
||||
|
||||
Алгоритм: |
||||
1. BEGIN IMMEDIATE |
||||
2. SELECT chain_hash записи на позиции `from_pos - 1` (или zero если from_pos = 0) как prev_ch |
||||
3. SELECT id, timestamp, datahash от позиции from_pos до конца |
||||
4. Для каждой: chain_hash = SHA256(prev_ch || id || ts || dh), UPDATE в БД, prev_ch = новый chain_hash |
||||
5. COMMIT |
||||
|
||||
Выделяется из текущего кода `db_record_insert` (строки 412-451) в отдельную функцию. |
||||
|
||||
### 5.2. `db_record_insert` — новый параметр |
||||
|
||||
```c |
||||
static int db_record_insert(struct DB_SYNC_INSTANCE* si, |
||||
uint64_t id, uint64_t ts, uint64_t dh, |
||||
const char* json, size_t jlen, |
||||
const uint8_t* sig, size_t sig_len, |
||||
int do_cascade); |
||||
``` |
||||
|
||||
- `do_cascade=1` — после INSERT выполняется cascade (строки 412-451). Используется для локальных вставок. |
||||
- `do_cascade=0` — без cascade. Используется для PUSH и SEND_DATA (каскад делается отдельно). |
||||
|
||||
### 5.3. `db_verify_chain` (стартап-проверка) |
||||
|
||||
```c |
||||
static void db_verify_chain(struct DB_SYNC_INSTANCE* si); |
||||
``` |
||||
|
||||
Вызывается один раз после `db_sync_instance_add` (перед инициацией sync с пирами). |
||||
|
||||
Алгоритм: |
||||
1. Проход записей 0..N-1 в порядке `ORDER BY timestamp, datahash` |
||||
2. Для каждой: chain_hash = SHA256(prev_ch || id || ts || dh) |
||||
3. Сравнить с хранимым в БД |
||||
4. При первом расхождении на позиции P: пересчитать цепочку от P до конца через `db_cascade_from(si, P)`. Завершить. |
||||
|
||||
Сложность: O(N) SHA256, однократно при старте. |
||||
|
||||
### 5.4. `si_find_pos` (новая) |
||||
|
||||
```c |
||||
static uint32_t si_find_pos(struct DB_SYNC_INSTANCE* si, |
||||
uint64_t ts, uint64_t dh); |
||||
``` |
||||
|
||||
Возвращает позицию (0-based index) записи с ключом `(ts, dh)` в глобальном порядке. Используется после вставки PUSH для корректировки `synced_pos`. |
||||
|
||||
### 5.5. `db_datahash_at` — уже существует (стр. 295) |
||||
|
||||
Возвращает `datahash` на заданной позиции. Используется для сравнения в INIT_SYNC/INIT_RESP/SYNC_DONE. |
||||
|
||||
## 6. Обработчики сообщений |
||||
|
||||
### 6.1. PUSH (`db_handle_push`) |
||||
|
||||
``` |
||||
Было: db_record_insert(si, ..., /* cascade встроен */) |
||||
Стало: db_record_insert(si, ..., /* do_cascade= */ 0) |
||||
uint32_t pos = si_find_pos(si, ts, dh); |
||||
for each peer: if peer->synced_pos >= pos: peer->synced_pos = pos - 1; |
||||
``` |
||||
|
||||
### 6.2. INIT_SYNC (`db_handle_init_sync`) |
||||
|
||||
``` |
||||
Изменения: |
||||
- Принимает [count:4] вместо [count:4][chain_hash:32] (проверка len >= 5 вместо 36) |
||||
- Сравнение: datahash вместо chain_hash (8 байт вместо 32) |
||||
- sparse: datahash вместо chain_hash (8 байт на элемент вместо 32) |
||||
- Размер sparse-элемента: 4(pos) + 8(dh) = 12 байт (было 4+32=36) |
||||
- resp буфер: 4096 → достаточно (14 + 16*12 = 206 байт при max sparse) |
||||
``` |
||||
|
||||
### 6.3. INIT_RESP (`db_handle_init_resp`) |
||||
|
||||
``` |
||||
Изменения: |
||||
- Принимает [tp:4][dh:8][sc:1][...] вместо [tp:4][ch:32][sc:1][...] |
||||
- Проверка len >= 13 вместо 37 |
||||
- Сравнение: datahash вместо chain_hash |
||||
- Пустая БД пира: zero-хеш 8 байт вместо 32 |
||||
- Sparse элементы: 12 байт вместо 36 |
||||
- При совпадении: synced_pos = tp (в дополнение к sync_state = 2) |
||||
``` |
||||
|
||||
### 6.4. SEND_DATA (`db_handle_send_data`) |
||||
|
||||
``` |
||||
Стало: |
||||
struct SI_PEER* sp = si_peer_find(si, src); |
||||
uint32_t fix_from = sp ? sp->synced_pos : 0; |
||||
|
||||
for each record: |
||||
db_record_insert(si, ..., /* do_cascade= */ 0); |
||||
|
||||
db_cascade_from(si, fix_from); |
||||
|
||||
sp->synced_pos = from + received_count - 1; |
||||
sp->sync_state = 2; |
||||
``` |
||||
|
||||
Каскад от `fix_from` (позиция, которая была synced ДО этого батча) гарантирует, что все chain_hash после этой точки пересчитаны — независимо от того, какие PUSH'и испортили их между синхронизациями. |
||||
|
||||
### 6.5. SYNC_DONE (`db_handle_sync_done`) |
||||
|
||||
``` |
||||
Изменения: |
||||
- Принимает [count:4][last_dh:8] вместо [count:4][last_chain_hash:32] (len >= 13 вместо 36) |
||||
- Сравнение последнего datahash вместо chain_hash |
||||
``` |
||||
|
||||
## 7. Механика многопировой синхронизации |
||||
|
||||
При наличии нескольких пиров с одинаковым `sync_state` (например, syncing=1) выбирается пир с минимальным `synced_pos` — у него самый старый общий префикс. После завершения его синхронизации выбор повторяется. |
||||
|
||||
Это гарантирует: двигаемся от самого старого несинхронизированного участка, не прыгая. Новые записи (в хвосте) синхронизируются последними. |
||||
|
||||
## 8. Полный сценарий: три участника A, B, C |
||||
|
||||
``` |
||||
Начальное состояние: все пусты. |
||||
|
||||
=== Шаг 1. A создаёт R1 (ts=1000) === |
||||
A: PUSH R1 → B, C. Все вставляют в конец, cascade 0. |
||||
synced_pos = 0 у всех. |
||||
|
||||
=== Шаг 2. B создаёт R2 (ts=2000), C создаёт R3 (ts=1500) === |
||||
B: [R1(1000), R2(2000)], PUSH R2 → A, C. |
||||
A: R2 в конец. C: R2 в конец. |
||||
C: [R1(1000), R2(2000)]? Нет — C создал R3(1500). |
||||
C: [R1(1000), R3(1500)] локально, потом PUSH R2 → R2.ts=2000 → в конец. |
||||
C: [R1(1000), R3(1500), R2(2000)] |
||||
C: PUSH R3 → A, B. |
||||
A: [R1(1000), R2(2000)] + R3(1500) → между R1 и R2: |
||||
A: [R1, R3, R2] ← R3 в середину, cascade ПРОПУЩЕН |
||||
B: [R1, R3, R2] ← аналогично |
||||
|
||||
synced_pos после PUSH R3 на A относительно B: |
||||
A вставил R3 на поз.1 → synced_pos_B: было 1 → стало 0 |
||||
(A считает, что совпадает с B только на позиции 0) |
||||
|
||||
=== Шаг 3. A инициирует sync с B (чемпион: min synced_pos) === |
||||
A→B: INIT_SYNC [count=3] |
||||
B→A: INIT_RESP [tp=min(3,3)-1=2, dh_at_tp=H2, sc=2, sparse:{(1,H3),(0,H1)}] |
||||
A проверяет: A@2=R2→dh=H2✓, A@1=R3→dh=H3✓, A@0=R1→dh=H1✓ |
||||
→ sync complete, synced_pos_B=2, sync_state=2 |
||||
|
||||
=== Шаг 4. Рестарт A === |
||||
db_verify_chain: |
||||
pos 0: R1.ch = SHA256(0||R1) ✓ |
||||
pos 1: R3.ch = SHA256(R1.ch||R3) ✓ (посчитан при PUSH без cascade — корректен) |
||||
pos 2: R2.ch = SHA256(R3.ch||R2) ✗ (STALE: был посчитан как SHA256(R1.ch||R2) до PUSH R3) |
||||
→ расхождение на pos=2 → db_cascade_from(2): пересчёт R2.ch = SHA256(R3.ch||R2) ✓ |
||||
|
||||
=== Шаг 5. A, B, C активно обмениваются === |
||||
Каждый PUSH: вставка без cascade. |
||||
Периодические sync (по min synced_pos): SEND_DATA + один cascade от synced_pos. |
||||
synced_pos растёт. |
||||
``` |
||||
|
||||
## 9. Что НЕ трогать |
||||
|
||||
- `db_sync_insert_signed` — локальная вставка. ts всегда монотонный, запись в конец, `do_cascade=1`, каскад всегда 0 строк. |
||||
- TTL cleanup (`db_sync_instance_ttl_cb`) — удаление старых записей. Нужен cascade после удаления (уже существующая логика, не меняется). |
||||
- ACK_PUSH, ERROR, REFINE — без изменений. |
||||
- Схема БД — без изменений. |
||||
- `etcp_bind`/эпилог — без изменений. |
||||
|
||||
## 10. Сложность операций (итого) |
||||
|
||||
| Операция | Каскад | Сложность | |
||||
|----------|--------|-----------| |
||||
| Локальная вставка | 0 строк (конец) | O(1) | |
||||
| PUSH (приём) | нет | O(1) | |
||||
| SEND_DATA (батч N записей) | 1 каскад после батча | O(N + tail) | |
||||
| Стартап-проверка | 1 раз | O(total) | |
||||
| Sync-протокол (INIT_SYNC/INIT_RESP/REFINE) | нет cascade | O(log N) сообщений | |
||||
|
||||
## 11. Порядок реализации |
||||
|
||||
1. **Выделить `db_cascade_from(si, from_pos)`** из тела `db_record_insert`. |
||||
2. **Добавить параметр `do_cascade`** в `db_record_insert`. При `0` — только INSERT, cascade вызывается отдельно. |
||||
3. **Добавить `synced_pos`** в `struct SI_PEER`. Инициализировать в 0. |
||||
4. **Добавить `si_find_pos(si, ts, dh)`** — поиск позиции записи в глобальном порядке. |
||||
5. **Изменить wire-формат** INIT_SYNC, INIT_RESP, SYNC_DONE (chain_hash → datahash, 32→8 байт). |
||||
6. **Изменить обработчики**: |
||||
- `db_handle_init_sync`: новые размеры, datahash вместо chain_hash |
||||
- `db_handle_init_resp`: новые размеры, datahash вместо chain_hash, обновлять synced_pos |
||||
- `db_handle_send_data`: `do_cascade=0`, `db_cascade_from(fix_from)`, обновить synced_pos |
||||
- `db_handle_push`: `do_cascade=0`, скорректировать все synced_pos |
||||
- `db_handle_sync_done`: обновить synced_pos |
||||
7. **Добавить `db_verify_chain(si)`** — вызвать при старте после `db_sync_instance_add`. |
||||
8. **Многопировая синхронизация**: в `db_sync_peer_check_cb` выбирать пира с min synced_pos вместо первого попавшегося. |
||||
@ -0,0 +1,85 @@
|
||||
Архитектура etcp: |
||||
|
||||
ETCP устанавливает только одно подключение между двумя узлами. Это подключение может добавлять-убирать каналы связи (динамически) без разрыва соединения. |
||||
Также каналы связи мониторятся и трафик распределяется между доступными каналами. Если канал определяется как недоступен - он исключается из обмена. |
||||
Все каналы периодически мониторятся на доступность (keepalive). |
||||
|
||||
Роутинг. |
||||
utun поддерживает встроенный ipv4 роутинг, может через промежуточные узлы доставлять пакеты. |
||||
У каждого узла есть свои маршруты. Эти маршруты при подключении распространяются по остальным узлам. |
||||
Связь между узлами: |
||||
Каждый узел имеет настройку NAT для каждого listen сокета: public / nat / private. |
||||
public - нет nat, можно подключаться напрямую |
||||
nat - есть правильный nat с static port (т.е. можно подключаться напрямую зная присвоенный ip:port) |
||||
private - нельзя подключаться |
||||
Приоритетный способ обмена данными - прямое подключение к узлу. Если один из узлов имеет не private интерфейс то создается подключение через него. |
||||
Если не удается подключиться напрямую - выбирается переадча через промежуточный узел с наилучшим качеством канала (считается из задержки, числа хопов, bandwidth, потерь) |
||||
Для установки прямого соединения шлётся запрос удаленному узлу через существующие соединения на запрос подключения. |
||||
Запрос включает мой id, pubkey, доступные каналы связи и их [ip:port]. удаленный узел запоминает эти данные и отправляет response с такими же данными. |
||||
При получении request или response узел смотрит каких подключений (links) еще нет и пробует инициировать на подходщих линках с узлом. |
||||
Таким образом будет инициировано подключение между узлами по всем доступным линкам. |
||||
Если у каждого из узлов есть более одного канала то создаётся m * n подключений (каждый сокет с каждым). которые дальше работают как обычно - балансировщик нагрузки далее в процессе выбирает как лучше отправить очередной пакет (больше вариантов - можно найти путь лучше). |
||||
Есть статические подключения (которые прописаны в конфиге) и есть динамические - которые устанавливаются в процессе по необходимости. |
||||
Обмен маршрутами происходит только через статические подключения. |
||||
|
||||
|
||||
## etcp_connections: |
||||
- обслуживает encrypt/decrypt и установку защищенного подключения per-link. |
||||
- для одного etcp соединения может испольоваться несколько подключений одновременно (load balancing / filover) |
||||
|
||||
## etcp_loadbalancer: |
||||
- выбирает через какой маршрут отправить пакет |
||||
- ограничивает rate отправки чтобы не забивать очереди каналов |
||||
- сделан для совместной работы с etcp модулем |
||||
|
||||
## pkt_normalizer: |
||||
- обеспечивает фрагментацию и сборку пакетов в единый поток для передачи через etcp. |
||||
- взаимодейстует с etcp и очередями для приема и передачи. |
||||
- в очередь для передачи может отправить любой модуль |
||||
- из очереди приёма пакеты забирает etcp_api |
||||
|
||||
## etcp_api: |
||||
- обеспечивает связь между etcp и остальными модулями |
||||
- имеет функцию которая забирает из normalizer принятые данные и перенаправляет их на нужный модуль |
||||
- имеет функцию connector_bind которой можно установить коллбэк для нужного типа кодограмм (первый байт). |
||||
|
||||
## etcp: |
||||
- обеспечивает ретрансмиссии при передаче и сборку в правильной последовательности при приёме |
||||
- взаимодействует с pkt_normalizer и с udp сокетами для отправки-получения пакетов |
||||
|
||||
## stcp_* : |
||||
- аналогично etcp только упрощенный вариант, используя tcp в качестве транспорта. |
||||
|
||||
## etcp_router: |
||||
- устанавливает связь между узлами, маршрутизирует трафик через цепочку узлов |
||||
|
||||
## route_lib, route6_lib : |
||||
- управляет таблицами маршрутизации (ipv4/ipv6 lookup) |
||||
|
||||
## route_node: |
||||
- хранит список доступных узлов (полученные от соседей) и их адреса/pubkeys для подключения. |
||||
- содержит апи для поиска-чтения-обновления узла |
||||
|
||||
## route_bgp: |
||||
- составляет карту маршрутизации между узлами и обновляет ее при подключении-отключении узлов |
||||
|
||||
## route_ping: |
||||
- модуль для определения типа nat (strict или eim) используя вспомогательный узел |
||||
|
||||
Библиотеки: |
||||
## uasync |
||||
- модуль для асинхронной работы. |
||||
реализует сокеты, таймауты и сигналы из других потоков |
||||
- быстро работает с большим числом таймаутов (timeout_heap) |
||||
|
||||
## ll_queue |
||||
- модуль работы с очередями, использует uasync. |
||||
- реализует fifo/lifo очереди, быстрый поиск по индексу, различные callback - сигнализация о состояниях очереди, |
||||
- congestion control: если много желающих записать -> очередь round-robin |
||||
|
||||
|
||||
Тесты: |
||||
# Правила работы с очередями в тестах: |
||||
- для добавления в очередь надо использовать queue_waiter_wait и queue_set_threshold, добавлять по одному пакету, каждый раз дожидаясь когда очередь станет пустой. |
||||
- для получения надо использовать queue_set_callback + queue_resume_callback |
||||
|
||||
@ -0,0 +1,10 @@
|
||||
В конфиге явно прописываются сокеты с ip и портом. |
||||
Через эти сокеты идёт весь трафик, случайные порты клиент не использует - только эти сокеты. |
||||
Соответственно, возможен только один линк между двумя пирами по одному каналу. |
||||
|
||||
виртуальные сокеты - плохо т.к. используются ip/port для поиск подключения |
||||
|
||||
|
||||
|
||||
./configure --with-openssl - openssl |
||||
./configure --without-openssl - tinycrypt |
||||
@ -0,0 +1,189 @@
|
||||
Формат кодограммы: |
||||
------------------ |
||||
пакеты идут по следующему пути: |
||||
пакет кем-то помещается в входную очередь (ETCP input_queue). |
||||
Когда load balancer готов отправить следующий пакет он делает вызов etcp_request_pkt в etcp.c/h и etcp либо формирует следующую кодограмму или встаёт в состояние async ожидания (возвращая null): |
||||
- либо wait ETCP input_queue (если нечего передавать) |
||||
- либо wait SACK + wait timeout (если sack очередь полная и нет необслуженных запросов ретрансмиссий). после истечения wait timeout он помечает используя round-robin очередной пакет как "нужна ретрансмиссия". |
||||
из async ожидания он может вызвать etcp_loadbalancer input_from_etcp и передать пакет на отправку. |
||||
как формируется пакет: |
||||
1. выбираем канал передачи (etcp_loadbalancer_select_link) |
||||
2. если есть место в rwin (объём inflight данных) то берем очередной пакет из ETCP input_queue и его отправляем |
||||
3. если пакет найден или есть неподтверждённые ACK: |
||||
1. вызываем функцию формирования опциональных секций (ACK, channel timestamp) - они записываются в начало |
||||
2. в конец добавляем секцию 0x00 с данными пакета |
||||
при отправке пакета помечаем в inflight списке с какого интерфейса он отправлен |
||||
Функция прикрепления опциональных секций: |
||||
- запрашивает выбор канала передачи. |
||||
- добавляет накопившиеся ACK |
||||
При приёме пакета (от etcp_connections): |
||||
- последовательно сканируем секции и отдаём их на обработку нужным обработчикам: |
||||
- ack (0x01): помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также снимаем блокировку отправки |
||||
- timestamp (0x06): обновляем last RTT, пересчитываем RTT avg10 и jitter по каждому линку; обновляем recv_dt_avg для раздельного измерения времени передачи/приёма |
||||
- [0x00] payload: добавляем пакет в нужное место сборочного linked-list (сверяем по ID если дубликат - игнорируем). также добавляем ACK запись в ack_q для отправки. |
||||
после сканирования проверяем появились ли в linked-list собранные данные которые можно переместить в выходную очередь. и перемещаем если есть. |
||||
|
||||
пересканирование inflight: |
||||
- запускается при добавлении пакета в очередь ожидания ACK (wait_ack_cb). устанавливается таймер на первый неистекший пакет. |
||||
- если время последней отправки > RTT_avg10*K1+jitter*K2 (коэффициенты K1=32, K2=32, деление на 16) то пакет заново отправляется |
||||
- очередь FIFO (сортирована по timestamp добавления = времени отправки), сканирование прекращается на первом неистекшем пакете |
||||
- по истечению таймера (ack_timeout_cb) снова вызывается проверка очереди |
||||
|
||||
inflight - две очереди (ll_queue): |
||||
- input_send_q: список пакетов ожидающих отправку |
||||
- input_wait_ack: список неподтвержденных пакетов ожидающих ack |
||||
При отправке пакет перемещается из input_send_q в input_wait_ack. |
||||
При ретрансмиссии пакет перемещается из input_wait_ack обратно в input_send_q. |
||||
доп. свойства пакетов (struct INFLIGHT_PACKET): |
||||
- указатель на ETC_LINK последней отправки (last_link) |
||||
- timestamp последней отправки (last_timestamp) |
||||
- число отправок (send_count) |
||||
- send_hist[8] - история через какие линки отправлялся (индекс линка) |
||||
- inflight_bytes/inflight_packets per-link отслеживается в ETCP_LINK |
||||
Размер буфера inflight лимитируем как сумму по всем активным линкам optimal_inflight. |
||||
optimal_inflight расчитывается из RTT и bandwidth линка. |
||||
|
||||
bandwidth по каждому линку адаптивно подстраиваем: |
||||
Периодически в stats_timer_cb собираем статистику окна (rtt_avg10, ретрансмиссии, переданные пакеты). |
||||
BW рассчитывается как: inflight_lim_bytes * 8 / rtt_min (в секундах) * 8 / 1024 * 1.3 (Kbits/sec). |
||||
Размер inflight_lim_bytes плавно подстраивается вверх/вниз в зависимости от того, растёт ли RTT. |
||||
Burst-измерения bandwidth (0x07, 0x08) зарезервированы, но на данный момент не реализованы. |
||||
|
||||
|
||||
**** Формат кодограмм для etcp.c/h **** |
||||
Каждая кодограмма состоит из обязательного заголовка и опциональных секций: |
||||
1. Обязательный заголовок всего пакета (3 байта) добавляется при передаче, есть во всех пакетах: |
||||
[Timestamp high][Timestamp low][flags/flag_up] |
||||
- Timestamp: uint16_t, время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется |
||||
- flag_up: uint8_t, bit0 = up/down (recv_keepalive) — признак живой приёмной стороны |
||||
|
||||
шифруется строго ВСЁ включая ВСЕ заголовки (кроме обфусцированного publickey в INIT-пакетах). |
||||
timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ (скрыто в шифрованном заголовке). |
||||
Итого шифруется: 3 байта заголовка + (data_len - noencrypt_len) байт данных. |
||||
2. Опциональные секции (одна или несколько) добавляются в etcp.c (внутри data[], без доп. заголовка): |
||||
а) Подтверждения (ACK) - заголовок 0x01: |
||||
[0x01][count][last_delivered_id(4 байта LE)][rx_dup_count(2 байта LE)][[seq(4 LE)][recv_ts(2 LE)][dly(2 LE)] × count] |
||||
- count: количество ACK записей (1 байт; до 32 ограничено размером пакета) |
||||
- last_delivered_id: uint32_t — последний ID, доставленный получателю (по этот ID всё собрано подряд) |
||||
- rx_dup_count: uint16_t — счётчик принятых дубликатов (локальный, для вычисления tx_dup_count на удалённой стороне) |
||||
- seq: uint32_t — номер подтверждаемого пакета |
||||
- recv_ts: uint16_t — локальный timestamp приёма пакета (в единицах 0.1 мс) |
||||
- dly: uint16_t — задержка между приёмом пакета и отправкой ACK (для расчёта RTT) |
||||
Каждая запись = 8 байт. |
||||
в) channel timestamp (добавляется после выбора канала передачи, если есть свежие данные): |
||||
[0x06] [RET_Timestamp(2 байта LE)] [RECV_Rel_Timestamp(2 байта LE)] |
||||
- RET_Timestamp: uint16_t = last_recv_timestamp + (now - last_recv_local_time) |
||||
(оценка текущего времени на передающей стороне в момент отправки) |
||||
- RECV_Rel_Timestamp: uint16_t = last_recv_local_time - last_recv_timestamp |
||||
(относительное смещение между локальным и удалённым временем) |
||||
Всего 5 байт. Добавляется только если last_recv_updated=1 (был принят пакет с момента последней отправки) |
||||
и если прошло не более ~100ms (dt < 1000000 таймбаз). |
||||
3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя): |
||||
[0x00] [ID 4 байта LE] [данные...] |
||||
- ID: uint32_t, 32-битный циклический номер пакета |
||||
Пакет может содержать несколько опциональных секций (например, ACK + channel_timestamp + данные), |
||||
всегда в порядке: ACK, channel_timestamp, payload; payload всегда последний. |
||||
Разбор секций — последовательное сканирование с известными размерами (нет явного поля длины секции). |
||||
|
||||
цель протокола: |
||||
1. контролировать состояние очередей (не накапливать данные где этого можно избежать), анализировать RTT и inflight - динамически подстраивать размер окна и ретрансмиссии, утилизируя сеть насколько это возможно но не допуская наполнения буферов в сети (что приводит к бестолковому увеличению RTT) |
||||
|
||||
Установка подключения для канала (реализуется в etcp_connections.c/h): |
||||
каждое ETCP подключение может содержать несколько линков до endpoint (разные маршруты, каналы связи). |
||||
При прохождении трафика через этот модуль подсчитываются метрики (для каждого канала отдельно): |
||||
- RTT последнего пакета и время отправки последнего пакета |
||||
- общее количество отправленных/принятых байт |
||||
- количество перезапросов (по каждому каналу отдельно) в пакетах. когда etcp модуль принял запрос ретрансмиссии он смотрит с какого интерфейса был отправлен этот пакет (это запоминается при отправке) и увеличивает счетчики. |
||||
- обновление текущего количества неподтвержденных данных inflight (в пакетах и байтах) - сколько по этому каналу отправленных и неподтвержденных на текущий момент |
||||
Планировщик/балансир каналов (etcp_loadbalancer.c/h): |
||||
для каждого канала ограничение полосы с тестированием отклика: |
||||
- полосу ограничиваем по объему данных - полному числу байт ethernet пакта (добавляем размер заголовков) |
||||
Ограничение реализуем следующим образом: |
||||
- в структуре есть счётчик времени (квантизация стандартная 0.1ms) по которое канал загружен данными. И суб-разряды (nanotime) этого счетчика 0.1ns - 0.1ms (считает по модулю 1000000 и переполнения добавляет в основной счетчик) чтобы при высоких скоростях не было потери точности. |
||||
Этим счётчиком контролируется полоса: при передаче к нему добавляется расчётное время передачи текущего ethernet пакета (используя текущий bandwidth считаем время передачи одного байта (float) и умножаем на число байт, переводим в таймбазу nanotime). А если при передаче время меньше current_time - delta_time (delta_time - константа в define, x0.1ms) то он устанавливается как current time-delta_time. (при неактивности обновляем время) |
||||
счетчик нужен для контроля можно ли передавать следующий пакет ("расчётное время передачи"); |
||||
когда etcp пытается отправить пакет через линк, то если в линке не инициализировано подключение и он клиент - то он отбрасывает этот пакет и запускает процесс установки соединения. |
||||
также процесс установки соединения инициируется при добавлении канала (в etcp_link_new) если это client. |
||||
процесс установки соединения (рукопожатие): |
||||
- клиент отправляет init запрос и выставляет таймаут |
||||
- по таймауту повторяет init запросы с экспоненциально растущим интервалом (начальный 500ms, макс 50s, +25% каждые 10 попыток), ведется счетчик запросов init. |
||||
- когда клиент получает init подтверждение — снимает таймаут, выставляет link_state=connected, запускает keepalive. |
||||
- сервер при получении init запроса: если линк существует и session_id совпал — отвечает без сброса (0x05); если session_id новый — выполняет etcp_conn_reinit и отвечает со сбросом (0x03). |
||||
При добавлении нового канала: если link_state=1 (handshake) — посылаем INIT со сбросом (0x02); если link_state=2 (reconnect) — без сброса (0x04). |
||||
link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected. |
||||
|
||||
**** Формат кодограмм для etcp_connections.c/h **** |
||||
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда одна секция-тип пакета). В обязательном заголовке (timestamp+flag_up) ID не используется. |
||||
|
||||
Типы пакетов: |
||||
0x02 — ETCP_INIT_REQUEST (со сбросом etcp сессии) |
||||
0x03 — ETCP_INIT_RESPONSE (подтверждение со сбросом) |
||||
0x04 — ETCP_INIT_REQUEST_NOINIT (без сброса) |
||||
0x05 — ETCP_INIT_RESPONSE_NOINIT (подтверждение без сброса) |
||||
0x06 — ETCP_PING (проверка доступности) |
||||
0x07 — ETCP_PONG (ответ на пинг) |
||||
|
||||
1) Init запрос (0x02 / 0x04): |
||||
[code(1)] [my_node_id(8 BE)] [session_id(4 BE)] [mtu_local(2)] [keepalive_interval(2)] |
||||
[recovery_interval/100(2)] [local_link_id(1)] [sock_id(1)] [only_local(1)] [type(1)] |
||||
[random_padding...] [salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)] |
||||
- code: 0x02 (со сбросом) или 0x04 (без сброса) |
||||
- node_id: 64 бита, идентификатор узла (big-endian) |
||||
- session_id: 32 бита, случайный ID сессии генерируется клиентом (big-endian) |
||||
- mtu_local: uint16_t локальный MTU |
||||
- keepalive_interval: uint16_t интервал keepalive в мс |
||||
- recovery_interval: uint16_t = recovery_interval/100 (в таймбазах 0.1мс / 100) |
||||
- local_link_id: uint8_t (0-255), назначается отправителем |
||||
- sock_id: uint8_t, уникальный ID сокета отправителя |
||||
- only_local: uint8_t, флаг "только локальные подключения" |
||||
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_UNKNOWN=0/PUBLIC=1/NAT=2/PRIVATE=3/LOCAL=4) |
||||
- random_padding: случайные байты до размера handshake (защита от fingerprinting) |
||||
- salt + obfuscated_pubkey: публичный ключ с обфускацией (не шифруется AES, |
||||
но скрывается XOR с salt и собственным публичным ключом). Передаётся в конце пакета |
||||
в noencrypt_len байтах (не попадает под AES-шифрование) |
||||
Инициирует новый connection для tcp instance. Если tcp instance нет (первое подключение) - создаёт. |
||||
Между нодами только одно подключение, но можно добавлять каналы. |
||||
|
||||
2) Init подтверждение (0x03 / 0x05): |
||||
[code(1)] [my_node_id(8 BE)] [session_id(4 BE)] [mtu_local(2)] [local_link_id(1)] |
||||
[remote_socket_id(1)] [only_local(1)] [type(1)] [peer_ipv4(4)] [peer_port(2)] |
||||
[random_padding...] |
||||
- code: 0x03 (со сбросом) или 0x05 (без сброса) |
||||
- node_id: 64 бита, big-endian |
||||
- session_id: 32 бита, big-endian (должен совпасть с запросом клиента; если нет — клиент игнорирует) |
||||
- mtu_local: uint16_t локальный MTU сервера |
||||
- local_link_id: uint8_t идентификатор канала сервера |
||||
- remote_socket_id: uint8_t (копируется из запроса) |
||||
- only_local: uint8_t флаг сервера |
||||
- type: uint8_t тип сокета сервера |
||||
- peer_ipv4: 4 байта (IPv4 адрес клиента как его видит сервер — для NAT traversal) |
||||
- peer_port: 2 байта (порт клиента как его видит сервер) |
||||
- random_padding: случайные байты |
||||
|
||||
3) PING (0x06): |
||||
[0x06] [node_id(8 BE)] [nonce(8 BE)] [user_data_len(2 BE)] [user_data...] |
||||
[salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)] |
||||
Отправляется с отдельным crypto-контекстом (не связан с ETCP-сессией). |
||||
Используется для проверки доступности узла и NAT-тестирования. |
||||
|
||||
4) PONG (0x07): |
||||
[0x07] [node_id(8 BE)] [nonce(8 BE)] [resp_data_len(2 BE)] [response_data...] |
||||
[salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)] |
||||
Ответ на PING. nonce копируется из запроса для сопоставления. |
||||
|
||||
При получении init получатель пакета должен: |
||||
- reset ETCP_LINK с этим ip_port если он есть |
||||
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - проверить ключи на совпадение. |
||||
приём кодограмм из UDP выглядит так: |
||||
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (etcp.c/h - сам tcp механизм) -> ETCP_CONN output_queue |
||||
|
||||
Keepalive: пакет без секций (только timestamp+flag_up в шифрованном заголовке, data_len=0). |
||||
keepalive пакеты шлют и клиент и сервер с заданным интервалом если нет полезного трафика. |
||||
Сервер прекращает слать keepalive если линк потерян (ждёт keepalive от клиента). |
||||
flag_up в заголовке устанавливается в значение recv_keepalive (признак что локальная сторона принимает пакеты). |
||||
|
||||
Клиент: если все линки =down то начинается процедура восстановления связи: |
||||
- клиент посылает init без сброса (0x04), link_state=2 |
||||
- сервер как получает init проверяет session_id: |
||||
- если session_id совпадает — шлёт подтверждение без сброса (0x05) |
||||
- если session_id изменился (клиент перезапустился) — шлёт подтверждение со сбросом (0x03) |
||||
@ -0,0 +1,324 @@
|
||||
# ETCP Router Architecture |
||||
|
||||
## Структуры данных |
||||
|
||||
``` |
||||
ETCP_ROUTER_CONN (один на пару remote_node_id + svc_id) |
||||
┌─────────────────────────────────────────────────────┐ |
||||
│ ll_entry (хеш-индекс: remote_node_id[8] + svc_id[1]) │ |
||||
│ remote_node_id: u64 │ |
||||
│ svc_id: u8 │ |
||||
│ last_dgram_ts: u16 (timestamp последней датаграммы) │ |
||||
│ │ |
||||
│ tx_seq: u32 следующий seq для отправки │ |
||||
│ rx_seq: u32 ожидаемый seq для сборки │ |
||||
│ tx_acked: u32 сколько наших пакетов подтвердил remote │ |
||||
│ last_sent_ack_seq: u32 последний отправленный ACK │ |
||||
│ │ |
||||
│ recv_q: ll_queue* очередь reorder (хеш по seq[4]) │ |
||||
│ send_q: ll_queue* очередь при переполнении inflight │ |
||||
│ │ |
||||
│ ack_timer: void* периодический 100ms │ |
||||
│ idle_ack_timer: void* idle таймаут 500ms │ |
||||
│ send_resume_timer: void* retry send_q 50ms │ |
||||
│ send_blocked: u8 inflight полон │ |
||||
└─────────────────────────────────────────────────────┘ |
||||
|
||||
SVC_ROUTE_HDR (22 байта) |
||||
┌──────┬────────────────┬────────────────┬───────┬────────┬──────────┐ |
||||
│ cmd │ dst_node_id │ src_node_id │ seq │ svc_id │ payload │ |
||||
│ u8 │ u64 │ u64 │ u32 │ u8 │ ... │ |
||||
└──────┴────────────────┴────────────────┴───────┴────────┴──────────┘ |
||||
|
||||
seq в data-пакетах = tx_seq (счетчик отправленных) |
||||
seq в ACK-пакетах = rx_seq (ожидаемый следующий) |
||||
CLOSE-флаг = 0x80000000 в seq, RST-флаг = 0x40000000 |
||||
``` |
||||
|
||||
## Send Path (отправка данных) |
||||
|
||||
``` |
||||
etcp_route_send(inst, dst, entry) |
||||
etcp_router_conn_send(rconn, data, len) |
||||
│ |
||||
▼ |
||||
┌─────────────────────┐ |
||||
│ rconn = find/get │ поиск по (dst, svc_id) |
||||
│ авто-создание если │ или etcp_router_conn_get() |
||||
│ ещё нет │ |
||||
└────────┬────────────┘ |
||||
│ |
||||
▼ |
||||
┌──────────────────────────────┐ |
||||
│ inflight < ROUTER_MAX(256)? │ |
||||
│ tx_seq - tx_acked < 256 ? │ |
||||
└──────┬───────────┬───────────┘ |
||||
│ YES │ NO (inflight полон) |
||||
▼ ▼ |
||||
┌──────────────┐ ┌─────────────────────┐ |
||||
│router_send_one│ │ router_enqueue_send │ → send_q (FIFO) |
||||
│ (прямая) │ │ (в очередь) │ send_blocked = 1 |
||||
└──────┬────────┘ └─────────┬───────────┘ send_resume_timer (50ms) |
||||
│ │ |
||||
▼ │ |
||||
┌────────────────────┐ │ |
||||
│ tx_seq++ │ │ |
||||
│ hdr.dst = dst │ │ |
||||
│ hdr.src = node_id │ │ |
||||
│ hdr.seq = tx_seq │ │ |
||||
│ hdr.svc_id = svc │ │ |
||||
│ │ │ |
||||
│ etcp_send(conn, e) │ │ |
||||
└────────────────────┘ │ |
||||
│ |
||||
┌────────────────────────────┘ |
||||
│ (при получении ACK от remote) |
||||
▼ |
||||
┌────────────────────┐ |
||||
│ router_drain_send_q│ вызывается из ACK handler |
||||
│ │ или по send_resume_timer |
||||
│ while inflight < 256│ |
||||
│ e = send_q.pop() │ |
||||
│ router_send_one(e)│ |
||||
│ │ |
||||
│ если send_q пуст: │ |
||||
│ send_blocked = 0 │ |
||||
└────────────────────┘ |
||||
``` |
||||
|
||||
## Receive Path (приём данных) |
||||
|
||||
``` |
||||
etcp_router_recv_cb(conn, entry) |
||||
│ |
||||
▼ |
||||
┌──────────────────────┐ |
||||
│ dst == node_id? │ |
||||
└──┬───────────────┬───┘ |
||||
│ YES │ NO (транзит) |
||||
▼ ▼ |
||||
┌────────────────────┐ ┌──────────────────────────┐ |
||||
│ Мы — целевая нода │ │ Транзит: найти next hop │ |
||||
└────────┬───────────┘ │ route_bgp_find_conn_for_ │ |
||||
│ │ node(bgp, hdr.dst) │ |
||||
▼ │ etcp_send(next, entry) │ |
||||
┌────────────────────┐ └──────────────────────────┘ |
||||
│ pl_len == 0? │ |
||||
└──┬───────────┬─────┘ |
||||
│ YES │ NO (данные) |
||||
▼ ▼ |
||||
┌─────────┐ ┌─────────────────────────┐ |
||||
│ CLOSE/ │ │ rconn = find/get │ |
||||
│ RST? │ │ (авто-создание если нет) │ |
||||
└──┬──┬───┘ └───────────┬─────────────┘ |
||||
│ │ │ |
||||
│ │ NO (ACK) ▼ |
||||
│ ▼ ┌───────────────────────────────────────┐ |
||||
│ ┌──────────┐│ Проверка границ seq: │ |
||||
│ │ tx_acked ││ d = seq - rx_seq │ |
||||
│ │ = hdr.seq││ |d| < ROUTER_MAX_INFLIGHT? │ |
||||
│ │ (только ││ │ |
||||
│ │ вперёд) ││ Дубликат? │ |
||||
│ │ drain_q ││ rx_seq - seq > 0 ИЛИ │ |
||||
│ └──────────┘│ seq уже в recv_q? │ |
||||
│ └──────┬─────────────┬──────────────────┘ |
||||
│ │ OK │ out-of-bounds / dup |
||||
│ ▼ ▼ |
||||
│ ┌──────────────┐ ┌─────────┐ |
||||
│ │ recv_q.put() │ │ DROP │ |
||||
│ │ хеш по seq │ └─────────┘ |
||||
│ └──────┬───────┘ |
||||
│ │ |
||||
│ ▼ |
||||
│ ┌──────────────────────┐ |
||||
│ │ seq == rx_seq? │ |
||||
│ └──┬───────────────┬───┘ |
||||
│ │ YES │ NO |
||||
│ ▼ │ |
||||
│ ┌──────────────┐ │ |
||||
│ │ try_assembly │ │ |
||||
│ │ while next в │ │ |
||||
│ │ recv_q: │ │ |
||||
│ │ deliver() │ │ |
||||
│ │ rx_seq++ │ │ |
||||
│ └──────┬───────┘ │ |
||||
│ │ │ |
||||
│ ▼ │ |
||||
│ ┌──────────────────────┴──┐ |
||||
│ │ router_deliver() │ |
||||
│ │ вызывает callback │ |
||||
│ │ сервиса (svc_id) │ |
||||
│ │ entry: [svc_id|payload] │ |
||||
│ └──────────────┬──────────┘ |
||||
│ │ |
||||
▼ ▼ |
||||
┌────────────────────────────────────┐ |
||||
│ router_schedule_ack(rconn) │ |
||||
│ запускает ack_timer (100ms) если │ |
||||
│ ещё не запущен │ |
||||
└────────────────────────────────────┘ |
||||
``` |
||||
|
||||
``` |
||||
CLOSE / RST ветка: |
||||
┌────────────────────────────────────────┐ |
||||
│ pl_len == 0 && seq & CLOSE/RST │ |
||||
│ → router_close_and_notify(rconn) │ |
||||
│ ├─ router_send_close_to_service() │ |
||||
│ │ (callback с NULL conn) │ |
||||
│ ├─ отправить CLOSE удалённой стороне│ |
||||
│ ├─ отмена всех таймеров │ |
||||
│ ├─ очистка recv_q + send_q │ |
||||
│ └─ удаление из inst->router_conns │ |
||||
└────────────────────────────────────────┘ |
||||
``` |
||||
|
||||
## ACK Mechanism |
||||
|
||||
``` |
||||
┌─────────────────────────────────────────┐ |
||||
│ router_schedule_ack(rconn) │ |
||||
│ (вызывается при получении данных) │ |
||||
└────────────────┬────────────────────────┘ |
||||
│ |
||||
┌────────────────▼────────────────────────┐ |
||||
│ если idle_ack_timer активен — отменить │ |
||||
│ если ack_timer не активен — запустить │ |
||||
│ ROUTER_ACK_INTERVAL_TB = 1000 (100ms) │ |
||||
└────────────────┬────────────────────────┘ |
||||
│ |
||||
▼ (через 100ms) |
||||
┌─────────────────────────────────────────┐ |
||||
│ router_ack_timer_cb() │ |
||||
│ │ |
||||
│ if rx_seq != last_sent_ack_seq: │ |
||||
│ router_send_ack(rconn) │ |
||||
│ last_sent_ack_seq = rx_seq │ |
||||
│ перезапустить ack_timer │ |
||||
│ else: │ |
||||
│ запустить idle_ack_timer (500ms) │ |
||||
└────────────────┬────────────────────────┘ |
||||
│ |
||||
▼ (через 500ms) |
||||
┌─────────────────────────────────────────┐ |
||||
│ router_idle_ack_timer_cb() │ |
||||
│ │ |
||||
│ if rx_seq != last_sent_ack_seq: │ |
||||
│ router_send_ack(rconn) (досылка) │ |
||||
│ last_sent_ack_seq = rx_seq │ |
||||
└─────────────────────────────────────────┘ |
||||
|
||||
router_send_ack(rconn): |
||||
hdr.seq = rx_seq |
||||
hdr.svc_id = svc_id |
||||
pl_len = 0 → это ACK-пакет |
||||
etcp_send(conn, entry) |
||||
``` |
||||
|
||||
``` |
||||
Обработка входящего ACK: |
||||
┌────────────────────────────────────────┐ |
||||
│ pl_len == 0 && нет CLOSE/RST флагов │ |
||||
│ │ |
||||
│ if seq >= tx_acked: // только вперёд │ |
||||
│ tx_acked = seq // обновить │ |
||||
│ else: │ |
||||
│ DEBUG_WARN "stale ACK" │ |
||||
│ │ |
||||
│ if send_blocked: │ |
||||
│ router_drain_send_q(rconn) │ |
||||
└────────────────────────────────────────┘ |
||||
``` |
||||
|
||||
## Inflight Control |
||||
|
||||
``` |
||||
ROUTER_MAX_INFLIGHT = 256 |
||||
|
||||
Отправка возможна когда: tx_seq - tx_acked < 256 |
||||
Отправка заблокирована: tx_seq - tx_acked >= 256 |
||||
|
||||
При блокировке: |
||||
данные → send_q (FIFO, без дропов) |
||||
send_blocked = 1 |
||||
send_resume_timer = 50ms retry |
||||
|
||||
Разблокировка: |
||||
приход ACK от remote → tx_acked обновляется |
||||
→ router_drain_send_q() разгребает send_q |
||||
или send_resume_timer (50ms) → router_drain_send_q() |
||||
|
||||
send_q полностью разобран → send_blocked = 0 |
||||
``` |
||||
|
||||
## Полная схема потоков |
||||
|
||||
``` |
||||
┌──────────┐ |
||||
│ Сервис │ (routing, proxy, NAT, ...) |
||||
│ (svc_id)│ |
||||
└────┬─────┘ |
||||
│ |
||||
etcp_route_send() │ callback(svc_id, entry) |
||||
▼ ▲ |
||||
┌─────────┐ send ┌─────────────────────────────────┐ recv ┌─────────┐ |
||||
│ TUN │─────────▶│ ETCP ROUTER │─────────▶│ TUN │ |
||||
│ (выход) │ │ │ │ (вход) │ |
||||
└─────────┘ │ ┌──────────┐ ┌──────────┐ │ └─────────┘ |
||||
│ │ send_q │ │ recv_q │ │ |
||||
│ │ (FIFO) │ │ (хеш seq)│ │ |
||||
│ └────┬─────┘ └────┬─────┘ │ |
||||
│ │ │ │ |
||||
│ ▼ ▼ │ |
||||
│ ┌─────────────────────────┐ │ |
||||
│ │ Inflight / Assembly │ │ |
||||
│ │ tx_seq - tx_acked │ │ |
||||
│ │ rx_seq → deliver │ │ |
||||
│ └───────────┬─────────────┘ │ |
||||
│ │ │ |
||||
└──────────────┼──────────────────┘ |
||||
│ |
||||
┌──────▼──────┐ |
||||
│ BGP │ |
||||
│ (нахождение│ |
||||
│ next hop) │ |
||||
└──────┬──────┘ |
||||
│ |
||||
┌──────▼──────┐ |
||||
│ ETCP conn │──▶ сеть |
||||
└─────────────┘ |
||||
|
||||
──▶ send (исходящие данные) |
||||
──▶ recv (входящие данные, callback сервису) |
||||
──▶ транзит (через BGP к следующему hop) |
||||
``` |
||||
|
||||
## Таймеры (один rconn) |
||||
|
||||
``` |
||||
┌─────────────────────────────────────────────────────┐ |
||||
│ │ |
||||
│ ack_timer ──────── 100ms ─────▶ router_ack_timer_cb│ |
||||
│ (периодический) │ |
||||
│ │ |
||||
│ idle_ack_timer ─── 500ms ─────▶ router_idle_ack_cb │ |
||||
│ (досылка последнего ACK) │ |
||||
│ │ |
||||
│ send_resume_timer ─ 50ms ─────▶ router_send_resume │ |
||||
│ (retry когда send_q не пуст) │ |
||||
│ │ |
||||
└─────────────────────────────────────────────────────┘ |
||||
``` |
||||
|
||||
## Флаги seq |
||||
|
||||
``` |
||||
┌──────────────────────────────────────────────────┐ |
||||
│ CLOSE: 0x80000000 нормальное закрытие conn │ |
||||
│ RST: 0x40000000 conn не найден (reset) │ |
||||
│ │ |
||||
│ CLOSE/RST-пакеты: seq=0 | флаг, pl_len=0 │ |
||||
│ Data-пакеты: seq=tx_seq, pl_len>0 │ |
||||
│ ACK-пакеты: seq=rx_seq, pl_len=0 │ |
||||
└──────────────────────────────────────────────────┘ |
||||
``` |
||||
@ -0,0 +1,344 @@
|
||||
Архитектура P2P Direct Connection (установка оптимального подключения между узлами) |
||||
|
||||
Цель: в большой сети узлов каждый узел умеет находить быстрые подключения |
||||
к любому другому узлу — либо прямое (direct), либо через минимальное число |
||||
промежуточных узлов с минимальным суммарным RTT. Оба узла пробуют доступные |
||||
варианты пингом и выбирают лучший. |
||||
|
||||
|
||||
=== Что уже есть в коде (используем, не дублируем) === |
||||
|
||||
1. NODEINFO уже содержит поле tranzit_nodes + структуру NODEINFO_TRANZIT_NODE |
||||
(node_id, rtt, link_q) — "лучшие транзитные узлы, выбирается/обновляется узлом", |
||||
но пока не заполняется и не используется. |
||||
|
||||
2. Пинг-инфраструктура: route_ping_send_req_addr (запрос удалённого пинга через |
||||
третий узел), route_ping_handle_resp (приём результата). Также есть |
||||
etcp_send_ping_to_socket — прямой пинг с конкретного сокета на конкретный адрес |
||||
с pubkey для шифрования. |
||||
|
||||
3. NAT-детекция уже работает: проверка типа NAT через третий узел, обмен NAT_INFO. |
||||
|
||||
4. Path system: NODEINFO_PATH с hop_count, routes → ROUTE_ENTRY → NODEINFO_Q → paths — |
||||
уже выбирается путь для маршрутизации пакетов. |
||||
|
||||
5. Константа ROUTE_SUBCMD_CONN_REQ (0x0B) зарезервирована в route_bgp.h, но не реализована. |
||||
|
||||
6. NODEINFO_Q уже имеет best_socket, last_ping_time, last_rtt. |
||||
|
||||
|
||||
=== Общий алгоритм (на примере узлов A и B) === |
||||
|
||||
A ──(ETCP через релейные узлы)──> B [текущий путь, hop_count > 1] |
||||
|
||||
1. A замечает большой объём трафика к B → запускает CONN_REQ |
||||
2. A шлёт B через существующий ETCP-путь: свои адреса-кандидаты + лучших транзитных соседей |
||||
3. B получает CONN_REQ, сразу начинает пинговать адреса A, собирает свои данные |
||||
4. B шлёт CONN_RESP: свои адреса + транзитных соседей + результаты пробных пингов |
||||
5. A получает CONN_RESP, пингует все адреса B |
||||
6. Оба вычисляют лучший вариант (прямой или релейный с минимальным RTT) |
||||
7. Оба шлют друг другу CONN_RESULT с выбором |
||||
8. Оба пытаются создать ETCP link к выбранному адресу (если прямой) |
||||
9. Лишние/худшие линки закрываются позже — все линки в рамках одного ETCP_CONN |
||||
|
||||
|
||||
=== Фаза 1: Обмен кандидатами (CONN_REQ → CONN_RESP) === |
||||
|
||||
Узел-инициатор A собирает и отправляет: |
||||
- Свои адреса-кандидаты: из своих ETCP_SOCKET (interface_addr + nat_addr). |
||||
Включаются только сокеты с NAT_VERIFIED_* / EIM / PUBLIC (type >= NAT_VERIFIED_UNKNOWN). |
||||
Для каждого: ip, port, type, socket_id. |
||||
- Лучших транзитных соседей: top-N directly-connected узлов (hop_count == 1) |
||||
отсортированных по NODEINFO_Q.last_rtt (лучший RTT первым). |
||||
|
||||
Узел B при получении CONN_REQ: |
||||
- Сохраняет кандидатов A |
||||
- Сразу запускает пробные пинги ко всем адресам A (etcp_send_ping_to_socket с pubkey) |
||||
- Собирает свои адреса-кандидаты |
||||
- Собирает своих транзитных соседей |
||||
- Шлёт CONN_RESP со своими данными + уже готовыми результатами проб |
||||
|
||||
|
||||
=== Фаза 2: Пробы (CONN_RESP → Пинги) === |
||||
|
||||
Узел A при получении CONN_RESP: |
||||
- Запускает пинги ко всем адресам B: |
||||
каждый адрес B пингуется с каждого своего сокета (N×M проб) |
||||
- Использует etcp_send_ping_to_socket с pubkey узла B (из NODEINFO) |
||||
- Пинг идёт напрямую по UDP (не через ETCP) — проверяет реальную достижимость |
||||
включая NAT traversal |
||||
- Ждёт все результаты либо таймаут |
||||
|
||||
Параллельно B тоже завершает свои пробы (запущенные на шаге 3). |
||||
|
||||
|
||||
=== Фаза 3: Выбор лучшего пути (→ CONN_RESULT) === |
||||
|
||||
Каждая сторона независимо вычисляет: |
||||
|
||||
best = {mode: none, rtt: 65535} |
||||
|
||||
Прямые варианты: |
||||
for each (мой_сокет, peer_addr) где probe.ok: |
||||
if probe.rtt < best.rtt: |
||||
best = {direct, my_sock_idx, peer_addr_idx, probe.rtt} |
||||
|
||||
Релейные варианты: |
||||
for each общий_транзитный_узел R (доступен и A и B): |
||||
// RTT(A↔R) берём из своих замеров (A знает RTT до своих соседей) |
||||
// RTT(R↔B) берём из транзитных соседей B (присланы в CONN_RESP) |
||||
total_rtt = A→R_rtt + R→B_rtt |
||||
if total_rtt < best.rtt: |
||||
best = {relay, R, total_rtt} |
||||
|
||||
Обе стороны приходят к одинаковому выводу (информация симметрична). |
||||
|
||||
Шлют CONN_RESULT с выбором. |
||||
Если прямой вариант — оба пытаются создать ETCP link к адресу пира. |
||||
Если релейный — используют существующий путь (уже работает). |
||||
|
||||
|
||||
=== Триггер запуска negotiation === |
||||
|
||||
В route_pkt (routing.c) при отправке пакета узлу с hop_count > 1: |
||||
- Накапливаем счётчик байт к этому узлу (per-node counter в bgp) |
||||
- При превышении порога (например 64KB) и если negotiation ещё не запущен — стартуем |
||||
- Cooldown: не чаще чем раз в 30 секунд для одной пары узлов |
||||
- Если negotiation уже в процессе для этой пары — не дублируем |
||||
|
||||
|
||||
=== Tranzit nodes — автоматическое заполнение === |
||||
|
||||
В route_bgp_update_my_nodeinfo (route_node.c) при каждом обновлении NODEINFO: |
||||
- Сканируем directly-connected соседей (из bgp->nodes, где hop_count == 1) |
||||
- Сортируем по NODEINFO_Q.last_rtt (лучший RTT первым) |
||||
- Берём top-N (до 4) и упаковываем в NODEINFO.tranzit_nodes как массив |
||||
NODEINFO_TRANZIT_NODE (node_id, rtt, link_q) |
||||
|
||||
Плюс периодический refill (раз в ~10 сек) для свежести RTT замеров. |
||||
|
||||
|
||||
=== Структуры данных (новые, route_p2pconn.h) === |
||||
|
||||
#define P2P_MAX_ADDRS 8 |
||||
#define P2P_MAX_RELAYS 8 |
||||
#define P2P_MAX_PROBES (P2P_MAX_ADDRS * P2P_MAX_ADDRS) // до 64 |
||||
|
||||
#define P2P_PHASE_WAIT_RESP 0 // ждём CONN_RESP от пира |
||||
#define P2P_PHASE_PROBING 1 // пингуем адреса пира |
||||
#define P2P_PHASE_SELECTING 2 // выбор лучшего, отправка CONN_RESULT |
||||
#define P2P_PHASE_DONE 3 // завершено |
||||
|
||||
// Один адрес-кандидат |
||||
struct P2P_ADDR { |
||||
uint32_t ip; // network byte order |
||||
uint16_t port; |
||||
uint8_t type; // NAT_VERIFIED_* |
||||
uint8_t socket_id; |
||||
}; |
||||
|
||||
// Один транзитный узел |
||||
struct P2P_RELAY { |
||||
uint64_t node_id; |
||||
uint16_t rtt; // x0.1ms |
||||
uint16_t link_q; |
||||
}; |
||||
|
||||
// Результат одного пробного пинга |
||||
struct P2P_PROBE_RESULT { |
||||
uint8_t my_sock_idx; // индекс в my_addrs[] |
||||
uint8_t peer_addr_idx; // индекс в peer_addrs[] |
||||
uint16_t rtt; // x0.1ms, 0 = fail |
||||
}; |
||||
|
||||
// Состояние одних переговоров (хранится в хеш-таблице bgp->p2p_negotiations) |
||||
struct P2P_NEGOTIATION { |
||||
struct ll_entry ll; |
||||
uint32_t request_id; |
||||
uint64_t peer_node_id; |
||||
uint8_t phase; |
||||
|
||||
// Мои данные |
||||
struct P2P_ADDR my_addrs[P2P_MAX_ADDRS]; |
||||
uint8_t my_addr_count; |
||||
struct P2P_RELAY my_relays[P2P_MAX_RELAYS]; |
||||
uint8_t my_relay_count; |
||||
|
||||
// Данные пира (заполняются из CONN_RESP) |
||||
struct P2P_ADDR peer_addrs[P2P_MAX_ADDRS]; |
||||
uint8_t peer_addr_count; |
||||
struct P2P_RELAY peer_relays[P2P_MAX_RELAYS]; |
||||
uint8_t peer_relay_count; |
||||
|
||||
// Результаты проб |
||||
struct P2P_PROBE_RESULT probes[P2P_MAX_PROBES]; |
||||
uint8_t probe_total; // сколько всего запланировано |
||||
uint8_t probe_done; // сколько завершилось (ok + fail) |
||||
|
||||
// Лучший выбор |
||||
uint8_t best_mode; // 1=direct, 2=relay |
||||
uint64_t best_relay; // node_id релея (если mode=relay) |
||||
uint16_t best_rtt; // x0.1ms |
||||
uint8_t best_my_sock_idx; // индекс в my_addrs (если direct) |
||||
uint8_t best_peer_addr_idx;// индекс в peer_addrs (если direct) |
||||
|
||||
void* timeout_timer; // общий таймаут на всю negotiation |
||||
}; |
||||
|
||||
// Счётчик трафика per-node (для триггера, хранится в bgp) |
||||
struct P2P_TRAFFIC_COUNTER { |
||||
uint64_t node_id; |
||||
uint64_t bytes_sent; // накоплено байт |
||||
uint64_t last_negotiation_time; // время последней попытки (0 = не было) |
||||
}; |
||||
|
||||
|
||||
=== Протокольные пакеты (добавляются в route_bgp.h) === |
||||
|
||||
ROUTE_SUBCMD_CONN_REQ 0x0B // запрос прямого подключения (уже зарезервирован) |
||||
ROUTE_SUBCMD_CONN_RESP 0x0C // ответ с адресами + результаты проб |
||||
ROUTE_SUBCMD_CONN_RESULT 0x0E // финальный выбор |
||||
|
||||
// Пакет CONN_REQ (A → B) |
||||
struct BGP_CONN_REQ { |
||||
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY |
||||
uint8_t subcmd; // ROUTE_SUBCMD_CONN_REQ |
||||
uint32_t request_id; // для корреляции |
||||
uint8_t addr_count; // число адресов-кандидатов |
||||
uint8_t relay_count; // число транзитных узлов |
||||
uint8_t reserved[2]; |
||||
// далее динамически (размер = addr_count*8 + relay_count*12): |
||||
// [addr_count × {ip[4] port[2] type[1] socket_id[1]}] |
||||
// [relay_count × {node_id[8] rtt[2] link_q[2]}] |
||||
}; |
||||
|
||||
// Пакет CONN_RESP (B → A) |
||||
struct BGP_CONN_RESP { |
||||
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY |
||||
uint8_t subcmd; // ROUTE_SUBCMD_CONN_RESP |
||||
uint32_t request_id; |
||||
uint8_t addr_count; // адреса B |
||||
uint8_t relay_count; // транзитные узлы B |
||||
uint8_t probe_count; // готовые результаты проб B→A |
||||
uint8_t reserved; |
||||
// [addr_count × {ip[4] port[2] type[1] socket_id[1]}] |
||||
// [relay_count × {node_id[8] rtt[2] link_q[2]}] |
||||
// [probe_count × {peer_addr_idx[1] my_sock_idx[1] rtt[2] ok[1]}] |
||||
}; |
||||
|
||||
// Пакет CONN_RESULT (A ↔ B, финальный) |
||||
struct BGP_CONN_RESULT { |
||||
uint8_t cmd; |
||||
uint8_t subcmd; // ROUTE_SUBCMD_CONN_RESULT |
||||
uint32_t request_id; |
||||
uint8_t chosen_mode; // 1=direct, 2=relay |
||||
uint8_t reserved; |
||||
uint16_t chosen_rtt; // x0.1ms |
||||
// Если direct: |
||||
uint32_t peer_ip; // IP пира к которому подключаемся |
||||
uint16_t peer_port; |
||||
uint8_t my_socket_id; // свой сокет для подключения |
||||
uint8_t peer_socket_id; // сокет пира |
||||
// Если relay (оверлей тех же байт): |
||||
// uint64_t relay_node_id; |
||||
}; |
||||
|
||||
|
||||
=== API модуля route_p2pconn === |
||||
|
||||
// Запуск negotiation к узлу peer_node_id. |
||||
// Вызывается из route_pkt при превышении порога трафика. |
||||
int p2p_start_negotiation(struct ROUTE_BGP* bgp, uint64_t peer_node_id); |
||||
|
||||
// Проверка: запущена ли уже negotiation для этой пары |
||||
int p2p_is_negotiating(struct ROUTE_BGP* bgp, uint64_t peer_node_id); |
||||
|
||||
// Сбор своих адресов-кандидатов (из etcp_sockets) |
||||
int p2p_collect_my_addrs(struct ROUTE_BGP* bgp, struct P2P_ADDR* out, uint8_t max); |
||||
|
||||
// Сбор лучших транзитных соседей (из directly-connected nodes) |
||||
int p2p_collect_my_relays(struct ROUTE_BGP* bgp, struct P2P_RELAY* out, uint8_t max); |
||||
|
||||
// Обработчики входящих пакетов (вызываются из route_bgp_receive_cbk): |
||||
void p2p_handle_conn_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, |
||||
const uint8_t* data, size_t len); |
||||
void p2p_handle_conn_resp(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, |
||||
const uint8_t* data, size_t len); |
||||
void p2p_handle_conn_result(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, |
||||
const uint8_t* data, size_t len); |
||||
|
||||
// Отмена negotiation при удалении conn |
||||
void p2p_cancel_for_conn(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn); |
||||
|
||||
// Очистка всех negotiation (при destroy bgp) |
||||
void p2p_destroy_all(struct ROUTE_BGP* bgp); |
||||
|
||||
|
||||
=== Схема состояний negotiation === |
||||
|
||||
IDLE ──(трафик > порог)──→ WAIT_RESP ──(CONN_RESP получен)──→ PROBING |
||||
↑ ↑ │ |
||||
│ (таймаут) (все пробы готовы) |
||||
│ ↓ │ |
||||
└──────────────────────────── DONE ←──(CONN_RESULT отправлен)── SELECTING |
||||
|
||||
|
||||
=== Интеграция (какие файлы меняются) === |
||||
|
||||
Новые файлы: |
||||
src/route_p2pconn.h — структуры, константы, прототипы |
||||
src/route_p2pconn.c — вся логика negotiation |
||||
|
||||
Изменения в существующих: |
||||
src/route_bgp.h — добавить ROUTE_SUBCMD_CONN_RESP 0x0C, |
||||
ROUTE_SUBCMD_CONN_RESULT 0x0E, |
||||
структуры пакетов BGP_CONN_REQ/RESP/RESULT |
||||
src/route_bgp.c — в route_bgp_receive_cbk добавить обработку |
||||
новых subcmd. В struct ROUTE_BGP добавить |
||||
поле p2p_negotiations (очередь/хеш negotiation) |
||||
и p2p_traffic_counters (per-node counters) |
||||
src/route_node.c — в route_bgp_update_my_nodeinfo заполнять |
||||
tranzit_nodes из RTT directly-connected соседей |
||||
src/routing.c — в route_pkt добавить накопление счётчика трафика |
||||
и вызов p2p_start_negotiation при превышении порога |
||||
src/Makefile.am — добавить route_p2pconn.c в сборку |
||||
|
||||
|
||||
=== Внутренняя логика route_p2pconn.c === |
||||
|
||||
p2p_start_negotiation(bgp, peer_node_id): |
||||
1. Проверить что нет активной negotiation для этой пары |
||||
2. Проверить cooldown (30 сек) |
||||
3. Создать struct P2P_NEGOTIATION, заполнить my_addrs, my_relays |
||||
4. Сформировать BGP_CONN_REQ и отправить через существующий путь к пиру |
||||
5. Поставить таймаут на всю negotiation (например 5 сек) |
||||
|
||||
p2p_handle_conn_req(bgp, from_conn, data, len): |
||||
1. Распарсить BGP_CONN_REQ |
||||
2. Сохранить адреса и релеи инициатора в P2P_NEGOTIATION |
||||
3. Собрать свои my_addrs, my_relays |
||||
4. Запустить пробные пинги к адресам инициатора (p2p_start_probes) |
||||
5. Когда пробы готовы — отправить CONN_RESP с результатами |
||||
|
||||
p2p_start_probes(neg): |
||||
1. Для каждой пары (мой_сокет, peer_addr) запланировать пинг |
||||
2. Вызвать etcp_send_ping_to_socket с pubkey пира |
||||
3. В коллбэке сохранить результат в probes[], инкрементировать probe_done |
||||
4. Когда probe_done == probe_total → вызвать p2p_select_best |
||||
|
||||
p2p_select_best(neg): |
||||
1. Прямые: найти пару (my_sock, peer_addr) с минимальным rtt > 0 |
||||
2. Релейные: для каждого общего транзитного узла посчитать суммарный rtt, |
||||
найти минимум |
||||
3. Сравнить прямой vs релейный лучший rtt |
||||
4. Записать выбор в neg->best_* |
||||
5. Отправить CONN_RESULT пиру |
||||
6. Вызвать p2p_establish_link(neg) |
||||
|
||||
p2p_establish_link(neg): |
||||
1. Если best_mode == direct: |
||||
- Найти/создать ETCP_CONN к пиру (по node_id) |
||||
- Вызвать etcp_link_new с chosen address |
||||
2. Если best_mode == relay: |
||||
- Ничего не делаем, существующий путь уже работает |
||||
3. Пометить negotiation как DONE |
||||
@ -0,0 +1,50 @@
|
||||
noinst_LIBRARIES = libuasync.a
|
||||
|
||||
libuasync_a_SOURCES = \
|
||||
u_async.c \
|
||||
u_async.h \
|
||||
ll_queue.h \
|
||||
ll_queue.c \
|
||||
debug_config.c \
|
||||
debug_config.h \
|
||||
timeout_heap.c \
|
||||
timeout_heap.h \
|
||||
memory_pool.c \
|
||||
memory_pool.h \
|
||||
socket_compat.c \
|
||||
socket_compat.h \
|
||||
platform_compat.c \
|
||||
platform_compat.h \
|
||||
getmyip.c \
|
||||
getmyip.h \
|
||||
swm_min.c \
|
||||
swm_min.h \
|
||||
mem.c \
|
||||
mem.h \
|
||||
radix.c \
|
||||
radix.h \
|
||||
serialize.c \
|
||||
serialize.h \
|
||||
json_flat.c \
|
||||
json_flat.h \
|
||||
tcp_io.c \
|
||||
tcp_io.h \
|
||||
sqlite3.c \
|
||||
sqlite3.h \
|
||||
opus_codec.c \
|
||||
opus_codec.h \
|
||||
audio_compressor.c \
|
||||
audio_compressor.h
|
||||
|
||||
libuasync_a_CFLAGS = \
|
||||
-D_ISOC99_SOURCE \
|
||||
-DDEBUG_OUTPUT_STDERR \
|
||||
-DSQLITE_THREADSAFE=1 \
|
||||
-g \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/lib \
|
||||
-I$(top_srcdir)/lib/libopus/include
|
||||
|
||||
# Clean build directory
|
||||
clean-local: |
||||
-rm -rf $(top_builddir)/build
|
||||
@ -0,0 +1,292 @@
|
||||
#include "audio_compressor.h" |
||||
#include "debug_config.h" |
||||
#include "mem.h" |
||||
#include <math.h> |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
|
||||
#define BLOCK_LEVELS_CHUNK 64 |
||||
#define PENDING_CHUNK 32 |
||||
|
||||
struct audio_compressor { |
||||
audio_compressor_config_t cfg; |
||||
int enabled; |
||||
|
||||
int block_samples; |
||||
int lookback_blocks; |
||||
int lookahead_blocks; |
||||
float rise_factor_per_block; |
||||
float max_gain; |
||||
float gain_smoothed; |
||||
|
||||
float* block_levels; |
||||
size_t block_levels_count; |
||||
size_t block_levels_cap; |
||||
|
||||
struct pending_block { |
||||
int16_t* samples; |
||||
size_t count; |
||||
size_t level_index; |
||||
}* pending; |
||||
size_t pending_count; |
||||
size_t pending_cap; |
||||
|
||||
int16_t* accumulator; |
||||
size_t accum_count; |
||||
|
||||
int16_t* output; |
||||
size_t output_size; |
||||
size_t output_cap; |
||||
|
||||
int dbg_counter; |
||||
}; |
||||
|
||||
struct audio_compressor* audio_compressor_create(void) { |
||||
struct audio_compressor* ac = u_calloc(1, sizeof(*ac)); |
||||
if (!ac) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "audio_compressor_create: OOM"); |
||||
return NULL; |
||||
} |
||||
ac->enabled = 1; |
||||
ac->gain_smoothed = 1.0f; |
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "audio_compressor_create: ok"); |
||||
return ac; |
||||
} |
||||
|
||||
void audio_compressor_destroy(struct audio_compressor* ac) { |
||||
if (!ac) return; |
||||
for (size_t i = 0; i < ac->pending_count; i++) |
||||
u_free(ac->pending[i].samples); |
||||
u_free(ac->pending); |
||||
u_free(ac->block_levels); |
||||
u_free(ac->accumulator); |
||||
u_free(ac->output); |
||||
u_free(ac); |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "audio_compressor_destroy"); |
||||
} |
||||
|
||||
void audio_compressor_configure(struct audio_compressor* ac, const audio_compressor_config_t* cfg) { |
||||
if (!ac || !cfg) return; |
||||
ac->cfg = *cfg; |
||||
if (ac->cfg.sample_rate <= 0) ac->cfg.sample_rate = 48000; |
||||
if (ac->cfg.channels <= 0) ac->cfg.channels = 1; |
||||
if (ac->cfg.block_duration_ms <= 0) ac->cfg.block_duration_ms = 20; |
||||
if (ac->cfg.lookback_ms <= 0) ac->cfg.lookback_ms = 200; |
||||
if (ac->cfg.lookahead_ms <= 0) ac->cfg.lookahead_ms = 100; |
||||
if (ac->cfg.target_level <= 0.0f) ac->cfg.target_level = 0.25f; |
||||
|
||||
ac->block_samples = ac->cfg.sample_rate * ac->cfg.block_duration_ms / 1000 * ac->cfg.channels; |
||||
ac->lookback_blocks = ac->cfg.lookback_ms / ac->cfg.block_duration_ms; |
||||
ac->lookahead_blocks = ac->cfg.lookahead_ms / ac->cfg.block_duration_ms; |
||||
ac->rise_factor_per_block = powf(ac->cfg.rise_rate_per_sec, (float)ac->cfg.block_duration_ms / 1000.0f); |
||||
ac->max_gain = powf(10.0f, ac->cfg.max_gain_db / 20.0f); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, |
||||
"audio_compressor_configure: rate=%d ch=%d block=%dms lookback=%dms lookahead=%dms maxGain=%.0fdB riseRate=%.1f/sec target=%.0fdBFS", |
||||
ac->cfg.sample_rate, ac->cfg.channels, ac->cfg.block_duration_ms, |
||||
ac->cfg.lookback_ms, ac->cfg.lookahead_ms, ac->cfg.max_gain_db, |
||||
ac->cfg.rise_rate_per_sec, 20.0f * log10f(ac->cfg.target_level)); |
||||
} |
||||
|
||||
void audio_compressor_reset(struct audio_compressor* ac) { |
||||
if (!ac) return; |
||||
for (size_t i = 0; i < ac->pending_count; i++) |
||||
u_free(ac->pending[i].samples); |
||||
ac->pending_count = 0; |
||||
ac->block_levels_count = 0; |
||||
ac->accum_count = 0; |
||||
ac->output_size = 0; |
||||
ac->gain_smoothed = 1.0f; |
||||
ac->dbg_counter = 0; |
||||
} |
||||
|
||||
void audio_compressor_set_enabled(struct audio_compressor* ac, int enabled) { |
||||
if (!ac) return; |
||||
ac->enabled = enabled ? 1 : 0; |
||||
} |
||||
|
||||
int audio_compressor_is_enabled(const struct audio_compressor* ac) { |
||||
return ac ? ac->enabled : 0; |
||||
} |
||||
|
||||
static float compute_block_level(const int16_t* samples, size_t count) { |
||||
double sum_sq = 0.0; |
||||
float peak = 0.0f; |
||||
for (size_t i = 0; i < count; i++) { |
||||
float v = (float)samples[i] / 32768.0f; |
||||
sum_sq += (double)v * v; |
||||
float av = fabsf(v); |
||||
if (av > peak) peak = av; |
||||
} |
||||
float rms = (float)sqrt(sum_sq / (double)count); |
||||
return (rms * 2.0f + peak) / 2.0f; |
||||
} |
||||
|
||||
static void ensure_output_cap(struct audio_compressor* ac, size_t needed) { |
||||
size_t want = ac->output_size + needed; |
||||
if (want <= ac->output_cap) return; |
||||
size_t new_cap = ac->output_cap ? ac->output_cap * 2 : 4096; |
||||
while (new_cap < want) new_cap *= 2; |
||||
int16_t* tmp = u_realloc(ac->output, new_cap * sizeof(int16_t)); |
||||
if (!tmp) return; |
||||
ac->output = tmp; |
||||
ac->output_cap = new_cap; |
||||
} |
||||
|
||||
static void append_output(struct audio_compressor* ac, const int16_t* samples, size_t count) { |
||||
ensure_output_cap(ac, count); |
||||
memcpy(ac->output + ac->output_size, samples, count * sizeof(int16_t)); |
||||
ac->output_size += count; |
||||
} |
||||
|
||||
static void ensure_block_levels_cap(struct audio_compressor* ac) { |
||||
if (ac->block_levels_count < ac->block_levels_cap) return; |
||||
size_t new_cap = ac->block_levels_cap ? ac->block_levels_cap * 2 : BLOCK_LEVELS_CHUNK; |
||||
float* tmp = u_realloc(ac->block_levels, new_cap * sizeof(float)); |
||||
if (!tmp) return; |
||||
ac->block_levels = tmp; |
||||
ac->block_levels_cap = new_cap; |
||||
} |
||||
|
||||
static void add_block_level(struct audio_compressor* ac, float level) { |
||||
ensure_block_levels_cap(ac); |
||||
ac->block_levels[ac->block_levels_count++] = level; |
||||
} |
||||
|
||||
static void ensure_pending_cap(struct audio_compressor* ac) { |
||||
if (ac->pending_count < ac->pending_cap) return; |
||||
size_t new_cap = ac->pending_cap ? ac->pending_cap * 2 : PENDING_CHUNK; |
||||
struct pending_block* tmp = u_realloc(ac->pending, new_cap * sizeof(*tmp)); |
||||
if (!tmp) return; |
||||
ac->pending = tmp; |
||||
ac->pending_cap = new_cap; |
||||
} |
||||
|
||||
static void process_pending_block(struct audio_compressor* ac); |
||||
|
||||
void audio_compressor_push(struct audio_compressor* ac, const int16_t* samples, size_t count) { |
||||
if (!ac || !samples || count == 0) return; |
||||
|
||||
if (!ac->enabled) { |
||||
append_output(ac, samples, count); |
||||
return; |
||||
} |
||||
|
||||
size_t remaining = count; |
||||
const int16_t* src = samples; |
||||
|
||||
while (remaining > 0) { |
||||
size_t need = (size_t)ac->block_samples - ac->accum_count; |
||||
size_t take = remaining < need ? remaining : need; |
||||
|
||||
size_t new_cnt = ac->accum_count + take; |
||||
int16_t* tmp = u_realloc(ac->accumulator, new_cnt * sizeof(int16_t)); |
||||
if (!tmp) return; |
||||
ac->accumulator = tmp; |
||||
memcpy(ac->accumulator + ac->accum_count, src, take * sizeof(int16_t)); |
||||
ac->accum_count = new_cnt; |
||||
|
||||
src += take; |
||||
remaining -= take; |
||||
|
||||
if (ac->accum_count == (size_t)ac->block_samples) { |
||||
float level = compute_block_level(ac->accumulator, ac->accum_count); |
||||
add_block_level(ac, level); |
||||
|
||||
ensure_pending_cap(ac); |
||||
ac->pending[ac->pending_count].samples = ac->accumulator; |
||||
ac->pending[ac->pending_count].count = ac->accum_count; |
||||
ac->pending[ac->pending_count].level_index = ac->block_levels_count - 1; |
||||
ac->pending_count++; |
||||
|
||||
ac->accumulator = NULL; |
||||
ac->accum_count = 0; |
||||
} |
||||
} |
||||
|
||||
while (ac->pending_count > (size_t)ac->lookahead_blocks) |
||||
process_pending_block(ac); |
||||
} |
||||
|
||||
static void process_pending_block(struct audio_compressor* ac) { |
||||
if (ac->pending_count == 0) return; |
||||
|
||||
size_t idx = ac->pending[0].level_index; |
||||
|
||||
size_t win_start = idx >= (size_t)ac->lookback_blocks ? idx - (size_t)ac->lookback_blocks : 0; |
||||
size_t win_end = idx + (size_t)ac->lookahead_blocks; |
||||
if (win_end >= ac->block_levels_count) win_end = ac->block_levels_count - 1; |
||||
|
||||
float envelope = 0.0f; |
||||
for (size_t i = win_start; i <= win_end; i++) { |
||||
if (ac->block_levels[i] > envelope) envelope = ac->block_levels[i]; |
||||
} |
||||
if (envelope < 0.0001f) envelope = 0.0001f; |
||||
|
||||
float G_raw = ac->cfg.target_level / envelope; |
||||
float G_prev = ac->gain_smoothed; |
||||
|
||||
if (G_raw > ac->gain_smoothed) { |
||||
float max_rise = ac->gain_smoothed * ac->rise_factor_per_block; |
||||
ac->gain_smoothed = G_raw < max_rise ? G_raw : max_rise; |
||||
} else { |
||||
ac->gain_smoothed = G_raw; |
||||
} |
||||
if (ac->gain_smoothed > ac->max_gain) ac->gain_smoothed = ac->max_gain; |
||||
|
||||
float gain_db = 20.0f * log10f(ac->gain_smoothed > 0.0001f ? ac->gain_smoothed : 0.0001f); |
||||
float prev_db = 20.0f * log10f(G_prev > 0.0001f ? G_prev : 0.0001f); |
||||
if (ac->dbg_counter % 25 == 0 || fabsf(gain_db - prev_db) > 3.0f) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "audio_compressor: block %zu level=%.4f envelope=%.4f gainRaw=%.1fdB gain=%.1fdB", |
||||
idx, ac->block_levels[idx], envelope, 20.0f * log10f(G_raw), gain_db); |
||||
} |
||||
ac->dbg_counter++; |
||||
|
||||
for (size_t i = 0; i < ac->pending[0].count; i++) { |
||||
float v = (float)ac->pending[0].samples[i] * ac->gain_smoothed; |
||||
if (v > 32767.0f) v = 32767.0f; |
||||
if (v < -32768.0f) v = -32768.0f; |
||||
ac->pending[0].samples[i] = (int16_t)(int)v; |
||||
} |
||||
append_output(ac, ac->pending[0].samples, ac->pending[0].count); |
||||
u_free(ac->pending[0].samples); |
||||
|
||||
/* shift remaining pending blocks down */ |
||||
if (ac->pending_count > 1) |
||||
memmove(ac->pending, ac->pending + 1, (ac->pending_count - 1) * sizeof(*ac->pending)); |
||||
ac->pending_count--; |
||||
} |
||||
|
||||
void audio_compressor_flush(struct audio_compressor* ac) { |
||||
if (!ac) return; |
||||
if (!ac->enabled) return; |
||||
|
||||
if (ac->accum_count > 0) { |
||||
float level = compute_block_level(ac->accumulator, ac->accum_count); |
||||
add_block_level(ac, level); |
||||
|
||||
ensure_pending_cap(ac); |
||||
ac->pending[ac->pending_count].samples = ac->accumulator; |
||||
ac->pending[ac->pending_count].count = ac->accum_count; |
||||
ac->pending[ac->pending_count].level_index = ac->block_levels_count - 1; |
||||
ac->pending_count++; |
||||
|
||||
ac->accumulator = NULL; |
||||
ac->accum_count = 0; |
||||
} |
||||
|
||||
if (ac->pending_count > 0) |
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "audio_compressor_flush: %zu pending blocks (gainSmoothed=%.1fdB)", |
||||
ac->pending_count, 20.0f * log10f(ac->gain_smoothed > 0.0001f ? ac->gain_smoothed : 0.0001f)); |
||||
|
||||
while (ac->pending_count > 0) |
||||
process_pending_block(ac); |
||||
} |
||||
|
||||
const int16_t* audio_compressor_output(const struct audio_compressor* ac) { |
||||
return ac ? ac->output : NULL; |
||||
} |
||||
|
||||
size_t audio_compressor_output_size(const struct audio_compressor* ac) { |
||||
return ac ? ac->output_size : 0; |
||||
} |
||||
@ -0,0 +1,43 @@
|
||||
#ifndef AUDIO_COMPRESSOR_H |
||||
#define AUDIO_COMPRESSOR_H |
||||
|
||||
#include <stdint.h> |
||||
#include <stddef.h> |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
struct audio_compressor; |
||||
|
||||
typedef struct { |
||||
int sample_rate; |
||||
int channels; |
||||
int block_duration_ms; |
||||
int lookback_ms; |
||||
int lookahead_ms; |
||||
float max_gain_db; |
||||
float rise_rate_per_sec; |
||||
float target_level; |
||||
} audio_compressor_config_t; |
||||
|
||||
struct audio_compressor* audio_compressor_create(void); |
||||
void audio_compressor_destroy(struct audio_compressor* ac); |
||||
|
||||
void audio_compressor_configure(struct audio_compressor* ac, const audio_compressor_config_t* cfg); |
||||
void audio_compressor_reset(struct audio_compressor* ac); |
||||
|
||||
void audio_compressor_set_enabled(struct audio_compressor* ac, int enabled); |
||||
int audio_compressor_is_enabled(const struct audio_compressor* ac); |
||||
|
||||
void audio_compressor_push(struct audio_compressor* ac, const int16_t* samples, size_t count); |
||||
void audio_compressor_flush(struct audio_compressor* ac); |
||||
|
||||
const int16_t* audio_compressor_output(const struct audio_compressor* ac); |
||||
size_t audio_compressor_output_size(const struct audio_compressor* ac); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif |
||||
@ -0,0 +1,514 @@
|
||||
/**
|
||||
* Debug module |
||||
*/ |
||||
|
||||
#include "debug_config.h" |
||||
#include "platform_compat.h" |
||||
#include "mem.h" |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
#include <ctype.h> |
||||
#include <stdarg.h> |
||||
#include <stdio.h> |
||||
#include <time.h> |
||||
|
||||
void log_dump(int level, int category, const char* prefix, const uint8_t* data, size_t len) { |
||||
if (!debug_should_output(level, category)) return; |
||||
|
||||
char hex_buf[513] = {'\0'}; |
||||
size_t hex_len = 0; |
||||
size_t show_len = (len > 128) ? 128 : len; |
||||
|
||||
for (size_t i = 0; i < show_len && hex_len < 512 - 3; i++) { |
||||
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]); |
||||
if (i < show_len - 1 && (i + 1) % 32 == 0) |
||||
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, " "); |
||||
} |
||||
|
||||
if (len > 128) |
||||
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "..."); |
||||
|
||||
switch (level) { |
||||
case DEBUG_LEVEL_ERROR: DEBUG_ERROR(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break; |
||||
case DEBUG_LEVEL_WARN: DEBUG_WARN(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break; |
||||
case DEBUG_LEVEL_INFO: DEBUG_INFO(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break; |
||||
case DEBUG_LEVEL_DEBUG: DEBUG_DEBUG(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break; |
||||
case DEBUG_LEVEL_TRACE: DEBUG_TRACE(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break; |
||||
} |
||||
} |
||||
|
||||
|
||||
ip_str_t ip_to_str(const void *addr, int family) { |
||||
ip_str_t result; |
||||
result.str[0] = '\0'; |
||||
|
||||
if (!addr) { |
||||
return result; |
||||
} |
||||
|
||||
if (family == AF_INET) { |
||||
inet_ntop(AF_INET, addr, result.str, 16); |
||||
} else if (family == AF_INET6) { |
||||
inet_ntop(AF_INET6, addr, result.str, 48); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
ip_str_t sockaddr_storage_to_str(const struct sockaddr_storage *addr) { |
||||
ip_str_t result; |
||||
result.str[0] = '\0'; |
||||
|
||||
if (!addr) { |
||||
return result; |
||||
} |
||||
|
||||
char ip_buf[INET6_ADDRSTRLEN]; |
||||
ip_buf[0] = '\0'; |
||||
|
||||
uint16_t port = 0; |
||||
void *src = NULL; |
||||
|
||||
if (addr->ss_family == AF_INET) { |
||||
const struct sockaddr_in *ipv4 = (const struct sockaddr_in *)addr; |
||||
src = (void *)&ipv4->sin_addr; |
||||
port = ntohs(ipv4->sin_port); |
||||
} |
||||
else if (addr->ss_family == AF_INET6) { |
||||
const struct sockaddr_in6 *ipv6 = (const struct sockaddr_in6 *)addr; |
||||
src = (void *)&ipv6->sin6_addr; |
||||
port = ntohs(ipv6->sin6_port); |
||||
} |
||||
else { |
||||
return result; |
||||
} |
||||
|
||||
if (!inet_ntop(addr->ss_family, src, ip_buf, sizeof(ip_buf))) { |
||||
return result; |
||||
} |
||||
|
||||
if (addr->ss_family == AF_INET6) { |
||||
// IPv6 обязательно в []
|
||||
snprintf(result.str, sizeof(result.str), "[%s]:%u", ip_buf, port); |
||||
} else { |
||||
snprintf(result.str, sizeof(result.str), "%s:%u", ip_buf, port); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
|
||||
|
||||
static const struct { |
||||
const char* name; |
||||
debug_category_t category; |
||||
} g_debug_categories[] = { |
||||
{"none", DEBUG_CATEGORY_NONE}, |
||||
{"sys", DEBUG_CATEGORY_SYS}, |
||||
{"connection", DEBUG_CATEGORY_CONNECTION}, |
||||
{"etcp", DEBUG_CATEGORY_ETCP}, |
||||
{"crypto", DEBUG_CATEGORY_CRYPTO}, |
||||
{"config", DEBUG_CATEGORY_CONFIG}, |
||||
{"tun", DEBUG_CATEGORY_TUN}, |
||||
{"routing", DEBUG_CATEGORY_ROUTING}, |
||||
{"timers", DEBUG_CATEGORY_TIMERS}, |
||||
{"bgp", DEBUG_CATEGORY_BGP}, |
||||
{"socket", DEBUG_CATEGORY_SOCKET}, |
||||
{"control", DEBUG_CATEGORY_CONTROL}, |
||||
{"dump", DEBUG_CATEGORY_DUMP}, |
||||
{"traffic", DEBUG_CATEGORY_TRAFFIC}, |
||||
{"debug", DEBUG_CATEGORY_DEBUG}, |
||||
{"general", DEBUG_CATEGORY_GENERAL}, |
||||
{"nat", DEBUG_CATEGORY_NAT}, |
||||
{"keepalive", DEBUG_CATEGORY_KEEPALIVE}, |
||||
{"etcp_route", DEBUG_CATEGORY_ETCPROUTE}, |
||||
{"etcp_dump", DEBUG_CATEGORY_ETCP_DUMP}, |
||||
{"chat_sync", DEBUG_CATEGORY_CHAT_SYNC}, |
||||
{"member_sync", DEBUG_CATEGORY_MEMBER_SYNC}, |
||||
{"all", DEBUG_CATEGORY_ALL}, |
||||
{NULL, DEBUG_CATEGORY_NONE} |
||||
}; |
||||
|
||||
static const struct { |
||||
const char* name; |
||||
debug_level_t level; |
||||
} g_level_table[] = { |
||||
{"disabled", DEBUG_LEVEL_DISABLED}, |
||||
{"none", DEBUG_LEVEL_NONE}, |
||||
{"error", DEBUG_LEVEL_ERROR}, |
||||
{"warn", DEBUG_LEVEL_WARN}, |
||||
{"info", DEBUG_LEVEL_INFO}, |
||||
{"debug", DEBUG_LEVEL_DEBUG}, |
||||
{"trace", DEBUG_LEVEL_TRACE}, |
||||
{NULL, DEBUG_LEVEL_NONE} |
||||
}; |
||||
|
||||
debug_category_t get_category_by_name(const char* name) { |
||||
if (!name) return DEBUG_CATEGORY_NONE; |
||||
for (int i = 0; g_debug_categories[i].name; i++) { |
||||
if (strcasecmp(g_debug_categories[i].name, name) == 0) { |
||||
return g_debug_categories[i].category; |
||||
} |
||||
} |
||||
return DEBUG_CATEGORY_NONE; |
||||
} |
||||
|
||||
debug_level_t debug_level_from_name(const char* name) { |
||||
if (!name) return DEBUG_LEVEL_NONE; |
||||
for (int i = 0; g_level_table[i].name; i++) { |
||||
if (strcasecmp(g_level_table[i].name, name) == 0) { |
||||
return g_level_table[i].level; |
||||
} |
||||
} |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid debug level '%s' (valid: disabled,none,error,warn,info,debug,trace)", name); |
||||
return DEBUG_LEVEL_INFO; /* fallback */ |
||||
} |
||||
|
||||
void debug_set_category_level_by_name(const char* category_name, const char* level_name) { |
||||
if (!category_name || !level_name) return; |
||||
debug_category_t cat = get_category_by_name(category_name); |
||||
if (cat == DEBUG_CATEGORY_NONE) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Unknown debug category '%s'", category_name); |
||||
return; |
||||
} |
||||
debug_level_t lvl = debug_level_from_name(level_name); |
||||
debug_set_category_level(cat, lvl); |
||||
} |
||||
|
||||
int debug_apply_category_config(const char* category_name, const char* level_name) { |
||||
if (!category_name || !level_name) return -1; |
||||
debug_category_t cat = get_category_by_name(category_name); |
||||
if (cat == DEBUG_CATEGORY_NONE) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Unknown debug category '%s'", category_name); |
||||
return -1; |
||||
} |
||||
debug_level_t lvl = debug_level_from_name(level_name); |
||||
debug_set_category_level(cat, lvl); |
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Applied per-category debug: %s=%s", category_name, level_name); |
||||
return 0; |
||||
} |
||||
|
||||
void debug_apply_global_level(const char* level_name) { |
||||
if (!level_name || !*level_name) return; |
||||
debug_level_t level = debug_level_from_name(level_name); |
||||
debug_set_level(level); |
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Applied global debug_level: %s", level_name); |
||||
} |
||||
|
||||
/* Get category name for logging */ |
||||
const char* debug_get_category_name(debug_category_t category_idx) { |
||||
for (int i = 0; g_debug_categories[i].name; i++) |
||||
if (g_debug_categories[i].category == category_idx) return g_debug_categories[i].name; |
||||
return "UNKNOWN"; |
||||
} |
||||
|
||||
/* Global debug configuration */ |
||||
debug_config_t g_debug_config; |
||||
static debug_log_hook_fn g_log_hook = NULL; |
||||
|
||||
/* Initialize debug system with default settings */ |
||||
void debug_config_init(void) { |
||||
g_debug_config.level = DEBUG_LEVEL_ERROR; // Global level: errors only by default
|
||||
for (int i = 0; i < DEBUG_CATEGORY_COUNT; i++) { |
||||
g_debug_config.category_levels[i] = DEBUG_LEVEL_NONE; // 0 = disabled per category
|
||||
} |
||||
g_debug_config.timestamp_enabled = 1; |
||||
g_debug_config.function_name_enabled = 1; |
||||
g_debug_config.file_line_enabled = 1; |
||||
g_debug_config.output_file = NULL; |
||||
|
||||
// Dual output defaults
|
||||
g_debug_config.file_output = NULL; // No file by default
|
||||
g_debug_config.console_enabled = 1; // Console enabled
|
||||
g_debug_config.thread_marker = 0; // No thread marker by default
|
||||
} |
||||
|
||||
/* Set debug level */ |
||||
void debug_set_level(debug_level_t level) { |
||||
g_debug_config.level = level; |
||||
} |
||||
|
||||
/* Set debug level for specific category (0 = disabled, otherwise uses that level) */ |
||||
void debug_set_category_level(debug_category_t category_idx, debug_level_t level) { |
||||
if (category_idx == DEBUG_CATEGORY_NONE || category_idx >= DEBUG_CATEGORY_COUNT) return; |
||||
g_debug_config.category_levels[category_idx] = level; |
||||
} |
||||
|
||||
/* Enable/disable specific categories (now sets level) */ |
||||
void debug_enable_category(debug_category_t category_idx) { |
||||
if (category_idx >= 0 && category_idx < DEBUG_CATEGORY_COUNT) { |
||||
g_debug_config.category_levels[category_idx] = g_debug_config.level; |
||||
} |
||||
} |
||||
|
||||
void debug_disable_category(debug_category_t category_idx) { |
||||
if (category_idx >= 0 && category_idx < DEBUG_CATEGORY_COUNT) { |
||||
g_debug_config.category_levels[category_idx] = DEBUG_LEVEL_NONE; |
||||
} |
||||
} |
||||
|
||||
void debug_set_categories(debug_category_t category_idx) { |
||||
/* Deprecated - use per-category levels */ |
||||
if (category_idx >= 0 && category_idx < DEBUG_CATEGORY_COUNT) { |
||||
g_debug_config.category_levels[category_idx] = g_debug_config.level; |
||||
} |
||||
} |
||||
|
||||
/* Set level for category (main API) */ |
||||
void debug_set_masks(debug_category_t category_idx, debug_level_t level) { |
||||
debug_set_category_level(category_idx, level); |
||||
} |
||||
|
||||
void debug_enable_function_name(int enable) { |
||||
g_debug_config.function_name_enabled = enable; |
||||
} |
||||
|
||||
void debug_enable_file_line(int enable) { |
||||
g_debug_config.file_line_enabled = enable; |
||||
} |
||||
|
||||
void debug_set_output_file(const char* file_path) { |
||||
if (g_debug_config.file_output) return;// уже установлен, кто первый того и тапки
|
||||
FILE* new_file = fopen(file_path, "a"); |
||||
if (new_file) { |
||||
g_debug_config.file_output = new_file; |
||||
g_debug_config.output_file = file_path; |
||||
} |
||||
} |
||||
|
||||
/* Set console log level */ |
||||
void debug_enable_console(int enable) { |
||||
g_debug_config.console_enabled = enable; |
||||
} |
||||
|
||||
/* Enable file output with optional truncation */ |
||||
void debug_enable_file_output(const char* file_path, int truncate) { |
||||
if (!file_path) return; |
||||
|
||||
// Close existing file if open
|
||||
if (g_debug_config.file_output) { |
||||
fclose(g_debug_config.file_output); |
||||
g_debug_config.file_output = NULL; |
||||
} |
||||
|
||||
// Open new file
|
||||
const char* mode = truncate ? "w" : "a"; |
||||
FILE* new_file = fopen(file_path, mode); |
||||
if (new_file) { |
||||
g_debug_config.file_output = new_file; |
||||
g_debug_config.output_file = file_path; |
||||
g_debug_config.console_enabled = 0; |
||||
} |
||||
} |
||||
|
||||
/* Disable file output */ |
||||
void debug_disable_file_output(void) { |
||||
if (g_debug_config.file_output) { |
||||
fclose(g_debug_config.file_output); |
||||
g_debug_config.file_output = NULL; |
||||
g_debug_config.output_file = NULL; |
||||
} |
||||
} |
||||
|
||||
/* Reopen log file (append mode) for SIGHUP. Complete operation with full error logging. */ |
||||
int debug_reopen_log(void) { |
||||
if (!g_debug_config.output_file) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "No log path for reopen"); |
||||
return -1; |
||||
} |
||||
const char* path = g_debug_config.output_file; |
||||
debug_disable_file_output(); |
||||
debug_enable_file_output(path, 0); // append
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Log reopened: %s", path); |
||||
return 0; |
||||
} |
||||
|
||||
/* Check if debug output should be shown for given level and category */ |
||||
int debug_should_output(debug_level_t level, debug_category_t category_idx) { |
||||
if (category_idx < 0 || category_idx >= DEBUG_CATEGORY_COUNT) { |
||||
return 0; |
||||
} |
||||
|
||||
debug_level_t cat_level = g_debug_config.category_levels[category_idx]; |
||||
if (cat_level == DEBUG_LEVEL_DISABLED) { |
||||
return 0; |
||||
} |
||||
if (cat_level == DEBUG_LEVEL_NONE) { |
||||
cat_level = g_debug_config.level; |
||||
} |
||||
|
||||
/* Check if level is sufficient */ |
||||
if (level > cat_level) { |
||||
return 0; |
||||
} |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
/* Get current debug level */ |
||||
debug_level_t debug_get_level(void) { |
||||
return g_debug_config.level; |
||||
} |
||||
|
||||
/* Get level name */ |
||||
static const char* get_level_name(debug_level_t level) { |
||||
switch (level) { |
||||
case DEBUG_LEVEL_ERROR: return "ERROR"; |
||||
case DEBUG_LEVEL_WARN: return "WARN"; |
||||
case DEBUG_LEVEL_INFO: return "INFO"; |
||||
case DEBUG_LEVEL_DEBUG: return "DEBUG"; |
||||
case DEBUG_LEVEL_TRACE: return "TRACE"; |
||||
default: return "UNKNOWN"; |
||||
} |
||||
} |
||||
|
||||
/* Format and output debug message */ |
||||
void debug_output(debug_level_t level, debug_category_t category_idx,
|
||||
const char* function, const char* file, int line, |
||||
const char* format, ...) { |
||||
|
||||
#define BUFFER_SIZE 4096 |
||||
char buffer[BUFFER_SIZE]; |
||||
int offset = 0; |
||||
size_t remaining = BUFFER_SIZE; |
||||
|
||||
va_list args; |
||||
va_start(args, format); |
||||
|
||||
/* Add timestamp with microseconds: hh:mm:ss-xxx.yyy */ |
||||
struct timeval tv; |
||||
utun_gettimeofday(&tv, NULL); |
||||
time_t tv_sec = (time_t)tv.tv_sec; |
||||
struct tm* tm_info = localtime(&tv_sec); |
||||
char time_str[32]; |
||||
strftime(time_str, sizeof(time_str), "%H:%M:%S", tm_info); |
||||
offset += snprintf(buffer + offset, remaining, "[%s-%03ld.%03ld] ", time_str, tv.tv_usec / 1000, tv.tv_usec % 1000); |
||||
remaining = BUFFER_SIZE - offset; |
||||
|
||||
/* Add thread marker if set */ |
||||
if (g_debug_config.thread_marker > 0) { |
||||
offset += snprintf(buffer + offset, remaining, "[%d] ", g_debug_config.thread_marker); |
||||
remaining = BUFFER_SIZE - offset; |
||||
} |
||||
|
||||
/* Add level */ |
||||
const char* level_name = get_level_name(level); |
||||
offset += snprintf(buffer + offset, remaining, "[%s] ", level_name); |
||||
remaining = BUFFER_SIZE - offset; |
||||
|
||||
/* Add category name */ |
||||
const char* cat_name = debug_get_category_name(category_idx); |
||||
offset += snprintf(buffer + offset, remaining, "[%s] ", cat_name); |
||||
remaining = BUFFER_SIZE - offset; |
||||
|
||||
/* Add file:line if enabled */ |
||||
if (g_debug_config.file_line_enabled && file) { |
||||
const char* bn = strrchr(file, '/'); |
||||
bn = bn ? bn + 1 : file; |
||||
offset += snprintf(buffer + offset, remaining, "(%s:%d) ", bn, line); |
||||
remaining = BUFFER_SIZE - offset; |
||||
} |
||||
|
||||
/* Add function name if enabled */ |
||||
if (g_debug_config.function_name_enabled && function) { |
||||
offset += snprintf(buffer + offset, remaining, "%s() ", function); |
||||
remaining = BUFFER_SIZE - offset; |
||||
} |
||||
|
||||
/* Add the actual message */ |
||||
offset += vsnprintf(buffer + offset, remaining, format, args); |
||||
remaining = BUFFER_SIZE - offset; |
||||
|
||||
/* Add newline */ |
||||
if (remaining > 1) { |
||||
buffer[offset] = '\n'; |
||||
buffer[offset + 1] = '\0'; |
||||
} else { |
||||
buffer[BUFFER_SIZE - 2] = '\n'; |
||||
buffer[BUFFER_SIZE - 1] = '\0'; |
||||
} |
||||
|
||||
va_end(args); |
||||
|
||||
/* Route to external hook (JNI bridge, etc.) */ |
||||
if (g_log_hook) { |
||||
const char* cat_name = debug_get_category_name(category_idx); |
||||
g_log_hook((int)level, cat_name, buffer); |
||||
} |
||||
|
||||
/* Output to console if enabled and level is sufficient */ |
||||
if (g_debug_config.file_output) { |
||||
fprintf(g_debug_config.file_output, "%s", buffer); |
||||
fflush(g_debug_config.file_output); |
||||
} else if (g_debug_config.console_enabled) { |
||||
fprintf(stdout, "%s", buffer); |
||||
fflush(stdout); |
||||
} |
||||
#undef BUFFER_SIZE |
||||
} |
||||
|
||||
void debug_set_log_hook(debug_log_hook_fn hook) { |
||||
g_log_hook = hook; |
||||
} |
||||
|
||||
/* Parse debug configuration from string (e.g., "ll_queue:debug,uasync:info,memory:warn,etcp:error") */ |
||||
int debug_parse_config(const char* config_string) { |
||||
if (!config_string || !*config_string) { |
||||
return 0; |
||||
} |
||||
|
||||
char* str = u_strdup(config_string); |
||||
if (!str) { |
||||
return -1; |
||||
} |
||||
|
||||
debug_category_t categories = 0; |
||||
debug_level_t level = DEBUG_LEVEL_NONE; |
||||
|
||||
char* token = strtok(str, ","); |
||||
while (token) { |
||||
/* Trim leading spaces */ |
||||
while (isspace(*token)) { |
||||
token++; |
||||
} |
||||
|
||||
char* colon = strchr(token, ':'); |
||||
if (!colon) { |
||||
u_free(str); |
||||
return -1; |
||||
} |
||||
|
||||
*colon = '\0'; |
||||
char* category_name = token; |
||||
char* level_name = colon + 1; |
||||
|
||||
/* Trim leading spaces in level_name */ |
||||
while (isspace(*level_name)) { |
||||
level_name++; |
||||
} |
||||
|
||||
debug_category_t cat = get_category_by_name(category_name); |
||||
if (cat == DEBUG_CATEGORY_NONE) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Unknown debug category '%s'", category_name); |
||||
u_free(str); |
||||
return -1; |
||||
} |
||||
|
||||
debug_level_t lev = debug_level_from_name(level_name); |
||||
|
||||
/* Set per-category level */ |
||||
int idx = 0; |
||||
while (!(cat & (1ULL << idx)) && idx < DEBUG_CATEGORY_COUNT) { |
||||
idx++; |
||||
} |
||||
if (idx < DEBUG_CATEGORY_COUNT) { |
||||
g_debug_config.category_levels[idx] = lev; |
||||
} |
||||
|
||||
token = strtok(NULL, ","); |
||||
} |
||||
|
||||
u_free(str); |
||||
return 0; |
||||
} |
||||
@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Runtime debug configuration system |
||||
* Provides flexible control over debug output without recompilation |
||||
*/ |
||||
|
||||
#ifndef DEBUG_CONFIG_H |
||||
#define DEBUG_CONFIG_H |
||||
|
||||
#include "platform_compat.h" |
||||
|
||||
#ifdef _WIN32 |
||||
#include <winsock2.h> |
||||
#include <ws2tcpip.h> |
||||
#else |
||||
#include <sys/socket.h> // struct sockaddr_storage |
||||
#endif |
||||
#include <stddef.h> // size_t |
||||
#include <stdint.h> |
||||
#include <stddef.h> |
||||
#include <time.h> |
||||
#include <stdio.h> // FILE |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/* Debug levels */ |
||||
typedef enum { |
||||
DEBUG_LEVEL_DISABLED = -1, // Category completely disabled, never outputs
|
||||
DEBUG_LEVEL_NONE = 0, // Fallback to global level
|
||||
DEBUG_LEVEL_ERROR = 1, // Errors only
|
||||
DEBUG_LEVEL_WARN = 2, // Warnings and errors
|
||||
DEBUG_LEVEL_INFO = 3, // Info, warnings, errors
|
||||
DEBUG_LEVEL_DEBUG = 4, // Full debug output
|
||||
DEBUG_LEVEL_TRACE = 5 // Trace everything
|
||||
} debug_level_t; |
||||
|
||||
/* Debug categories as indices (0-based) */ |
||||
typedef int debug_category_t; |
||||
|
||||
#define DEBUG_CATEGORY_NONE 0 |
||||
#define DEBUG_CATEGORY_SYS 1 // System: u_async, ll_queue, memory, timing
|
||||
// #define DEBUG_CATEGORY_LL_QUEUE 2 (merged into SYS)
|
||||
#define DEBUG_CATEGORY_CONNECTION 3 // Connection: links, keepalive, status events
|
||||
#define DEBUG_CATEGORY_ETCP 4 // ETCP: protocol, BBR, normalizer
|
||||
#define DEBUG_CATEGORY_CRYPTO 5 // crypto operations
|
||||
// #define DEBUG_CATEGORY_MEMORY 6 (merged into SYS)
|
||||
// #define DEBUG_CATEGORY_TIMING 7 (merged into SYS)
|
||||
#define DEBUG_CATEGORY_CONFIG 8 // configuration parsing
|
||||
#define DEBUG_CATEGORY_TUN 9 // TUN interface
|
||||
#define DEBUG_CATEGORY_ROUTING 10 // routing table
|
||||
#define DEBUG_CATEGORY_TIMERS 11 // timer management
|
||||
// #define DEBUG_CATEGORY_NORMALIZER 12 (merged into ETCP)
|
||||
#define DEBUG_CATEGORY_BGP 13 // BGP route exchange
|
||||
#define DEBUG_CATEGORY_SOCKET 14 // Socket operations
|
||||
#define DEBUG_CATEGORY_CONTROL 15 // Control/monitoring server
|
||||
#define DEBUG_CATEGORY_DUMP 16 // Packet dump/logging
|
||||
#define DEBUG_CATEGORY_TRAFFIC 17 // Traffic flow (src/dst node IDs)
|
||||
#define DEBUG_CATEGORY_DEBUG 18 // Current debugging
|
||||
#define DEBUG_CATEGORY_GENERAL 19 // Messages for user (common log)
|
||||
#define DEBUG_CATEGORY_NAT 20 // EIM NAT module
|
||||
#define DEBUG_CATEGORY_KEEPALIVE 21 // Keepalive periodic monitoring only
|
||||
#define DEBUG_CATEGORY_ETCPROUTE 22 // ETCP routing
|
||||
// #define DEBUG_CATEGORY_BBR 23 (merged into ETCP)
|
||||
#define DEBUG_CATEGORY_ETCP_DUMP 24 // ETCP packet dump
|
||||
// #define DEBUG_CATEGORY_CONNECTIVITY 25 (unused, removed)
|
||||
#define DEBUG_CATEGORY_CHAT_SYNC 26 // Message DB sync (INIT_SYNC, PUSH, chain hash, recalc)
|
||||
#define DEBUG_CATEGORY_MEMBER_SYNC 27 // Member + address + merkle sync
|
||||
#define DEBUG_CATEGORY_COUNT 28 // Total number of categories
|
||||
#define DEBUG_CATEGORY_ALL (-1) // special value for all categories
|
||||
|
||||
/* Debug configuration structure */ |
||||
typedef struct { |
||||
debug_level_t level; // Global debug level (default: ERROR)
|
||||
debug_level_t category_levels[DEBUG_CATEGORY_COUNT]; // Per-category levels (0 = disabled)
|
||||
int timestamp_enabled; // Include timestamps in output
|
||||
int function_name_enabled; // Include function names
|
||||
int file_line_enabled; // Include file:line info
|
||||
const char* output_file; // NULL = stdout, otherwise file path
|
||||
|
||||
// Dual output support
|
||||
FILE* file_output; // File handle for log file (NULL if not open)
|
||||
int console_enabled; // Enable console output
|
||||
int thread_marker; // Thread identification (0=none, e.g. 2 for uasync)
|
||||
} debug_config_t; |
||||
|
||||
/* Global debug configuration */ |
||||
extern debug_config_t g_debug_config; |
||||
|
||||
/* Initialize debug system with default settings */ |
||||
void debug_config_init(void); |
||||
|
||||
/* Set debug level */ |
||||
// итоговый level = max (global level - здесь задается, category level)
|
||||
void debug_set_level(debug_level_t level); |
||||
|
||||
/* Set debug level for specific category (0 = disabled, otherwise uses that level) */ |
||||
void debug_set_category_level(debug_category_t category, debug_level_t level); |
||||
|
||||
/* Enable/disable specific categories */ |
||||
void debug_enable_category(debug_category_t category); |
||||
void debug_disable_category(debug_category_t category); |
||||
void debug_set_categories(debug_category_t categories); |
||||
|
||||
/* Set masks directly */ |
||||
void debug_set_masks(debug_category_t categories, debug_level_t level); |
||||
|
||||
/* Configure output options */ |
||||
void debug_enable_timestamp(int enable); |
||||
void debug_enable_function_name(int enable); |
||||
void debug_enable_file_line(int enable); |
||||
void debug_set_output_file(const char* file_path); |
||||
|
||||
/* Dual output configuration */ |
||||
void debug_enable_file_output(const char* file_path, int truncate); |
||||
void debug_disable_file_output(void); |
||||
int debug_reopen_log(void); |
||||
void debug_enable_console(int enable); |
||||
|
||||
|
||||
// IP address to string (static buffer, single-threaded)
|
||||
typedef struct { |
||||
char str[54]; // INET6_ADDRSTRLEN(45) + 6chars (:port) + \0
|
||||
} ip_str_t; |
||||
|
||||
ip_str_t ip_to_str(const void *addr, int family);// big endian addr
|
||||
ip_str_t sockaddr_storage_to_str(const struct sockaddr_storage *addr);// автоопределение v4 или v6
|
||||
|
||||
// hex dump в лог
|
||||
void log_dump(int level, int category, const char* prefix, const uint8_t* data, size_t len); |
||||
|
||||
/* Check if debug output should be shown for given level and category */ |
||||
int debug_should_output(debug_level_t level, debug_category_t category_idx); |
||||
|
||||
/* Get current debug level */ |
||||
debug_level_t debug_get_level(void); |
||||
|
||||
/* Get category name by index */ |
||||
const char* debug_get_category_name(debug_category_t category_idx); |
||||
|
||||
/* Get category by name string */ |
||||
debug_category_t get_category_by_name(const char* name); |
||||
|
||||
/* Get level by name string with error reporting */ |
||||
debug_level_t debug_level_from_name(const char* name); |
||||
|
||||
/* Set category level using text names (main API) */ |
||||
void debug_set_category_level_by_name(const char* category_name, const char* level_name); |
||||
|
||||
/* Apply full category config with error reporting */ |
||||
int debug_apply_category_config(const char* category_name, const char* level_name); |
||||
|
||||
/* Apply global debug level from text name */ |
||||
void debug_apply_global_level(const char* level_name); |
||||
|
||||
/* Format and output debug message (internal use by macros) */ |
||||
void debug_output(debug_level_t level, debug_category_t category_idx,
|
||||
const char* function, const char* file, int line, |
||||
const char* format, ...); |
||||
|
||||
/* Log hook — if set, called with every debug message (for JNI/bridge integration) */ |
||||
typedef void (*debug_log_hook_fn)(int level, const char* category, const char* message); |
||||
void debug_set_log_hook(debug_log_hook_fn hook); |
||||
|
||||
/* Parse debug configuration from string (e.g. "etcp=trace,config=info") */ |
||||
int debug_parse_config(const char* config_string); |
||||
|
||||
/* Convenience macros for debug output */ |
||||
#define DEBUG_ERROR(category, fmt, ...) \ |
||||
do { if (debug_should_output(DEBUG_LEVEL_ERROR, category)) { \
|
||||
debug_output(DEBUG_LEVEL_ERROR, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
|
||||
} } while(0) |
||||
|
||||
#define DEBUG_WARN(category, fmt, ...) \ |
||||
do { if (debug_should_output(DEBUG_LEVEL_WARN, category)) { \
|
||||
debug_output(DEBUG_LEVEL_WARN, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
|
||||
} } while(0) |
||||
|
||||
#define DEBUG_INFO(category, fmt, ...) \ |
||||
do { if (debug_should_output(DEBUG_LEVEL_INFO, category)) { \
|
||||
debug_output(DEBUG_LEVEL_INFO, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
|
||||
} } while(0) |
||||
|
||||
#define DEBUG_DEBUG(category, fmt, ...) \ |
||||
do { if (debug_should_output(DEBUG_LEVEL_DEBUG, category)) { \
|
||||
debug_output(DEBUG_LEVEL_DEBUG, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
|
||||
} } while(0) |
||||
|
||||
#define DEBUG_TRACE(category, fmt, ...) \ |
||||
do { if (debug_should_output(DEBUG_LEVEL_TRACE, category)) { \
|
||||
debug_output(DEBUG_LEVEL_TRACE, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \
|
||||
} } while(0) |
||||
|
||||
/* Backward compatibility - default to ERROR level if not specified */ |
||||
#define DEBUG_OUTPUT(fmt, ...) DEBUG_ERROR(DEBUG_CATEGORY_ALL, fmt, ##__VA_ARGS__) |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif // DEBUG_CONFIG_H
|
||||
@ -0,0 +1,112 @@
|
||||
# debug_config — Runtime-система отладочного логирования |
||||
|
||||
## 1. Назначение |
||||
|
||||
Модуль обеспечивает гибкое управление отладочным выводом без перекомпиляции. Позволяет: |
||||
- Задавать **глобальный уровень** логирования (error/warn/info/debug/trace). |
||||
- **Покатегорийно** включать/выключать или переопределять уровень (26 категорий: etcp, crypto, tun, routing, bgp, bbr, …). |
||||
- Вести **двойной вывод**: в консоль (stdout) и/или в файл. |
||||
- Настраивать формат сообщений (временные метки с микросекундами, имя функции, файл:строка). |
||||
- Парсить конфигурационную строку вида `"etcp=trace,config=info"` из конфиг-файла. |
||||
|
||||
Логика вывода: итоговый уровень = **max(глобальный уровень, уровень категории)**. Сообщение печатается, если его уровень ≤ итогового. |
||||
|
||||
## 2. Как пользоваться |
||||
|
||||
### 2.1. Инициализация |
||||
|
||||
```c |
||||
#include "debug_config.h" |
||||
|
||||
debug_config_init(); // уровень по умолчанию: ERROR, консоль включена |
||||
debug_set_level(DEBUG_LEVEL_INFO); // поднять глобальный уровень до INFO |
||||
debug_enable_file_output("/tmp/utun.log", 1); // писать в файл (truncate=1) |
||||
debug_apply_category_config("etcp", "trace"); // для ETCP-категории — всё |
||||
``` |
||||
|
||||
### 2.2. Макросы логирования |
||||
|
||||
```c |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "Decrypt failed: len=%d", len); |
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Retransmit timeout, seq=%u", seq); |
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Socket created on port %d", port); |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ROUTING, "Route added: %s -> %s", dst, gw); |
||||
DEBUG_TRACE(DEBUG_CATEGORY_TUN, "Packet recv: %zu bytes", n); |
||||
``` |
||||
|
||||
Макросы делают проверку `debug_should_output()` **перед** вызовом `debug_output()`, поэтому дорогой `vsnprintf` не вызывается, если вывод не нужен. |
||||
|
||||
### 2.3. Конфигурационная строка |
||||
|
||||
Формат: `"категория=уровень,категория=уровень,..."`. Парсится вызовом `debug_parse_config()`. Пример из конфиг-файла: |
||||
|
||||
```ini |
||||
debug = etcp=trace,config=info,crypto=error |
||||
``` |
||||
|
||||
Категории: `none, uasync, ll_queue, connection, etcp, crypto, memory, timing, config, tun, routing, timers, normalizer, bgp, socket, control, dump, traffic, debug, general, nat, keepalive, etcp_route, bbr, etcp_dump, connectivity, all`. |
||||
|
||||
Уровни: `disabled, none, error, warn, info, debug, trace`. |
||||
|
||||
**Важно:** старый формат `ll_queue:debug` (двоеточие) тоже работает через `debug_parse_config`, но в конфиге рекомендуется `=`. |
||||
|
||||
### 2.4. Двойной вывод (консоль + файл) |
||||
|
||||
- По умолчанию вывод в **stdout**. |
||||
- `debug_enable_file_output(path, truncate)` — перенаправляет вывод в файл, консоль отключается. |
||||
- `debug_disable_file_output()` — закрывает файл. |
||||
- `debug_reopen_log()` — переоткрывает лог-файл (используется по SIGHUP для ротации). |
||||
- `debug_enable_console(1/0)` — ручное управление консольным выводом. |
||||
|
||||
### 2.5. Вспомогательные утилиты |
||||
|
||||
```c |
||||
log_dump(DEBUG_LEVEL_TRACE, DEBUG_CATEGORY_DUMP, "payload", data, len); // hex-дамп (до 128 байт) |
||||
|
||||
ip_str_t s = sockaddr_storage_to_str(&addr); // "192.168.1.1:8080" или "[::1]:443" |
||||
ip_str_t s = ip_to_str(&addr, AF_INET); // "192.168.1.1" (без порта) |
||||
``` |
||||
|
||||
### 2.6. Нюансы |
||||
|
||||
- **Потокобезопасность:** модуль не потокобезопасен. Несколько потоков, одновременно вызывающих макросы, могут перемешать вывод (но не упадут). Файловый вывод использует `fflush()` после каждой строки. |
||||
- **Буфер:** размер строки лога ограничен 4096 байт (`BUFFER_SIZE`), длинные сообщения обрезаются. |
||||
- **`debug_set_output_file`** против `debug_enable_file_output`: первая вызывается один раз (кто первый — того и тапки), вторая позволяет переоткрыть файл. |
||||
- **Флаг `-f` (foreground):** в foreground-режиме вывод идёт в stdout, в daemon-режиме — в файл. |
||||
|
||||
## 3. API |
||||
|
||||
| Функция/макрос | Назначение | |
||||
|---|---| |
||||
| `debug_config_init()` | Инициализация: глобальный уровень ERROR, консоль включена, метки времени/функций/файлов — да | |
||||
| `debug_set_level(level)` | Задать глобальный уровень (ERROR..TRACE) | |
||||
| `debug_set_category_level(cat, level)` | Задать уровень конкретной категории (0 = использовать глобальный) | |
||||
| `debug_set_category_level_by_name("etcp", "trace")` | То же, но именами строк | |
||||
| `debug_apply_category_config("etcp", "trace")` | Установить уровень категории с логированием результата | |
||||
| `debug_apply_global_level("info")` | Установить глобальный уровень по имени строки | |
||||
| `debug_parse_config("etcp=trace,config=info")` | Разобрать конфигурационную строку | |
||||
| `debug_should_output(level, cat)` | Проверить, нужно ли выводить сообщение данного уровня/категории | |
||||
| `debug_output(level, cat, func, file, line, fmt, ...)` | Форматировать и вывести сообщение (вызывается макросами) | |
||||
| `debug_enable_file_output(path, truncate)` | Включить вывод в файл (консоль отключается) | |
||||
| `debug_disable_file_output()` | Закрыть файл вывода | |
||||
| `debug_reopen_log()` | Переоткрыть лог-файл (для SIGHUP/ротации) | |
||||
| `debug_enable_console(1/0)` | Включить/выключить консольный вывод | |
||||
| `debug_get_level()` | Получить текущий глобальный уровень | |
||||
| `get_category_by_name("etcp")` | Получить ID категории по имени строки | |
||||
| `debug_level_from_name("trace")` | Получить уровень по имени строки | |
||||
| `debug_get_category_name(cat)` | Получить имя категории по ID | |
||||
| `log_dump(level, cat, prefix, data, len)` | Hex-дамп данных (первые 128 байт) в лог | |
||||
| `ip_to_str(addr, family)` | IP-адрес → строка (без порта). Статический буфер | |
||||
| `sockaddr_storage_to_str(addr)` | sockaddr_storage → "ip:port" (IPv6 в скобках). Статический буфер | |
||||
| `DEBUG_ERROR/WARN/INFO/DEBUG/TRACE(cat, fmt, ...)` | Макросы логирования с проверкой `debug_should_output()` | |
||||
| `DEBUG_OUTPUT(fmt, ...)` | Устаревший макрос совместимости (ERROR, категория ALL) | |
||||
|
||||
**Глобальная переменная:** `g_debug_config` (тип `debug_config_t`) — хранит всё состояние системы логирования. |
||||
|
||||
**Структура `debug_config_t`:** |
||||
- `level` — глобальный уровень (по умолчанию ERROR) |
||||
- `category_levels[26]` — уровни по категориям (NONE = использовать глобальный, DISABLED = никогда не выводить) |
||||
- `timestamp_enabled` / `function_name_enabled` / `file_line_enabled` — формат вывода |
||||
- `file_output` — FILE* файла (NULL если вывод в консоль) |
||||
- `console_enabled` — вывод в stdout |
||||
- `thread_marker` — числовой маркер потока (0 = нет) |
||||
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* getmyip.c - Определение локального IP-адреса для исходящих соединений |
||||
* |
||||
* Использует временный UDP-сокет с connect() для определения |
||||
* локального IP, который ОС выберет для заданного маршрута. |
||||
*/ |
||||
|
||||
#include "getmyip.h" |
||||
#include "socket_compat.h" |
||||
#include "debug_config.h" |
||||
|
||||
#ifdef _WIN32 |
||||
#include <winsock2.h> |
||||
#include <ws2tcpip.h> |
||||
#else |
||||
#include <unistd.h> |
||||
#include <arpa/inet.h> |
||||
#include <errno.h> |
||||
#endif |
||||
|
||||
int get_outgoing_local_ip(const struct sockaddr_storage *local_addr, |
||||
const struct sockaddr_storage *remote_addr, |
||||
struct sockaddr_storage *result) |
||||
{ |
||||
if (!local_addr || !remote_addr || !result) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: NULL argument"); |
||||
return -1; |
||||
} |
||||
|
||||
int family = local_addr->ss_family; |
||||
if (family != AF_INET && family != AF_INET6) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: unsupported family %d", family); |
||||
return -1; |
||||
} |
||||
|
||||
if (remote_addr->ss_family != family) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: family mismatch"); |
||||
return -1; |
||||
} |
||||
|
||||
socket_t sock = socket_create_udp(family); |
||||
if (sock == SOCKET_INVALID) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: socket_create_udp failed"); |
||||
return -1; |
||||
} |
||||
|
||||
// Bind на local_addr, но порт ставим 0 чтобы не конфликтовать с основным сокетом
|
||||
struct sockaddr_storage bind_addr; |
||||
memcpy(&bind_addr, local_addr, sizeof(bind_addr)); |
||||
if (family == AF_INET) { |
||||
((struct sockaddr_in *)&bind_addr)->sin_port = 0; |
||||
} else { |
||||
((struct sockaddr_in6 *)&bind_addr)->sin6_port = 0; |
||||
} |
||||
|
||||
socklen_t bind_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); |
||||
if (bind(sock, (struct sockaddr *)&bind_addr, bind_len) != 0) { |
||||
int err = socket_get_error(); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: bind failed: %s", socket_strerror(err)); |
||||
socket_close_wrapper(sock); |
||||
return -1; |
||||
} |
||||
|
||||
// Connect к remote_addr — ядро вычислит маршрут и выберет source IP
|
||||
socklen_t remote_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); |
||||
if (connect(sock, (struct sockaddr *)remote_addr, remote_len) != 0) { |
||||
int err = socket_get_error(); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: connect failed: %s", socket_strerror(err)); |
||||
socket_close_wrapper(sock); |
||||
return -1; |
||||
} |
||||
|
||||
// Получаем выбранный локальный адрес
|
||||
struct sockaddr_storage local; |
||||
socklen_t local_len = sizeof(local); |
||||
if (getsockname(sock, (struct sockaddr *)&local, &local_len) != 0) { |
||||
int err = socket_get_error(); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: getsockname failed: %s", socket_strerror(err)); |
||||
socket_close_wrapper(sock); |
||||
return -1; |
||||
} |
||||
|
||||
socket_close_wrapper(sock); |
||||
|
||||
// Копируем только IP (без порта)
|
||||
memset(result, 0, sizeof(*result)); |
||||
if (family == AF_INET) { |
||||
struct sockaddr_in *sin = (struct sockaddr_in *)result; |
||||
struct sockaddr_in *src = (struct sockaddr_in *)&local; |
||||
sin->sin_family = AF_INET; |
||||
sin->sin_addr = src->sin_addr; |
||||
sin->sin_port = 0; |
||||
} else { |
||||
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)result; |
||||
struct sockaddr_in6 *src6 = (struct sockaddr_in6 *)&local; |
||||
sin6->sin6_family = AF_INET6; |
||||
sin6->sin6_addr = src6->sin6_addr; |
||||
sin6->sin6_port = 0; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* getmyip.h - Определение локального IP-адреса для исходящих соединений |
||||
* |
||||
* Определяет локальный IP, который ОС выберет для отправки |
||||
* на заданный удаленный адрес через заданный локальный адрес. |
||||
*/ |
||||
|
||||
#ifndef GETMYIP_H |
||||
#define GETMYIP_H |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#ifdef _WIN32 |
||||
#include <winsock2.h> |
||||
#include <ws2tcpip.h> |
||||
#else |
||||
#include <sys/socket.h> |
||||
#include <netinet/in.h> |
||||
#endif |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/**
|
||||
* Определяет локальный IP-адрес, который ОС выберет для отправки |
||||
* на заданный удаленный адрес через заданный локальный адрес. |
||||
* |
||||
* Создает временный UDP-сокет, выполняет bind на local_addr, |
||||
* connect к remote_addr и получает выбранный локальный IP через getsockname. |
||||
* |
||||
* @param local_addr Локальный адрес (0.0.0.0 / :: для автоопределения) |
||||
* @param remote_addr Удаленный адрес (для определения маршрута) |
||||
* @param result Заполняемый локальный IP (порт = 0) |
||||
* @return 0 при успехе, -1 при ошибке |
||||
*/ |
||||
int get_outgoing_local_ip(const struct sockaddr_storage *local_addr, |
||||
const struct sockaddr_storage *remote_addr, |
||||
struct sockaddr_storage *result); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif |
||||
@ -0,0 +1,34 @@
|
||||
# Get My IP (getmyip.h) |
||||
|
||||
## 1. Назначение |
||||
Определение локального IP-адреса, который ОС выберет для исходящего UDP-соединения на заданный удалённый хост. Используется в `etcp_connections.c` при установке соединения: зная remote-адрес, модуль узнаёт, какой source IP ядро назначит пакетам (нужно для проверки NAT и привязки сокета к правильному интерфейсу). |
||||
|
||||
## 2. Как пользоваться |
||||
Передать локальный адрес-подсказку (или `0.0.0.0` для автоопределения) и удалённый адрес. Функция создаст временный UDP-сокет, сделает bind+connect, считает выбранный IP через `getsockname` и вернёт его в `result` (порт = 0, только IP). |
||||
|
||||
```c |
||||
struct sockaddr_storage local_hint; // AF_INET6, sin6_addr = in6addr_any |
||||
struct sockaddr_storage remote; // AF_INET6, адрес сервера |
||||
struct sockaddr_storage chosen_ip; |
||||
|
||||
// ... заполнить local_hint и remote ... |
||||
|
||||
if (get_outgoing_local_ip(&local_hint, &remote, &chosen_ip) == 0) { |
||||
// chosen_ip.ss_family == AF_INET6, sin6_addr содержит выбранный source IP |
||||
// chosen_ip.sin6_port == 0 |
||||
} |
||||
``` |
||||
|
||||
**Нюансы:** |
||||
- Функция временно создаёт и закрывает UDP-сокет — нельзя вызывать в hot path |
||||
- `local_addr` и `remote_addr` должны быть одного семейства (оба AF_INET или оба AF_INET6) |
||||
- Порт в `local_addr` заменяется на 0 (чтобы не конфликтовать с основными сокетами) |
||||
- Порт в `result` всегда 0 — только IP |
||||
|
||||
## 3. API |
||||
|
||||
### Функция |
||||
- **`get_outgoing_local_ip(local_addr, remote_addr, result)`** — создаёт временный UDP-сокет, привязывает к `local_addr` (с портом 0), делает `connect` к `remote_addr` для вычисления маршрута ядром, получает выбранный source IP через `getsockname`. Возвращает 0 при успехе, -1 при ошибке (NULL-аргументы, несовпадение семейств, ошибки сокета). |
||||
|
||||
### Зависимости |
||||
- `socket_compat.h` — кроссплатформенные обёртки: `socket_create_udp`, `SOCKET_INVALID`, `socket_close_wrapper`, `socket_get_error`, `socket_strerror` |
||||
@ -0,0 +1,87 @@
|
||||
#include "json_flat.h" |
||||
#include <string.h> |
||||
|
||||
#define SKIP_WS(p, end) while ((p) < (end) && (*(p) == ' ' || *(p) == '\t' || *(p) == '\n' || *(p) == '\r')) (p)++ |
||||
#define CHECK(p, end) if ((p) >= (end)) return -1 |
||||
|
||||
static int json_read_string(const char** pp, const char* end, char* out, size_t out_sz) { |
||||
const char* p = *pp; |
||||
CHECK(p, end); |
||||
if (*p != '"') return -1; p++; |
||||
size_t n = 0; |
||||
while (p < end && *p != '"') { |
||||
char ch = *p; |
||||
if (*p == '\\') { p++; CHECK(p, end); |
||||
switch (*p) { |
||||
case '"': ch = '"'; break; |
||||
case '\\': ch = '\\'; break; |
||||
case '/': ch = '/'; break; |
||||
case 'n': ch = '\n'; break; |
||||
case 'r': ch = '\r'; break; |
||||
case 't': ch = '\t'; break; |
||||
default: return -1; |
||||
} |
||||
} |
||||
if (out && n < out_sz - 1) out[n++] = ch; |
||||
p++; |
||||
} |
||||
CHECK(p, end); /* closing quote */ |
||||
p++; |
||||
if (out && n < out_sz) out[n] = '\0'; |
||||
else if (out && out_sz > 0) out[out_sz - 1] = '\0'; |
||||
*pp = p; |
||||
return 0; |
||||
} |
||||
|
||||
static int json_parse_pair(const char** pp, const char* end, |
||||
json_flat_cb cb, void* arg) { |
||||
const char* p = *pp; |
||||
SKIP_WS(p, end); CHECK(p, end); |
||||
if (*p == '}') { *pp = p; return 0; } |
||||
|
||||
char key[128], val[256]; |
||||
if (json_read_string(&p, end, key, sizeof(key)) != 0) return -1; |
||||
SKIP_WS(p, end); CHECK(p, end); |
||||
if (*p != ':') return -1; p++; |
||||
SKIP_WS(p, end); CHECK(p, end); |
||||
if (json_read_string(&p, end, val, sizeof(val)) != 0) return -1; |
||||
|
||||
if (cb && cb(key, val, arg) != 0) { *pp = p; return 1; } |
||||
|
||||
SKIP_WS(p, end); CHECK(p, end); |
||||
if (*p == ',') p++; |
||||
|
||||
*pp = p; |
||||
return 0; |
||||
} |
||||
|
||||
int json_flat_parse(const char* json, json_flat_cb cb, void* arg) { |
||||
if (!json) return -1; |
||||
const char* p = json, *end = json + strlen(json); |
||||
SKIP_WS(p, end); CHECK(p, end); |
||||
if (*p != '{') return -1; p++; |
||||
|
||||
for (;;) { |
||||
int rc = json_parse_pair(&p, end, cb, arg); |
||||
if (rc < 0) return -1; |
||||
if (rc > 0) return 0; /* callback stopped */ |
||||
SKIP_WS(p, end); CHECK(p, end); |
||||
if (*p == '}') return 0; |
||||
} |
||||
} |
||||
|
||||
struct get_ctx { const char* key; char* out; size_t sz; int found; }; |
||||
|
||||
static int get_cb(const char* k, const char* v, void* arg) { |
||||
struct get_ctx* g = (struct get_ctx*)arg; |
||||
if (strcmp(k, g->key) == 0) { if (g->out) { size_t vl = strlen(v); |
||||
size_t cp = vl < g->sz - 1 ? vl : g->sz - 1; memcpy(g->out, v, cp); g->out[cp] = '\0'; } |
||||
g->found = 1; return 1; } |
||||
return 0; |
||||
} |
||||
|
||||
int json_flat_get(const char* json, const char* key, char* value_out, size_t value_sz) { |
||||
struct get_ctx g = { key, value_out, value_sz, 0 }; |
||||
if (json_flat_parse(json, get_cb, &g) != 0) return -1; |
||||
return g.found ? 0 : -1; |
||||
} |
||||
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @file json_flat.h |
||||
* @brief Парсер плоского JSON без вложенности: {"key":"value",...} |
||||
* |
||||
* Только строковые значения. Без malloc, без зависимостей. |
||||
*/ |
||||
|
||||
#ifndef JSON_FLAT_H |
||||
#define JSON_FLAT_H |
||||
|
||||
#include <stddef.h> |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
typedef int (*json_flat_cb)(const char* key, const char* value, void* arg); |
||||
|
||||
int json_flat_parse(const char* json, json_flat_cb cb, void* arg); |
||||
int json_flat_get(const char* json, const char* key, char* value_out, size_t value_sz); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif |
||||
@ -0,0 +1,23 @@
|
||||
mtest |
||||
mtest[23456] |
||||
testdb |
||||
mdb_copy |
||||
mdb_stat |
||||
mdb_dump |
||||
mdb_load |
||||
*.lo |
||||
*.[ao] |
||||
*.so |
||||
*.exe |
||||
*[~#] |
||||
*.bak |
||||
*.orig |
||||
*.rej |
||||
*.gcov |
||||
*.gcda |
||||
*.gcno |
||||
core |
||||
core.* |
||||
valgrind.* |
||||
man/ |
||||
html/ |
||||
@ -0,0 +1,317 @@
|
||||
LMDB 0.9 Change Log |
||||
|
||||
LMDB 0.9.36 Engineering |
||||
ITS#7772 - limit sub-page growth when re-adding existing data |
||||
ITS#8335 - more input validation for put(MDB_MULTIPLE) |
||||
ITS#8386 - be a bit more precise that mdb_get retrieves data in intro.doc |
||||
ITS#8590 - suppress SIGPIPE on Mac OSX for mdb_copy/compact |
||||
ITS#8739 - don't use fdatasync on FreeBSD 11.0 and older |
||||
ITS#8824 - mdb_dump: cleanup check for MDB_SUCCESS |
||||
ITS#9388 - fix mdb_stat page counts for dupsort DBs |
||||
ITS#10054 - fix write of 2GB values |
||||
ITS#10108 - mdb_dump: fix comment |
||||
ITS#10454 - fix metapage sync on Mac OSX |
||||
ITS#10504 - silence linker waarning on Mac OSX |
||||
ITS#10522 - reject 0-length values in DUPSORT DBs |
||||
|
||||
LMDB 0.9.35 Release (2026/01/27) |
||||
ITS#10434 - Fix typo affecting Mac OSX |
||||
|
||||
LMDB 0.9.34 Release (2026/01/26) |
||||
ITS#9564 - fix race condition freeing spilled pages at end of transaction |
||||
ITS#10222 - Update mdb_dump(1) and mdb_load(1) man pages for append (-a) option |
||||
ITS#10275 - mdb_load: add -Q option to use NOSYNC |
||||
ITS#10296 - fix fdatasync on MacOS |
||||
ITS#10342 - fix memleak in mdb_txn_begin for nested txns |
||||
ITS#10346 - fix mdb_env_copy2 with values > (2GB-16) |
||||
ITS#10355 - fix mplay build on musl |
||||
ITS#10396 - fix mdb_cursor_del0 with multiple DUPSORT cursors |
||||
ITS#10419 - add support for NetBSD |
||||
ITS#10420 - add support for Haiku |
||||
ITS#10421 - mdb_load: check for malicious input |
||||
|
||||
LMDB 0.9.33 Release (2024/05/21) |
||||
ITS#9037 mdb_page_search: fix error code when DBI record is missing |
||||
ITS#10198 For win32, stop passing ignored parameter |
||||
ITS#10212 Fix meta page usage by read only tools |
||||
|
||||
LMDB 0.9.32 Release (2024/01/29) |
||||
ITS#9378 - Add ability to replay log and replay log tool |
||||
ITS#10095 - partial revert of ITS#9278. The patch was incorrect and introduced numerous race conditions. |
||||
ITS#10125 - mdb_load: fix cursor reinit in Append mode |
||||
ITS#10137 - Allow users to define MDB_IDL_LOGN |
||||
|
||||
LMDB 0.9.31 Release (2023/07/10) |
||||
ITS#8447 - Fix cursor_put(MDB_CURRENT) on DUPSORT DB with different sized data |
||||
|
||||
LMDB 0.9.30 Release (2023/02/08) |
||||
ITS#9806 - LMDB page_split: key threshold depends on page size |
||||
ITS#9916 - avoid gcc optimization bug on sparc64 linux |
||||
ITS#9919 - Mark infrequently used functions as cold |
||||
ITS#9723 - clear C_EOF on cursor with MDB_FIRST_DUP |
||||
ITS#9030 - Use sys/cachectl.h rather than asm/cachectl.h on mips |
||||
|
||||
LMDB 0.9.29 Release (2021/03/16) |
||||
ITS#9461 refix ITS#9376 |
||||
ITS#9500 fix regression from ITS#8662 |
||||
|
||||
LMDB 0.9.28 Release (2021/02/04) |
||||
ITS#8662 add -a append option to mdb_load |
||||
|
||||
LMDB 0.9.27 Release (2020/10/26) |
||||
ITS#9376 fix repeated DUPSORT cursor deletes |
||||
|
||||
LMDB 0.9.26 Release (2020/08/11) |
||||
ITS#9278 fix robust mutex cleanup for FreeBSD |
||||
|
||||
LMDB 0.9.25 Release (2020/01/30) |
||||
ITS#9068 fix mdb_dump/load backslashes in printable content |
||||
ITS#9118 add MAP_NOSYNC for FreeBSD |
||||
ITS#9155 free mt_spill_pgs in non-nested txn on end |
||||
|
||||
LMDB 0.9.24 Release (2019/07/24) |
||||
ITS#8969 Tweak mdb_page_split |
||||
ITS#8975 WIN32 fix writemap set_mapsize crash |
||||
ITS#9007 Fix loose pages in WRITEMAP |
||||
|
||||
LMDB 0.9.23 Release (2018/12/19) |
||||
ITS#8756 Fix loose pages in dirty list |
||||
ITS#8831 Fix mdb_load flag init |
||||
ITS#8844 Fix mdb_env_close in forked process |
||||
Documentation |
||||
ITS#8857 mdb_cursor_del doesn't invalidate cursor |
||||
ITS#8908 GET_MULTIPLE etc don't change passed in key |
||||
|
||||
LMDB 0.9.22 Release (2018/03/22) |
||||
Fix MDB_DUPSORT alignment bug (ITS#8819) |
||||
Fix regression with new db from 0.9.19 (ITS#8760) |
||||
Fix liblmdb to build on Solaris (ITS#8612) |
||||
Fix delete behavior with DUPSORT DB (ITS#8622) |
||||
Fix mdb_cursor_get/mdb_cursor_del behavior (ITS#8722) |
||||
|
||||
LMDB 0.9.21 Release (2017/06/01) |
||||
Fix xcursor after cursor_del (ITS#8622) |
||||
|
||||
LMDB 0.9.20 (Withdrawn) |
||||
Fix mdb_load with escaped plaintext (ITS#8558) |
||||
Fix mdb_cursor_last / mdb_put interaction (ITS#8557) |
||||
|
||||
LMDB 0.9.19 Release (2016/12/28) |
||||
Fix mdb_env_cwalk cursor init (ITS#8424) |
||||
Fix robust mutexes on Solaris 10/11 (ITS#8339) |
||||
Tweak Win32 error message buffer |
||||
Fix MDB_GET_BOTH on non-dup record (ITS#8393) |
||||
Optimize mdb_drop |
||||
Fix xcursors after mdb_cursor_del (ITS#8406) |
||||
Fix MDB_NEXT_DUP after mdb_cursor_del (ITS#8412) |
||||
Fix mdb_cursor_put resetting C_EOF (ITS#8489) |
||||
Fix mdb_env_copyfd2 to return EPIPE on SIGPIPE (ITS#8504) |
||||
Fix mdb_env_copy with empty DB (ITS#8209) |
||||
Fix behaviors with fork (ITS#8505) |
||||
Fix mdb_dbi_open with mainDB cursors (ITS#8542) |
||||
Fix robust mutexes on kFreeBSD (ITS#8554) |
||||
Fix utf8_to_utf16 error checks (ITS#7992) |
||||
Fix F_NOCACHE on MacOS, error is non-fatal (ITS#7682) |
||||
Build |
||||
Make shared lib suffix overridable (ITS#8481) |
||||
Documentation |
||||
Cleanup doxygen nits |
||||
Note reserved vs actual mem/disk usage |
||||
|
||||
|
||||
LMDB 0.9.18 Release (2016/02/05) |
||||
Fix robust mutex detection on glibc 2.10-11 (ITS#8330) |
||||
Fix page_search_root assert on FreeDB (ITS#8336) |
||||
Fix MDB_APPENDDUP vs. rewrite(single item) (ITS#8334) |
||||
Fix mdb_copy of large files on Windows |
||||
Fix subcursor move after delete (ITS#8355) |
||||
Fix mdb_midl_shirnk off-by-one (ITS#8363) |
||||
Check for utf8_to_utf16 failures (ITS#7992) |
||||
Catch strdup failure in mdb_dbi_open |
||||
Build |
||||
Additional makefile var tweaks (ITS#8169) |
||||
Documentation |
||||
Add Getting Started page |
||||
Update WRITEMAP description |
||||
|
||||
|
||||
LMDB 0.9.17 Release (2015/11/30) |
||||
Fix ITS#7377 catch calloc failure |
||||
Fix ITS#8237 regression from ITS#7589 |
||||
Fix ITS#8238 page_split for DUPFIXED pages |
||||
Fix ITS#8221 MDB_PAGE_FULL on delete/rebalance |
||||
Fix ITS#8258 rebalance/split assert |
||||
Fix ITS#8263 cursor_put cursor tracking |
||||
Fix ITS#8264 cursor_del cursor tracking |
||||
Fix ITS#8310 cursor_del cursor tracking |
||||
Fix ITS#8299 mdb_del cursor tracking |
||||
Fix ITS#8300 mdb_del cursor tracking |
||||
Fix ITS#8304 mdb_del cursor tracking |
||||
Fix ITS#7771 fakepage cursor tracking |
||||
Fix ITS#7789 ensure mapsize >= pages in use |
||||
Fix ITS#7971 mdb_txn_renew0() new reader slots |
||||
Fix ITS#7969 use __sync_synchronize on non-x86 |
||||
Fix ITS#8311 page_split from update_key |
||||
Fix ITS#8312 loose pages in nested txn |
||||
Fix ITS#8313 mdb_rebalance dummy cursor |
||||
Fix ITS#8315 dirty_room in nested txn |
||||
Fix ITS#8323 dirty_list in nested txn |
||||
Fix ITS#8316 page_merge cursor tracking |
||||
Fix ITS#8321 cursor tracking |
||||
Fix ITS#8319 mdb_load error messages |
||||
Fix ITS#8320 mdb_load plaintext input |
||||
Added mdb_txn_id() (ITS#7994) |
||||
Added robust mutex support |
||||
Miscellaneous cleanup/simplification |
||||
Build |
||||
Create install dirs if needed (ITS#8256) |
||||
Fix ThreadProc decl on Win32/MSVC (ITS#8270) |
||||
Added ssize_t typedef for MSVC (ITS#8067) |
||||
Use ANSI apis on Windows (ITS#8069) |
||||
Use O_SYNC if O_DSYNC,MDB_DSYNC are not defined (ITS#7209) |
||||
Allow passing AR to make (ITS#8168) |
||||
Allow passing mandir to make install (ITS#8169) |
||||
|
||||
LMDB 0.9.16 Release (2015/08/14) |
||||
Fix cursor EOF bug (ITS#8190) |
||||
Fix handling of subDB records (ITS#8181) |
||||
Fix mdb_midl_shrink() usage (ITS#8200) |
||||
|
||||
LMDB 0.9.15 Release (2015/06/19) |
||||
Fix txn init (ITS#7961,#7987) |
||||
Fix MDB_PREV_DUP (ITS#7955,#7671) |
||||
Fix compact of empty env (ITS#7956) |
||||
Fix mdb_copy file mode |
||||
Fix mdb_env_close() after failed mdb_env_open() |
||||
Fix mdb_rebalance collapsing root (ITS#8062) |
||||
Fix mdb_load with large values (ITS#8066) |
||||
Fix to retry writes on EINTR (ITS#8106) |
||||
Fix mdb_cursor_del on empty DB (ITS#8109) |
||||
Fix MDB_INTEGERDUP key compare (ITS#8117) |
||||
Fix error handling (ITS#7959,#8157,etc.) |
||||
Fix race conditions (ITS#7969,7970) |
||||
Added workaround for fdatasync bug in ext3fs |
||||
Build |
||||
Don't use -fPIC for static lib |
||||
Update .gitignore (ITS#7952,#7953) |
||||
Cleanup for "make test" (ITS#7841), "make clean", mtest*.c |
||||
Misc. Android/Windows cleanup |
||||
Documentation |
||||
Fix MDB_APPEND doc |
||||
Fix MDB_MAXKEYSIZE doc (ITS#8156) |
||||
Fix mdb_cursor_put,mdb_cursor_del EACCES description |
||||
Fix mdb_env_sync(MDB_RDONLY env) doc (ITS#8021) |
||||
Clarify MDB_WRITEMAP doc (ITS#8021) |
||||
Clarify mdb_env_open doc |
||||
Clarify mdb_dbi_open doc |
||||
|
||||
LMDB 0.9.14 Release (2014/09/20) |
||||
Fix to support 64K page size (ITS#7713) |
||||
Fix to persist decreased as well as increased mapsizes (ITS#7789) |
||||
Fix cursor bug when deleting last node of a DUPSORT key |
||||
Fix mdb_env_info to return FIXEDMAP address |
||||
Fix ambiguous error code from writing to closed DBI (ITS#7825) |
||||
Fix mdb_copy copying past end of file (ITS#7886) |
||||
Fix cursor bugs from page_merge/rebalance |
||||
Fix to dirty fewer pages in deletes (mdb_page_loose()) |
||||
Fix mdb_dbi_open creating subDBs (ITS#7917) |
||||
Fix mdb_cursor_get(_DUP) with single value (ITS#7913) |
||||
Fix Windows compat issues in mtests (ITS#7879) |
||||
Add compacting variant of mdb_copy |
||||
Add BigEndian integer key compare code |
||||
Add mdb_dump/mdb_load utilities |
||||
|
||||
LMDB 0.9.13 Release (2014/06/18) |
||||
Fix mdb_page_alloc unlimited overflow page search |
||||
Documentation |
||||
Re-fix MDB_CURRENT doc (ITS#7793) |
||||
Fix MDB_GET_MULTIPLE/MDB_NEXT_MULTIPLE doc |
||||
|
||||
LMDB 0.9.12 Release (2014/06/13) |
||||
Fix MDB_GET_BOTH regression (ITS#7875,#7681) |
||||
Fix MDB_MULTIPLE writing multiple keys (ITS#7834) |
||||
Fix mdb_rebalance (ITS#7829) |
||||
Fix mdb_page_split (ITS#7815) |
||||
Fix md_entries count (ITS#7861,#7828,#7793) |
||||
Fix MDB_CURRENT (ITS#7793) |
||||
Fix possible crash on Windows DLL detach |
||||
Misc code cleanup |
||||
Documentation |
||||
mdb_cursor_put: cursor moves on error (ITS#7771) |
||||
|
||||
|
||||
LMDB 0.9.11 Release (2014/01/15) |
||||
Add mdb_env_set_assert() (ITS#7775) |
||||
Fix: invalidate txn on page allocation errors (ITS#7377) |
||||
Fix xcursor tracking in mdb_cursor_del0() (ITS#7771) |
||||
Fix corruption from deletes (ITS#7756) |
||||
Fix Windows/MSVC build issues |
||||
Raise safe limit of max MDB_MAXKEYSIZE |
||||
Misc code cleanup |
||||
Documentation |
||||
Remove spurious note about non-overlapping flags (ITS#7665) |
||||
|
||||
LMDB 0.9.10 Release (2013/11/12) |
||||
Add MDB_NOMEMINIT option |
||||
Fix mdb_page_split() again (ITS#7589) |
||||
Fix MDB_NORDAHEAD definition (ITS#7734) |
||||
Fix mdb_cursor_del() positioning (ITS#7733) |
||||
Partial fix for larger page sizes (ITS#7713) |
||||
Fix Windows64/MSVC build issues |
||||
|
||||
LMDB 0.9.9 Release (2013/10/24) |
||||
Add mdb_env_get_fd() |
||||
Add MDB_NORDAHEAD option |
||||
Add MDB_NOLOCK option |
||||
Avoid wasting space in mdb_page_split() (ITS#7589) |
||||
Fix mdb_page_merge() cursor fixup (ITS#7722) |
||||
Fix mdb_cursor_del() on last delete (ITS#7718) |
||||
Fix adding WRITEMAP on existing env (ITS#7715) |
||||
Fix nested txns (ITS#7515) |
||||
Fix mdb_env_copy() O_DIRECT bug (ITS#7682) |
||||
Fix mdb_cursor_set(SET_RANGE) return code (ITS#7681) |
||||
Fix mdb_rebalance() cursor fixup (ITS#7701) |
||||
Misc code cleanup |
||||
Documentation |
||||
Note that by default, readers need write access |
||||
|
||||
|
||||
LMDB 0.9.8 Release (2013/09/09) |
||||
Allow mdb_env_set_mapsize() on an open environment |
||||
Fix mdb_dbi_flags() (ITS#7672) |
||||
Fix mdb_page_unspill() in nested txns |
||||
Fix mdb_cursor_get(CURRENT|NEXT) after a delete |
||||
Fix mdb_cursor_get(DUP) to always return key (ITS#7671) |
||||
Fix mdb_cursor_del() to always advance to next item (ITS#7670) |
||||
Fix mdb_cursor_set(SET_RANGE) for tree with single page (ITS#7681) |
||||
Fix mdb_env_copy() retry open if O_DIRECT fails (ITS#7682) |
||||
Tweak mdb_page_spill() to be less aggressive |
||||
Documentation |
||||
Update caveats since mdb_reader_check() added in 0.9.7 |
||||
|
||||
LMDB 0.9.7 Release (2013/08/17) |
||||
Don't leave stale lockfile on failed RDONLY open (ITS#7664) |
||||
Fix mdb_page_split() ref beyond cursor depth |
||||
Fix read txn data race (ITS#7635) |
||||
Fix mdb_rebalance (ITS#7536, #7538) |
||||
Fix mdb_drop() (ITS#7561) |
||||
Misc DEBUG macro fixes |
||||
Add MDB_NOTLS envflag |
||||
Add mdb_env_copyfd() |
||||
Add mdb_txn_env() (ITS#7660) |
||||
Add mdb_dbi_flags() (ITS#7661) |
||||
Add mdb_env_get_maxkeysize() |
||||
Add mdb_env_reader_list()/mdb_env_reader_check() |
||||
Add mdb_page_spill/unspill, remove hard txn size limit |
||||
Use shorter names for semaphores (ITS#7615) |
||||
Build |
||||
Fix install target (ITS#7656) |
||||
Documentation |
||||
Misc updates for cursors, DB handles, data lifetime |
||||
|
||||
LMDB 0.9.6 Release (2013/02/25) |
||||
Many fixes/enhancements |
||||
|
||||
LMDB 0.9.5 Release (2012/11/30) |
||||
Renamed from libmdb to liblmdb |
||||
Many fixes/enhancements |
||||
@ -0,0 +1,20 @@
|
||||
Copyright 2011-2021 Howard Chu, Symas Corp. |
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted only as authorized by the OpenLDAP |
||||
Public License. |
||||
|
||||
A copy of this license is available in the file LICENSE in the |
||||
top-level directory of the distribution or, alternatively, at |
||||
<http://www.OpenLDAP.org/license.html>. |
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation. |
||||
|
||||
Individual files and/or contributed packages may be copyright by |
||||
other parties and/or subject to additional restrictions. |
||||
|
||||
This work also contains materials derived from public sources. |
||||
|
||||
Additional information about OpenLDAP can be obtained at |
||||
<http://www.openldap.org/>. |
||||
@ -0,0 +1,47 @@
|
||||
The OpenLDAP Public License |
||||
Version 2.8, 17 August 2003 |
||||
|
||||
Redistribution and use of this software and associated documentation |
||||
("Software"), with or without modification, are permitted provided |
||||
that the following conditions are met: |
||||
|
||||
1. Redistributions in source form must retain copyright statements |
||||
and notices, |
||||
|
||||
2. Redistributions in binary form must reproduce applicable copyright |
||||
statements and notices, this list of conditions, and the following |
||||
disclaimer in the documentation and/or other materials provided |
||||
with the distribution, and |
||||
|
||||
3. Redistributions must contain a verbatim copy of this document. |
||||
|
||||
The OpenLDAP Foundation may revise this license from time to time. |
||||
Each revision is distinguished by a version number. You may use |
||||
this Software under terms of this license revision or under the |
||||
terms of any subsequent revision of the license. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS |
||||
CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, |
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY |
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT |
||||
SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S) |
||||
OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
The names of the authors and copyright holders must not be used in |
||||
advertising or otherwise to promote the sale, use or other dealing |
||||
in this Software without specific, written prior permission. Title |
||||
to copyright in this Software shall at all times remain with copyright |
||||
holders. |
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation. |
||||
|
||||
Copyright 1999-2003 The OpenLDAP Foundation, Redwood City, |
||||
California, USA. All Rights Reserved. Permission to copy and |
||||
distribute verbatim copies of this document is granted. |
||||
@ -0,0 +1,192 @@
|
||||
/* |
||||
* Copyright 2015-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>. |
||||
*/ |
||||
/** @page starting Getting Started |
||||
|
||||
LMDB is compact, fast, powerful, and robust and implements a simplified |
||||
variant of the BerkeleyDB (BDB) API. (BDB is also very powerful, and verbosely |
||||
documented in its own right.) After reading this page, the main |
||||
\ref mdb documentation should make sense. Thanks to Bert Hubert |
||||
for creating the |
||||
<a href="https://github.com/ahupowerdns/ahutils/blob/master/lmdb-semantics.md"> |
||||
initial version</a> of this writeup. |
||||
|
||||
Everything starts with an environment, created by #mdb_env_create(). |
||||
Once created, this environment must also be opened with #mdb_env_open(). |
||||
|
||||
#mdb_env_open() gets passed a name which is interpreted as a directory |
||||
path. Note that this directory must exist already, it is not created |
||||
for you. Within that directory, a lock file and a storage file will be |
||||
generated. If you don't want to use a directory, you can pass the |
||||
#MDB_NOSUBDIR option, in which case the path you provided is used |
||||
directly as the data file, and another file with a "-lock" suffix |
||||
added will be used for the lock file. |
||||
|
||||
Once the environment is open, a transaction can be created within it |
||||
using #mdb_txn_begin(). Transactions may be read-write or read-only, |
||||
and read-write transactions may be nested. A transaction must only |
||||
be used by one thread at a time. Transactions are always required, |
||||
even for read-only access. The transaction provides a consistent |
||||
view of the data. |
||||
|
||||
Once a transaction has been created, a database can be opened within it |
||||
using #mdb_dbi_open(). If only one database will ever be used in the |
||||
environment, a NULL can be passed as the database name. For named |
||||
databases, the #MDB_CREATE flag must be used to create the database |
||||
if it doesn't already exist. Also, #mdb_env_set_maxdbs() must be |
||||
called after #mdb_env_create() and before #mdb_env_open() to set the |
||||
maximum number of named databases you want to support. |
||||
|
||||
Note: a single transaction can open multiple databases. Generally |
||||
databases should only be opened once, by the first transaction in |
||||
the process. After the first transaction completes, the database |
||||
handles can freely be used by all subsequent transactions. |
||||
|
||||
Within a transaction, #mdb_get() can retrieve and #mdb_put() can store single |
||||
key/value pairs if that is all you need to do (but see \ref Cursors |
||||
below if you want to do more). |
||||
|
||||
A key/value pair is expressed as two #MDB_val structures. This struct |
||||
has two fields, \c mv_size and \c mv_data. The data is a \c void pointer to |
||||
an array of \c mv_size bytes. |
||||
|
||||
Because LMDB is very efficient (and usually zero-copy), the data returned |
||||
in an #MDB_val structure may be memory-mapped straight from disk. In |
||||
other words <b>look but do not touch</b> (or free() for that matter). |
||||
Once a transaction is closed, the values can no longer be used, so |
||||
make a copy if you need to keep them after that. |
||||
|
||||
@section Cursors Cursors |
||||
|
||||
To do more powerful things, we must use a cursor. |
||||
|
||||
Within the transaction, a cursor can be created with #mdb_cursor_open(). |
||||
With this cursor we can store/retrieve/delete (multiple) values using |
||||
#mdb_cursor_get(), #mdb_cursor_put(), and #mdb_cursor_del(). |
||||
|
||||
#mdb_cursor_get() positions itself depending on the cursor operation |
||||
requested, and for some operations, on the supplied key. For example, |
||||
to list all key/value pairs in a database, use operation #MDB_FIRST for |
||||
the first call to #mdb_cursor_get(), and #MDB_NEXT on subsequent calls, |
||||
until the end is hit. |
||||
|
||||
To retrieve all keys starting from a specified key value, use #MDB_SET. |
||||
For more cursor operations, see the \ref mdb docs. |
||||
|
||||
When using #mdb_cursor_put(), either the function will position the |
||||
cursor for you based on the \b key, or you can use operation |
||||
#MDB_CURRENT to use the current position of the cursor. Note that |
||||
\b key must then match the current position's key. |
||||
|
||||
@subsection summary Summarizing the Opening |
||||
|
||||
So we have a cursor in a transaction which opened a database in an |
||||
environment which is opened from a filesystem after it was |
||||
separately created. |
||||
|
||||
Or, we create an environment, open it from a filesystem, create a |
||||
transaction within it, open a database within that transaction, |
||||
and create a cursor within all of the above. |
||||
|
||||
Got it? |
||||
|
||||
@section thrproc Threads and Processes |
||||
|
||||
LMDB uses POSIX locks on files, and these locks have issues if one |
||||
process opens a file multiple times. Because of this, do not |
||||
#mdb_env_open() a file multiple times from a single process. Instead, |
||||
share the LMDB environment that has opened the file across all threads. |
||||
Otherwise, if a single process opens the same environment multiple times, |
||||
closing it once will remove all the locks held on it, and the other |
||||
instances will be vulnerable to corruption from other processes. |
||||
|
||||
Also note that a transaction is tied to one thread by default using |
||||
Thread Local Storage. If you want to pass read-only transactions across |
||||
threads, you can use the #MDB_NOTLS option on the environment. |
||||
|
||||
@section txns Transactions, Rollbacks, etc. |
||||
|
||||
To actually get anything done, a transaction must be committed using |
||||
#mdb_txn_commit(). Alternatively, all of a transaction's operations |
||||
can be discarded using #mdb_txn_abort(). In a read-only transaction, |
||||
any cursors will \b not automatically be freed. In a read-write |
||||
transaction, all cursors will be freed and must not be used again. |
||||
|
||||
For read-only transactions, obviously there is nothing to commit to |
||||
storage. The transaction still must eventually be aborted to close |
||||
any database handle(s) opened in it, or committed to keep the |
||||
database handles around for reuse in new transactions. |
||||
|
||||
In addition, as long as a transaction is open, a consistent view of |
||||
the database is kept alive, which requires storage. A read-only |
||||
transaction that no longer requires this consistent view should |
||||
be terminated (committed or aborted) when the view is no longer |
||||
needed (but see below for an optimization). |
||||
|
||||
There can be multiple simultaneously active read-only transactions |
||||
but only one that can write. Once a single read-write transaction |
||||
is opened, all further attempts to begin one will block until the |
||||
first one is committed or aborted. This has no effect on read-only |
||||
transactions, however, and they may continue to be opened at any time. |
||||
|
||||
@section dupkeys Duplicate Keys |
||||
|
||||
#mdb_get() and #mdb_put() respectively have no and only some support |
||||
for multiple key/value pairs with identical keys. If there are multiple |
||||
values for a key, #mdb_get() will only return the first value. |
||||
|
||||
When multiple values for one key are required, pass the #MDB_DUPSORT |
||||
flag to #mdb_dbi_open(). In an #MDB_DUPSORT database, by default |
||||
#mdb_put() will not replace the value for a key if the key existed |
||||
already. Instead it will add the new value to the key. In addition, |
||||
#mdb_del() will pay attention to the value field too, allowing for |
||||
specific values of a key to be deleted. |
||||
|
||||
Finally, additional cursor operations become available for |
||||
traversing through and retrieving duplicate values. |
||||
|
||||
@section optim Some Optimization |
||||
|
||||
If you frequently begin and abort read-only transactions, as an |
||||
optimization, it is possible to only reset and renew a transaction. |
||||
|
||||
#mdb_txn_reset() releases any old copies of data kept around for |
||||
a read-only transaction. To reuse this reset transaction, call |
||||
#mdb_txn_renew() on it. Any cursors in this transaction must also |
||||
be renewed using #mdb_cursor_renew(). |
||||
|
||||
Note that #mdb_txn_reset() is similar to #mdb_txn_abort() and will |
||||
close any databases you opened within the transaction. |
||||
|
||||
To permanently free a transaction, reset or not, use #mdb_txn_abort(). |
||||
|
||||
@section cleanup Cleaning Up |
||||
|
||||
For read-only transactions, any cursors created within it must |
||||
be closed using #mdb_cursor_close(). |
||||
|
||||
It is very rarely necessary to close a database handle, and in |
||||
general they should just be left open. |
||||
|
||||
@section onward The Full API |
||||
|
||||
The full \ref mdb documentation lists further details, like how to: |
||||
|
||||
\li size a database (the default limits are intentionally small) |
||||
\li drop and clean a database |
||||
\li detect and report errors |
||||
\li optimize (bulk) loading speed |
||||
\li (temporarily) reduce robustness to gain even more speed |
||||
\li gather statistics about the database |
||||
\li define custom sort orders |
||||
|
||||
*/ |
||||
@ -0,0 +1,55 @@
|
||||
.TH MDB_COPY 1 "2014/07/01" "LMDB 0.9.14" |
||||
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved. |
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE. |
||||
.SH NAME |
||||
mdb_copy \- LMDB environment copy tool |
||||
.SH SYNOPSIS |
||||
.B mdb_copy |
||||
[\c |
||||
.BR \-V ] |
||||
[\c |
||||
.BR \-c ] |
||||
[\c |
||||
.BR \-n ] |
||||
.B srcpath |
||||
[\c |
||||
.BR dstpath ] |
||||
.SH DESCRIPTION |
||||
The |
||||
.B mdb_copy |
||||
utility copies an LMDB environment. The environment can |
||||
be copied regardless of whether it is currently in use. |
||||
No lockfile is created, since it gets recreated at need. |
||||
|
||||
If |
||||
.I dstpath |
||||
is specified it must be the path of an empty directory |
||||
for storing the backup. Otherwise, the backup will be |
||||
written to stdout. |
||||
|
||||
.SH OPTIONS |
||||
.TP |
||||
.BR \-V |
||||
Write the library version number to the standard output, and exit. |
||||
.TP |
||||
.BR \-c |
||||
Compact while copying. Only current data pages will be copied; freed |
||||
or unused pages will be omitted from the copy. This option will |
||||
slow down the backup process as it is more CPU-intensive. |
||||
Currently it fails if the environment has suffered a page leak. |
||||
.TP |
||||
.BR \-n |
||||
Open LDMB environment(s) which do not use subdirectories. |
||||
|
||||
.SH DIAGNOSTICS |
||||
Exit status is zero if no errors occur. |
||||
Errors result in a non-zero exit status and |
||||
a diagnostic message being written to standard error. |
||||
.SH CAVEATS |
||||
This utility can trigger significant file size growth if run |
||||
in parallel with write transactions, because pages which they |
||||
free during copying cannot be reused until the copy is done. |
||||
.SH "SEE ALSO" |
||||
.BR mdb_stat (1) |
||||
.SH AUTHOR |
||||
Howard Chu of Symas Corporation <http://www.symas.com> |
||||
@ -0,0 +1,82 @@
|
||||
/* mdb_copy.c - memory-mapped database backup tool */ |
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
#ifdef _WIN32 |
||||
#include <windows.h> |
||||
#define MDB_STDOUT GetStdHandle(STD_OUTPUT_HANDLE) |
||||
#else |
||||
#define MDB_STDOUT 1 |
||||
#endif |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <signal.h> |
||||
#include "lmdb.h" |
||||
|
||||
static void |
||||
sighandle(int sig) |
||||
{ |
||||
} |
||||
|
||||
int main(int argc,char * argv[]) |
||||
{ |
||||
int rc; |
||||
MDB_env *env; |
||||
const char *progname = argv[0], *act; |
||||
unsigned flags = MDB_RDONLY; |
||||
unsigned cpflags = 0; |
||||
|
||||
for (; argc > 1 && argv[1][0] == '-'; argc--, argv++) { |
||||
if (argv[1][1] == 'n' && argv[1][2] == '\0') |
||||
flags |= MDB_NOSUBDIR; |
||||
else if (argv[1][1] == 'c' && argv[1][2] == '\0') |
||||
cpflags |= MDB_CP_COMPACT; |
||||
else if (argv[1][1] == 'V' && argv[1][2] == '\0') { |
||||
printf("%s\n", MDB_VERSION_STRING); |
||||
exit(0); |
||||
} else |
||||
argc = 0; |
||||
} |
||||
|
||||
if (argc<2 || argc>3) { |
||||
fprintf(stderr, "usage: %s [-V] [-c] [-n] srcpath [dstpath]\n", progname); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
|
||||
#ifdef SIGPIPE |
||||
signal(SIGPIPE, sighandle); |
||||
#endif |
||||
#ifdef SIGHUP |
||||
signal(SIGHUP, sighandle); |
||||
#endif |
||||
signal(SIGINT, sighandle); |
||||
signal(SIGTERM, sighandle); |
||||
|
||||
act = "opening environment"; |
||||
rc = mdb_env_create(&env); |
||||
if (rc == MDB_SUCCESS) { |
||||
rc = mdb_env_open(env, argv[1], flags, 0600); |
||||
} |
||||
if (rc == MDB_SUCCESS) { |
||||
act = "copying"; |
||||
if (argc == 2) |
||||
rc = mdb_env_copyfd2(env, MDB_STDOUT, cpflags); |
||||
else |
||||
rc = mdb_env_copy2(env, argv[2], cpflags); |
||||
} |
||||
if (rc) |
||||
fprintf(stderr, "%s: %s failed, error %d (%s)\n", |
||||
progname, act, rc, mdb_strerror(rc)); |
||||
mdb_env_close(env); |
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS; |
||||
} |
||||
@ -0,0 +1,75 @@
|
||||
.TH MDB_DUMP 1 "2015/09/30" "LMDB 0.9.17" |
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved. |
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE. |
||||
.SH NAME |
||||
mdb_dump \- LMDB environment export tool |
||||
.SH SYNOPSIS |
||||
.B mdb_dump |
||||
[\c |
||||
.BR \-V ] |
||||
[\c |
||||
.BI \-f \ file\fR] |
||||
[\c |
||||
.BR \-l ] |
||||
[\c |
||||
.BR \-n ] |
||||
[\c |
||||
.BR \-p ] |
||||
[\c |
||||
.BR \-a \ | |
||||
.BI \-s \ subdb\fR] |
||||
.BR \ envpath |
||||
.SH DESCRIPTION |
||||
The |
||||
.B mdb_dump |
||||
utility reads a database and writes its contents to the |
||||
standard output using a portable flat-text format |
||||
understood by the |
||||
.BR mdb_load (1) |
||||
utility. |
||||
.SH OPTIONS |
||||
.TP |
||||
.BR \-V |
||||
Write the library version number to the standard output, and exit. |
||||
.TP |
||||
.BR \-f \ file |
||||
Write to the specified file instead of to the standard output. |
||||
.TP |
||||
.BR \-l |
||||
List the databases stored in the environment. Just the |
||||
names will be listed, no data will be output. |
||||
.TP |
||||
.BR \-n |
||||
Dump an LMDB database which does not use subdirectories. |
||||
.TP |
||||
.BR \-p |
||||
If characters in either the key or data items are printing characters (as |
||||
defined by isprint(3)), output them directly. This option permits users to |
||||
use standard text editors and tools to modify the contents of databases. |
||||
|
||||
Note: different systems may have different notions about what characters |
||||
are considered printing characters, and databases dumped in this manner may |
||||
be less portable to external systems. |
||||
.TP |
||||
.BR \-a |
||||
Dump all of the subdatabases in the environment. |
||||
.TP |
||||
.BR \-s \ subdb |
||||
Dump a specific subdatabase. If no database is specified, only the main database is dumped. |
||||
.SH DIAGNOSTICS |
||||
Exit status is zero if no errors occur. |
||||
Errors result in a non-zero exit status and |
||||
a diagnostic message being written to standard error. |
||||
|
||||
Dumping databases that use user-defined comparison functions will output |
||||
records with the ordering imposed by those comparison functions. If |
||||
.B mdb_load |
||||
is invoked without including the |
||||
.B -a |
||||
option when reloading those records, the new databases will likely be |
||||
damaged beyond repair, permitting neither record storage nor retrieval. |
||||
|
||||
.SH "SEE ALSO" |
||||
.BR mdb_load (1) |
||||
.SH AUTHOR |
||||
Howard Chu of Symas Corporation <http://www.symas.com> |
||||
@ -0,0 +1,319 @@
|
||||
/* mdb_dump.c - memory-mapped database dump tool */ |
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
#include <stdio.h> |
||||
#include <errno.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <ctype.h> |
||||
#include <unistd.h> |
||||
#include <signal.h> |
||||
#include "lmdb.h" |
||||
|
||||
#ifdef _WIN32 |
||||
#define Z "I" |
||||
#else |
||||
#define Z "z" |
||||
#endif |
||||
|
||||
#define PRINT 1 |
||||
static int mode; |
||||
|
||||
typedef struct flagbit { |
||||
int bit; |
||||
char *name; |
||||
} flagbit; |
||||
|
||||
flagbit dbflags[] = { |
||||
{ MDB_REVERSEKEY, "reversekey" }, |
||||
{ MDB_DUPSORT, "dupsort" }, |
||||
{ MDB_INTEGERKEY, "integerkey" }, |
||||
{ MDB_DUPFIXED, "dupfixed" }, |
||||
{ MDB_INTEGERDUP, "integerdup" }, |
||||
{ MDB_REVERSEDUP, "reversedup" }, |
||||
{ 0, NULL } |
||||
}; |
||||
|
||||
static volatile sig_atomic_t gotsig; |
||||
|
||||
static void dumpsig( int sig ) |
||||
{ |
||||
gotsig=1; |
||||
} |
||||
|
||||
static const char hexc[] = "0123456789abcdef"; |
||||
|
||||
static void hex(unsigned char c) |
||||
{ |
||||
putchar(hexc[c >> 4]); |
||||
putchar(hexc[c & 0xf]); |
||||
} |
||||
|
||||
static void text(MDB_val *v) |
||||
{ |
||||
unsigned char *c, *end; |
||||
|
||||
putchar(' '); |
||||
c = v->mv_data; |
||||
end = c + v->mv_size; |
||||
while (c < end) { |
||||
if (isprint(*c)) { |
||||
if (*c == '\\') |
||||
putchar('\\'); |
||||
putchar(*c); |
||||
} else { |
||||
putchar('\\'); |
||||
hex(*c); |
||||
} |
||||
c++; |
||||
} |
||||
putchar('\n'); |
||||
} |
||||
|
||||
static void byte(MDB_val *v) |
||||
{ |
||||
unsigned char *c, *end; |
||||
|
||||
putchar(' '); |
||||
c = v->mv_data; |
||||
end = c + v->mv_size; |
||||
while (c < end) { |
||||
hex(*c++); |
||||
} |
||||
putchar('\n'); |
||||
} |
||||
|
||||
/* Dump in BDB-compatible format */ |
||||
static int dumpit(MDB_txn *txn, MDB_dbi dbi, char *name) |
||||
{ |
||||
MDB_cursor *mc; |
||||
MDB_stat ms; |
||||
MDB_val key, data; |
||||
MDB_envinfo info; |
||||
unsigned int flags; |
||||
int rc, i; |
||||
|
||||
rc = mdb_dbi_flags(txn, dbi, &flags); |
||||
if (rc) return rc; |
||||
|
||||
rc = mdb_stat(txn, dbi, &ms); |
||||
if (rc) return rc; |
||||
|
||||
rc = mdb_env_info(mdb_txn_env(txn), &info); |
||||
if (rc) return rc; |
||||
|
||||
printf("VERSION=3\n"); |
||||
printf("format=%s\n", mode & PRINT ? "print" : "bytevalue"); |
||||
if (name) |
||||
printf("database=%s\n", name); |
||||
printf("type=btree\n"); |
||||
printf("mapsize=%" Z "u\n", info.me_mapsize); |
||||
if (info.me_mapaddr) |
||||
printf("mapaddr=%p\n", info.me_mapaddr); |
||||
printf("maxreaders=%u\n", info.me_maxreaders); |
||||
|
||||
if (flags & MDB_DUPSORT) |
||||
printf("duplicates=1\n"); |
||||
|
||||
for (i=0; dbflags[i].bit; i++) |
||||
if (flags & dbflags[i].bit) |
||||
printf("%s=1\n", dbflags[i].name); |
||||
|
||||
printf("db_pagesize=%d\n", ms.ms_psize); |
||||
printf("HEADER=END\n"); |
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &mc); |
||||
if (rc) return rc; |
||||
|
||||
while ((rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT)) == MDB_SUCCESS) { |
||||
if (gotsig) { |
||||
rc = EINTR; |
||||
break; |
||||
} |
||||
if (mode & PRINT) { |
||||
text(&key); |
||||
text(&data); |
||||
} else { |
||||
byte(&key); |
||||
byte(&data); |
||||
} |
||||
} |
||||
printf("DATA=END\n"); |
||||
if (rc == MDB_NOTFOUND) |
||||
rc = MDB_SUCCESS; |
||||
|
||||
return rc; |
||||
} |
||||
|
||||
static void usage(char *prog) |
||||
{ |
||||
fprintf(stderr, "usage: %s [-V] [-f output] [-l] [-n] [-p] [-a|-s subdb] dbpath\n", prog); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) |
||||
{ |
||||
int i, rc; |
||||
MDB_env *env; |
||||
MDB_txn *txn; |
||||
MDB_dbi dbi; |
||||
char *prog = argv[0]; |
||||
char *envname; |
||||
char *subname = NULL; |
||||
int alldbs = 0, envflags = 0, list = 0; |
||||
|
||||
if (argc < 2) { |
||||
usage(prog); |
||||
} |
||||
|
||||
/* -a: dump all subDBs
|
||||
* -s: dump only the named subDB |
||||
* -n: use NOSUBDIR flag on env_open |
||||
* -p: use printable characters |
||||
* -f: write to file instead of stdout |
||||
* -V: print version and exit |
||||
* (default) dump only the main DB |
||||
*/ |
||||
while ((i = getopt(argc, argv, "af:lnps:V")) != EOF) { |
||||
switch(i) { |
||||
case 'V': |
||||
printf("%s\n", MDB_VERSION_STRING); |
||||
exit(0); |
||||
break; |
||||
case 'l': |
||||
list = 1; |
||||
/*FALLTHROUGH*/ |
||||
case 'a': |
||||
if (subname) |
||||
usage(prog); |
||||
alldbs++; |
||||
break; |
||||
case 'f': |
||||
if (freopen(optarg, "w", stdout) == NULL) { |
||||
fprintf(stderr, "%s: %s: reopen: %s\n", |
||||
prog, optarg, strerror(errno)); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
break; |
||||
case 'n': |
||||
envflags |= MDB_NOSUBDIR; |
||||
break; |
||||
case 'p': |
||||
mode |= PRINT; |
||||
break; |
||||
case 's': |
||||
if (alldbs) |
||||
usage(prog); |
||||
subname = optarg; |
||||
break; |
||||
default: |
||||
usage(prog); |
||||
} |
||||
} |
||||
|
||||
if (optind != argc - 1) |
||||
usage(prog); |
||||
|
||||
#ifdef SIGPIPE |
||||
signal(SIGPIPE, dumpsig); |
||||
#endif |
||||
#ifdef SIGHUP |
||||
signal(SIGHUP, dumpsig); |
||||
#endif |
||||
signal(SIGINT, dumpsig); |
||||
signal(SIGTERM, dumpsig); |
||||
|
||||
envname = argv[optind]; |
||||
rc = mdb_env_create(&env); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
return EXIT_FAILURE; |
||||
} |
||||
|
||||
if (alldbs || subname) { |
||||
mdb_env_set_maxdbs(env, 2); |
||||
} |
||||
|
||||
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto env_close; |
||||
} |
||||
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto env_close; |
||||
} |
||||
|
||||
rc = mdb_open(txn, subname, 0, &dbi); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto txn_abort; |
||||
} |
||||
|
||||
if (alldbs) { |
||||
MDB_cursor *cursor; |
||||
MDB_val key; |
||||
int count = 0; |
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto txn_abort; |
||||
} |
||||
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) { |
||||
char *str; |
||||
MDB_dbi db2; |
||||
if (memchr(key.mv_data, '\0', key.mv_size)) |
||||
continue; |
||||
count++; |
||||
str = malloc(key.mv_size+1); |
||||
memcpy(str, key.mv_data, key.mv_size); |
||||
str[key.mv_size] = '\0'; |
||||
rc = mdb_open(txn, str, 0, &db2); |
||||
if (rc == MDB_SUCCESS) { |
||||
if (list) { |
||||
printf("%s\n", str); |
||||
list++; |
||||
} else { |
||||
rc = dumpit(txn, db2, str); |
||||
if (rc) |
||||
break; |
||||
} |
||||
mdb_close(env, db2); |
||||
} |
||||
free(str); |
||||
if (rc) continue; |
||||
} |
||||
mdb_cursor_close(cursor); |
||||
if (!count) { |
||||
fprintf(stderr, "%s: %s does not contain multiple databases\n", prog, envname); |
||||
rc = MDB_NOTFOUND; |
||||
} else if (rc == MDB_NOTFOUND) { |
||||
rc = MDB_SUCCESS; |
||||
} |
||||
} else { |
||||
rc = dumpit(txn, dbi, subname); |
||||
} |
||||
if (rc && rc != MDB_NOTFOUND) |
||||
fprintf(stderr, "%s: %s: %s\n", prog, envname, mdb_strerror(rc)); |
||||
|
||||
mdb_close(env, dbi); |
||||
txn_abort: |
||||
mdb_txn_abort(txn); |
||||
env_close: |
||||
mdb_env_close(env); |
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS; |
||||
} |
||||
@ -0,0 +1,91 @@
|
||||
.TH MDB_LOAD 1 "2015/09/30" "LMDB 0.9.17" |
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved. |
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE. |
||||
.SH NAME |
||||
mdb_load \- LMDB environment import tool |
||||
.SH SYNOPSIS |
||||
.B mdb_load |
||||
[\c |
||||
.BR \-V ] |
||||
[\c |
||||
.BR \-a ] |
||||
[\c |
||||
.BI \-f \ file\fR] |
||||
[\c |
||||
.BR \-n ] |
||||
[\c |
||||
.BI \-s \ subdb\fR] |
||||
[\c |
||||
.BR \-N ] |
||||
[\c |
||||
.BR \-Q ] |
||||
[\c |
||||
.BR \-T ] |
||||
.BR \ envpath |
||||
.SH DESCRIPTION |
||||
The |
||||
.B mdb_load |
||||
utility reads from the standard input and loads it into the |
||||
LMDB environment |
||||
.BR envpath . |
||||
|
||||
The input to |
||||
.B mdb_load |
||||
must be in the output format specified by the |
||||
.BR mdb_dump (1) |
||||
utility or as specified by the |
||||
.B -T |
||||
option below. |
||||
.SH OPTIONS |
||||
.TP |
||||
.BR \-V |
||||
Write the library version number to the standard output, and exit. |
||||
.TP |
||||
.BR \-a |
||||
Append all records in the order they appear in the input. The input is assumed to already be |
||||
in correctly sorted order and no sorting or checking for redundant values will be performed. |
||||
This option must be used to reload data that was produced by running |
||||
.B mdb_dump |
||||
on a database that uses custom compare functions. |
||||
.TP |
||||
.BR \-f \ file |
||||
Read from the specified file instead of from the standard input. |
||||
.TP |
||||
.BR \-n |
||||
Load an LMDB database which does not use subdirectories. |
||||
.TP |
||||
.BR \-s \ subdb |
||||
Load a specific subdatabase. If no database is specified, data is loaded into the main database. |
||||
.TP |
||||
.BR \-N |
||||
Don't overwrite existing records when loading into an already existing database; just skip them. |
||||
.TP |
||||
.BR \-Q |
||||
Quick mode, uses MDB_NOSYNC for faster loading. Forces sync with mdb_env_sync() before exiting. |
||||
.TP |
||||
.BR \-T |
||||
Load data from simple text files. The input must be paired lines of text, where the first |
||||
line of the pair is the key item, and the second line of the pair is its corresponding |
||||
data item. |
||||
|
||||
A simple escape mechanism, where newline and backslash (\\) characters are special, is |
||||
applied to the text input. Newline characters are interpreted as record separators. |
||||
Backslash characters in the text will be interpreted in one of two ways: If the backslash |
||||
character precedes another backslash character, the pair will be interpreted as a literal |
||||
backslash. If the backslash character precedes any other character, the two characters |
||||
following the backslash will be interpreted as a hexadecimal specification of a single |
||||
character; for example, \\0a is a newline character in the ASCII character set. |
||||
|
||||
For this reason, any backslash or newline characters that naturally occur in the text |
||||
input must be escaped to avoid misinterpretation by |
||||
.BR mdb_load . |
||||
|
||||
.SH DIAGNOSTICS |
||||
Exit status is zero if no errors occur. |
||||
Errors result in a non-zero exit status and |
||||
a diagnostic message being written to standard error. |
||||
|
||||
.SH "SEE ALSO" |
||||
.BR mdb_dump (1) |
||||
.SH AUTHOR |
||||
Howard Chu of Symas Corporation <http://www.symas.com> |
||||
@ -0,0 +1,515 @@
|
||||
/* mdb_load.c - memory-mapped database load tool */ |
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <errno.h> |
||||
#include <string.h> |
||||
#include <ctype.h> |
||||
#include <unistd.h> |
||||
#include "lmdb.h" |
||||
|
||||
#define PRINT 1 |
||||
#define NOHDR 2 |
||||
static int mode; |
||||
|
||||
static char *subname = NULL; |
||||
|
||||
static size_t lineno; |
||||
static int version; |
||||
|
||||
static int flags; |
||||
|
||||
static char *prog; |
||||
|
||||
static int Eof; |
||||
|
||||
static MDB_envinfo info; |
||||
|
||||
static MDB_val kbuf, dbuf; |
||||
static MDB_val k0buf; |
||||
|
||||
#ifdef _WIN32 |
||||
#define Z "I" |
||||
#else |
||||
#define Z "z" |
||||
#endif |
||||
|
||||
#define STRLENOF(s) (sizeof(s)-1) |
||||
|
||||
typedef struct flagbit { |
||||
int bit; |
||||
char *name; |
||||
int len; |
||||
} flagbit; |
||||
|
||||
#define S(s) s, STRLENOF(s) |
||||
|
||||
flagbit dbflags[] = { |
||||
{ MDB_REVERSEKEY, S("reversekey") }, |
||||
{ MDB_DUPSORT, S("dupsort") }, |
||||
{ MDB_INTEGERKEY, S("integerkey") }, |
||||
{ MDB_DUPFIXED, S("dupfixed") }, |
||||
{ MDB_INTEGERDUP, S("integerdup") }, |
||||
{ MDB_REVERSEDUP, S("reversedup") }, |
||||
{ 0, NULL, 0 } |
||||
}; |
||||
|
||||
static void readhdr(void) |
||||
{ |
||||
char *ptr; |
||||
|
||||
flags = 0; |
||||
while (fgets(dbuf.mv_data, dbuf.mv_size, stdin) != NULL) { |
||||
lineno++; |
||||
if (!strncmp(dbuf.mv_data, "VERSION=", STRLENOF("VERSION="))) { |
||||
version=atoi((char *)dbuf.mv_data+STRLENOF("VERSION=")); |
||||
if (version > 3) { |
||||
fprintf(stderr, "%s: line %" Z "d: unsupported VERSION %d\n", |
||||
prog, lineno, version); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
} else if (!strncmp(dbuf.mv_data, "HEADER=END", STRLENOF("HEADER=END"))) { |
||||
break; |
||||
} else if (!strncmp(dbuf.mv_data, "format=", STRLENOF("format="))) { |
||||
if (!strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "print", STRLENOF("print"))) |
||||
mode |= PRINT; |
||||
else if (strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "bytevalue", STRLENOF("bytevalue"))) { |
||||
fprintf(stderr, "%s: line %" Z "d: unsupported FORMAT %s\n", |
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("FORMAT=")); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
} else if (!strncmp(dbuf.mv_data, "database=", STRLENOF("database="))) { |
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size); |
||||
if (ptr) *ptr = '\0'; |
||||
if (subname) free(subname); |
||||
subname = strdup((char *)dbuf.mv_data+STRLENOF("database=")); |
||||
} else if (!strncmp(dbuf.mv_data, "type=", STRLENOF("type="))) { |
||||
if (strncmp((char *)dbuf.mv_data+STRLENOF("type="), "btree", STRLENOF("btree"))) { |
||||
fprintf(stderr, "%s: line %" Z "d: unsupported type %s\n", |
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("type=")); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
} else if (!strncmp(dbuf.mv_data, "mapaddr=", STRLENOF("mapaddr="))) { |
||||
int i; |
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size); |
||||
if (ptr) *ptr = '\0'; |
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapaddr="), "%p", &info.me_mapaddr); |
||||
if (i != 1) { |
||||
fprintf(stderr, "%s: line %" Z "d: invalid mapaddr %s\n", |
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapaddr=")); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
} else if (!strncmp(dbuf.mv_data, "mapsize=", STRLENOF("mapsize="))) { |
||||
int i; |
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size); |
||||
if (ptr) *ptr = '\0'; |
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapsize="), "%" Z "u", &info.me_mapsize); |
||||
if (i != 1) { |
||||
fprintf(stderr, "%s: line %" Z "d: invalid mapsize %s\n", |
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapsize=")); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
} else if (!strncmp(dbuf.mv_data, "maxreaders=", STRLENOF("maxreaders="))) { |
||||
int i; |
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size); |
||||
if (ptr) *ptr = '\0'; |
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("maxreaders="), "%u", &info.me_maxreaders); |
||||
if (i != 1) { |
||||
fprintf(stderr, "%s: line %" Z "d: invalid maxreaders %s\n", |
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("maxreaders=")); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
} else { |
||||
int i; |
||||
for (i=0; dbflags[i].bit; i++) { |
||||
if (!strncmp(dbuf.mv_data, dbflags[i].name, dbflags[i].len) && |
||||
((char *)dbuf.mv_data)[dbflags[i].len] == '=') { |
||||
flags |= dbflags[i].bit; |
||||
break; |
||||
} |
||||
} |
||||
if (!dbflags[i].bit) { |
||||
ptr = memchr(dbuf.mv_data, '=', dbuf.mv_size); |
||||
if (!ptr) { |
||||
fprintf(stderr, "%s: line %" Z "d: unexpected format\n", |
||||
prog, lineno); |
||||
exit(EXIT_FAILURE); |
||||
} else { |
||||
*ptr = '\0'; |
||||
fprintf(stderr, "%s: line %" Z "d: unrecognized keyword ignored: %s\n", |
||||
prog, lineno, (char *)dbuf.mv_data); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
static void badend(void) |
||||
{ |
||||
fprintf(stderr, "%s: line %" Z "d: unexpected end of input\n", |
||||
prog, lineno); |
||||
} |
||||
|
||||
static int unhex(unsigned char *c2) |
||||
{ |
||||
int x, c; |
||||
x = *c2++ & 0x4f; |
||||
if (x & 0x40) |
||||
x -= 55; |
||||
c = x << 4; |
||||
x = *c2 & 0x4f; |
||||
if (x & 0x40) |
||||
x -= 55; |
||||
c |= x; |
||||
return c; |
||||
} |
||||
|
||||
static int readline(MDB_val *out, MDB_val *buf) |
||||
{ |
||||
unsigned char *c1, *c2, *end; |
||||
size_t len, l2; |
||||
int c; |
||||
|
||||
if (!(mode & NOHDR)) { |
||||
c = fgetc(stdin); |
||||
if (c == EOF) { |
||||
Eof = 1; |
||||
return EOF; |
||||
} |
||||
if (c != ' ') { |
||||
lineno++; |
||||
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) { |
||||
badend: |
||||
Eof = 1; |
||||
badend(); |
||||
return EOF; |
||||
} |
||||
if (c == 'D' && !strncmp(buf->mv_data, "ATA=END", STRLENOF("ATA=END"))) |
||||
return EOF; |
||||
goto badend; |
||||
} |
||||
} |
||||
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) { |
||||
Eof = 1; |
||||
return EOF; |
||||
} |
||||
lineno++; |
||||
|
||||
c1 = buf->mv_data; |
||||
len = strlen((char *)c1); |
||||
if (!len) { |
||||
/* This can only happen with an intentionally invalid input
|
||||
* with a NUL byte after the leading SPACE |
||||
*/ |
||||
goto badend; |
||||
} |
||||
l2 = len; |
||||
|
||||
/* Is buffer too short? */ |
||||
while (c1[len-1] != '\n') { |
||||
buf->mv_data = realloc(buf->mv_data, buf->mv_size*2); |
||||
if (!buf->mv_data) { |
||||
Eof = 1; |
||||
fprintf(stderr, "%s: line %" Z "d: out of memory, line too long\n", |
||||
prog, lineno); |
||||
return EOF; |
||||
} |
||||
c1 = buf->mv_data; |
||||
c1 += l2; |
||||
if (fgets((char *)c1, buf->mv_size+1, stdin) == NULL) { |
||||
Eof = 1; |
||||
badend(); |
||||
return EOF; |
||||
} |
||||
buf->mv_size *= 2; |
||||
len = strlen((char *)c1); |
||||
l2 += len; |
||||
} |
||||
c1 = c2 = buf->mv_data; |
||||
len = l2; |
||||
c1[--len] = '\0'; |
||||
end = c1 + len; |
||||
|
||||
if (mode & PRINT) { |
||||
while (c2 < end) { |
||||
if (*c2 == '\\') { |
||||
if (c2[1] == '\\') { |
||||
*c1++ = *c2; |
||||
} else { |
||||
if (c2+3 > end || !isxdigit(c2[1]) || !isxdigit(c2[2])) { |
||||
Eof = 1; |
||||
badend(); |
||||
return EOF; |
||||
} |
||||
*c1++ = unhex(++c2); |
||||
} |
||||
c2 += 2; |
||||
} else { |
||||
/* copies are redundant when no escapes were used */ |
||||
*c1++ = *c2++; |
||||
} |
||||
} |
||||
} else { |
||||
/* odd length not allowed */ |
||||
if (len & 1) { |
||||
Eof = 1; |
||||
badend(); |
||||
return EOF; |
||||
} |
||||
while (c2 < end) { |
||||
if (!isxdigit(*c2) || !isxdigit(c2[1])) { |
||||
Eof = 1; |
||||
badend(); |
||||
return EOF; |
||||
} |
||||
*c1++ = unhex(c2); |
||||
c2 += 2; |
||||
} |
||||
} |
||||
c2 = out->mv_data = buf->mv_data; |
||||
out->mv_size = c1 - c2; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void usage(void) |
||||
{ |
||||
fprintf(stderr, "usage: %s [-V] [-a] [-f input] [-n] [-s name] [-N] [-T] dbpath\n", prog); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
|
||||
static int greater(const MDB_val *a, const MDB_val *b) |
||||
{ |
||||
return 1; |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) |
||||
{ |
||||
int i, rc; |
||||
MDB_env *env; |
||||
MDB_txn *txn; |
||||
MDB_cursor *mc; |
||||
MDB_dbi dbi; |
||||
char *envname; |
||||
int envflags = MDB_NOSYNC, putflags = 0; |
||||
int dohdr = 0, append = 0; |
||||
MDB_val prevk; |
||||
|
||||
prog = argv[0]; |
||||
|
||||
if (argc < 2) { |
||||
usage(); |
||||
} |
||||
|
||||
/* -a: append records in input order
|
||||
* -f: load file instead of stdin |
||||
* -n: use NOSUBDIR flag on env_open |
||||
* -s: load into named subDB |
||||
* -N: use NOOVERWRITE on puts |
||||
* -Q: quick mode using NOSYNC |
||||
* -T: read plaintext |
||||
* -V: print version and exit |
||||
*/ |
||||
while ((i = getopt(argc, argv, "af:ns:NQTV")) != EOF) { |
||||
switch(i) { |
||||
case 'V': |
||||
printf("%s\n", MDB_VERSION_STRING); |
||||
exit(0); |
||||
break; |
||||
case 'a': |
||||
append = 1; |
||||
break; |
||||
case 'f': |
||||
if (freopen(optarg, "r", stdin) == NULL) { |
||||
fprintf(stderr, "%s: %s: reopen: %s\n", |
||||
prog, optarg, strerror(errno)); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
break; |
||||
case 'n': |
||||
envflags |= MDB_NOSUBDIR; |
||||
break; |
||||
case 's': |
||||
subname = strdup(optarg); |
||||
break; |
||||
case 'N': |
||||
putflags = MDB_NOOVERWRITE|MDB_NODUPDATA; |
||||
break; |
||||
case 'Q': |
||||
envflags |= MDB_NOSYNC; |
||||
break; |
||||
case 'T': |
||||
mode |= NOHDR | PRINT; |
||||
break; |
||||
default: |
||||
usage(); |
||||
} |
||||
} |
||||
|
||||
if (optind != argc - 1) |
||||
usage(); |
||||
|
||||
dbuf.mv_size = 4096; |
||||
dbuf.mv_data = malloc(dbuf.mv_size); |
||||
|
||||
if (!(mode & NOHDR)) |
||||
readhdr(); |
||||
|
||||
envname = argv[optind]; |
||||
rc = mdb_env_create(&env); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
return EXIT_FAILURE; |
||||
} |
||||
|
||||
mdb_env_set_maxdbs(env, 2); |
||||
|
||||
if (info.me_maxreaders) |
||||
mdb_env_set_maxreaders(env, info.me_maxreaders); |
||||
|
||||
if (info.me_mapsize) |
||||
mdb_env_set_mapsize(env, info.me_mapsize); |
||||
|
||||
if (info.me_mapaddr) |
||||
envflags |= MDB_FIXEDMAP; |
||||
|
||||
rc = mdb_env_open(env, envname, envflags, 0664); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto env_close; |
||||
} |
||||
|
||||
kbuf.mv_size = mdb_env_get_maxkeysize(env) * 2 + 2; |
||||
kbuf.mv_data = malloc(kbuf.mv_size * 2); |
||||
k0buf.mv_size = kbuf.mv_size; |
||||
k0buf.mv_data = (char *)kbuf.mv_data + kbuf.mv_size; |
||||
prevk.mv_data = k0buf.mv_data; |
||||
|
||||
while(!Eof) { |
||||
MDB_val key, data; |
||||
int batch = 0; |
||||
flags = 0; |
||||
int appflag; |
||||
|
||||
if (!dohdr) { |
||||
dohdr = 1; |
||||
} else if (!(mode & NOHDR)) |
||||
readhdr(); |
||||
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto env_close; |
||||
} |
||||
|
||||
rc = mdb_dbi_open(txn, subname, flags|MDB_CREATE, &dbi); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_dbi_open failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto txn_abort; |
||||
} |
||||
prevk.mv_size = 0; |
||||
if (append) { |
||||
mdb_set_compare(txn, dbi, greater); |
||||
if (flags & MDB_DUPSORT) |
||||
mdb_set_dupsort(txn, dbi, greater); |
||||
} |
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &mc); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto txn_abort; |
||||
} |
||||
|
||||
while(1) { |
||||
rc = readline(&key, &kbuf); |
||||
if (rc) /* rc == EOF */ |
||||
break; |
||||
|
||||
rc = readline(&data, &dbuf); |
||||
if (rc) { |
||||
fprintf(stderr, "%s: line %" Z "d: failed to read key value\n", prog, lineno); |
||||
goto txn_abort; |
||||
} |
||||
|
||||
if (append) { |
||||
appflag = MDB_APPEND; |
||||
if (flags & MDB_DUPSORT) { |
||||
if (prevk.mv_size == key.mv_size && !memcmp(prevk.mv_data, key.mv_data, key.mv_size)) |
||||
appflag = MDB_CURRENT|MDB_APPENDDUP; |
||||
else { |
||||
memcpy(prevk.mv_data, key.mv_data, key.mv_size); |
||||
prevk.mv_size = key.mv_size; |
||||
} |
||||
} |
||||
} else { |
||||
appflag = 0; |
||||
} |
||||
rc = mdb_cursor_put(mc, &key, &data, putflags|appflag); |
||||
if (rc == MDB_KEYEXIST && putflags) |
||||
continue; |
||||
if (rc) { |
||||
fprintf(stderr, "%s: line %" Z "d: mdb_cursor_put failed, error %d %s\n", prog, lineno, rc, mdb_strerror(rc)); |
||||
goto txn_abort; |
||||
} |
||||
batch++; |
||||
if (batch == 100) { |
||||
rc = mdb_txn_commit(txn); |
||||
if (rc) { |
||||
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n", |
||||
prog, lineno, mdb_strerror(rc)); |
||||
goto env_close; |
||||
} |
||||
rc = mdb_txn_begin(env, NULL, 0, &txn); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto env_close; |
||||
} |
||||
rc = mdb_cursor_open(txn, dbi, &mc); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto txn_abort; |
||||
} |
||||
if (append) { |
||||
MDB_val k, d; |
||||
mdb_cursor_get(mc, &k, &d, MDB_LAST); |
||||
memcpy(prevk.mv_data, k.mv_data, k.mv_size); |
||||
prevk.mv_size = k.mv_size; |
||||
} |
||||
batch = 0; |
||||
} |
||||
} |
||||
rc = mdb_txn_commit(txn); |
||||
txn = NULL; |
||||
if (rc) { |
||||
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n", |
||||
prog, lineno, mdb_strerror(rc)); |
||||
goto env_close; |
||||
} |
||||
if (envflags & MDB_NOSYNC) { |
||||
rc = mdb_env_sync(env, 1); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_env_sync failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto env_close; |
||||
} |
||||
} |
||||
mdb_dbi_close(env, dbi); |
||||
} |
||||
|
||||
txn_abort: |
||||
mdb_txn_abort(txn); |
||||
env_close: |
||||
mdb_env_close(env); |
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS; |
||||
} |
||||
@ -0,0 +1,64 @@
|
||||
.TH MDB_STAT 1 "2015/09/30" "LMDB 0.9.17" |
||||
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved. |
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE. |
||||
.SH NAME |
||||
mdb_stat \- LMDB environment status tool |
||||
.SH SYNOPSIS |
||||
.B mdb_stat |
||||
[\c |
||||
.BR \-V ] |
||||
[\c |
||||
.BR \-e ] |
||||
[\c |
||||
.BR \-f [ f [ f ]]] |
||||
[\c |
||||
.BR \-n ] |
||||
[\c |
||||
.BR \-r [ r ]] |
||||
[\c |
||||
.BR \-a \ | |
||||
.BI \-s \ subdb\fR] |
||||
.BR \ envpath |
||||
.SH DESCRIPTION |
||||
The |
||||
.B mdb_stat |
||||
utility displays the status of an LMDB environment. |
||||
.SH OPTIONS |
||||
.TP |
||||
.BR \-V |
||||
Write the library version number to the standard output, and exit. |
||||
.TP |
||||
.BR \-e |
||||
Display information about the database environment. |
||||
.TP |
||||
.BR \-f |
||||
Display information about the environment freelist. |
||||
If \fB\-ff\fP is given, summarize each freelist entry. |
||||
If \fB\-fff\fP is given, display the full list of page IDs in the freelist. |
||||
.TP |
||||
.BR \-n |
||||
Display the status of an LMDB database which does not use subdirectories. |
||||
.TP |
||||
.BR \-r |
||||
Display information about the environment reader table. |
||||
Shows the process ID, thread ID, and transaction ID for each active |
||||
reader slot. The process ID and transaction ID are in decimal, the |
||||
thread ID is in hexadecimal. The transaction ID is displayed as "-" |
||||
if the reader does not currently have a read transaction open. |
||||
If \fB\-rr\fP is given, check for stale entries in the reader |
||||
table and clear them. The reader table will be printed again |
||||
after the check is performed. |
||||
.TP |
||||
.BR \-a |
||||
Display the status of all of the subdatabases in the environment. |
||||
.TP |
||||
.BR \-s \ subdb |
||||
Display the status of a specific subdatabase. |
||||
.SH DIAGNOSTICS |
||||
Exit status is zero if no errors occur. |
||||
Errors result in a non-zero exit status and |
||||
a diagnostic message being written to standard error. |
||||
.SH "SEE ALSO" |
||||
.BR mdb_copy (1) |
||||
.SH AUTHOR |
||||
Howard Chu of Symas Corporation <http://www.symas.com> |
||||
@ -0,0 +1,263 @@
|
||||
/* mdb_stat.c - memory-mapped database status tool */ |
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include "lmdb.h" |
||||
|
||||
#ifdef _WIN32 |
||||
#define Z "I" |
||||
#else |
||||
#define Z "z" |
||||
#endif |
||||
|
||||
static void prstat(MDB_stat *ms) |
||||
{ |
||||
#if 0 |
||||
printf(" Page size: %u\n", ms->ms_psize); |
||||
#endif |
||||
printf(" Tree depth: %u\n", ms->ms_depth); |
||||
printf(" Branch pages: %"Z"u\n", ms->ms_branch_pages); |
||||
printf(" Leaf pages: %"Z"u\n", ms->ms_leaf_pages); |
||||
printf(" Overflow pages: %"Z"u\n", ms->ms_overflow_pages); |
||||
printf(" Entries: %"Z"u\n", ms->ms_entries); |
||||
} |
||||
|
||||
static void usage(char *prog) |
||||
{ |
||||
fprintf(stderr, "usage: %s [-V] [-n] [-e] [-r[r]] [-f[f[f]]] [-a|-s subdb] dbpath\n", prog); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) |
||||
{ |
||||
int i, rc; |
||||
MDB_env *env; |
||||
MDB_txn *txn; |
||||
MDB_dbi dbi; |
||||
MDB_stat mst; |
||||
MDB_envinfo mei; |
||||
char *prog = argv[0]; |
||||
char *envname; |
||||
char *subname = NULL; |
||||
int alldbs = 0, envinfo = 0, envflags = 0, freinfo = 0, rdrinfo = 0; |
||||
|
||||
if (argc < 2) { |
||||
usage(prog); |
||||
} |
||||
|
||||
/* -a: print stat of main DB and all subDBs
|
||||
* -s: print stat of only the named subDB |
||||
* -e: print env info |
||||
* -f: print freelist info |
||||
* -r: print reader info |
||||
* -n: use NOSUBDIR flag on env_open |
||||
* -V: print version and exit |
||||
* (default) print stat of only the main DB |
||||
*/ |
||||
while ((i = getopt(argc, argv, "Vaefnrs:")) != EOF) { |
||||
switch(i) { |
||||
case 'V': |
||||
printf("%s\n", MDB_VERSION_STRING); |
||||
exit(0); |
||||
break; |
||||
case 'a': |
||||
if (subname) |
||||
usage(prog); |
||||
alldbs++; |
||||
break; |
||||
case 'e': |
||||
envinfo++; |
||||
break; |
||||
case 'f': |
||||
freinfo++; |
||||
break; |
||||
case 'n': |
||||
envflags |= MDB_NOSUBDIR; |
||||
break; |
||||
case 'r': |
||||
rdrinfo++; |
||||
break; |
||||
case 's': |
||||
if (alldbs) |
||||
usage(prog); |
||||
subname = optarg; |
||||
break; |
||||
default: |
||||
usage(prog); |
||||
} |
||||
} |
||||
|
||||
if (optind != argc - 1) |
||||
usage(prog); |
||||
|
||||
envname = argv[optind]; |
||||
rc = mdb_env_create(&env); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
return EXIT_FAILURE; |
||||
} |
||||
|
||||
if (alldbs || subname) { |
||||
mdb_env_set_maxdbs(env, 4); |
||||
} |
||||
|
||||
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto env_close; |
||||
} |
||||
|
||||
if (envinfo) { |
||||
(void)mdb_env_stat(env, &mst); |
||||
(void)mdb_env_info(env, &mei); |
||||
printf("Environment Info\n"); |
||||
printf(" Map address: %p\n", mei.me_mapaddr); |
||||
printf(" Map size: %"Z"u\n", mei.me_mapsize); |
||||
printf(" Page size: %u\n", mst.ms_psize); |
||||
printf(" Max pages: %"Z"u\n", mei.me_mapsize / mst.ms_psize); |
||||
printf(" Number of pages used: %"Z"u\n", mei.me_last_pgno+1); |
||||
printf(" Last transaction ID: %"Z"u\n", mei.me_last_txnid); |
||||
printf(" Max readers: %u\n", mei.me_maxreaders); |
||||
printf(" Number of readers used: %u\n", mei.me_numreaders); |
||||
} |
||||
|
||||
if (rdrinfo) { |
||||
printf("Reader Table Status\n"); |
||||
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout); |
||||
if (rdrinfo > 1) { |
||||
int dead; |
||||
mdb_reader_check(env, &dead); |
||||
printf(" %d stale readers cleared.\n", dead); |
||||
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout); |
||||
} |
||||
if (!(subname || alldbs || freinfo)) |
||||
goto env_close; |
||||
} |
||||
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto env_close; |
||||
} |
||||
|
||||
if (freinfo) { |
||||
MDB_cursor *cursor; |
||||
MDB_val key, data; |
||||
size_t pages = 0, *iptr; |
||||
|
||||
printf("Freelist Status\n"); |
||||
dbi = 0; |
||||
rc = mdb_cursor_open(txn, dbi, &cursor); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto txn_abort; |
||||
} |
||||
rc = mdb_stat(txn, dbi, &mst); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto txn_abort; |
||||
} |
||||
prstat(&mst); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
iptr = data.mv_data; |
||||
pages += *iptr; |
||||
if (freinfo > 1) { |
||||
char *bad = ""; |
||||
size_t pg, prev; |
||||
ssize_t i, j, span = 0; |
||||
j = *iptr++; |
||||
for (i = j, prev = 1; --i >= 0; ) { |
||||
pg = iptr[i]; |
||||
if (pg <= prev) |
||||
bad = " [bad sequence]"; |
||||
prev = pg; |
||||
pg += span; |
||||
for (; i >= span && iptr[i-span] == pg; span++, pg++) ; |
||||
} |
||||
printf(" Transaction %"Z"u, %"Z"d pages, maxspan %"Z"d%s\n", |
||||
*(size_t *)key.mv_data, j, span, bad); |
||||
if (freinfo > 2) { |
||||
for (--j; j >= 0; ) { |
||||
pg = iptr[j]; |
||||
for (span=1; --j >= 0 && iptr[j] == pg+span; span++) ; |
||||
printf(span>1 ? " %9"Z"u[%"Z"d]\n" : " %9"Z"u\n", |
||||
pg, span); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
mdb_cursor_close(cursor); |
||||
printf(" Free pages: %"Z"u\n", pages); |
||||
} |
||||
|
||||
rc = mdb_open(txn, subname, 0, &dbi); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto txn_abort; |
||||
} |
||||
|
||||
rc = mdb_stat(txn, dbi, &mst); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto txn_abort; |
||||
} |
||||
printf("Status of %s\n", subname ? subname : "Main DB"); |
||||
prstat(&mst); |
||||
|
||||
if (alldbs) { |
||||
MDB_cursor *cursor; |
||||
MDB_val key; |
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto txn_abort; |
||||
} |
||||
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) { |
||||
char *str; |
||||
MDB_dbi db2; |
||||
if (memchr(key.mv_data, '\0', key.mv_size)) |
||||
continue; |
||||
str = malloc(key.mv_size+1); |
||||
memcpy(str, key.mv_data, key.mv_size); |
||||
str[key.mv_size] = '\0'; |
||||
rc = mdb_open(txn, str, 0, &db2); |
||||
if (rc == MDB_SUCCESS) |
||||
printf("Status of %s\n", str); |
||||
free(str); |
||||
if (rc) continue; |
||||
rc = mdb_stat(txn, db2, &mst); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc)); |
||||
goto txn_abort; |
||||
} |
||||
prstat(&mst); |
||||
mdb_close(env, db2); |
||||
} |
||||
mdb_cursor_close(cursor); |
||||
} |
||||
|
||||
if (rc == MDB_NOTFOUND) |
||||
rc = MDB_SUCCESS; |
||||
|
||||
mdb_close(env, dbi); |
||||
txn_abort: |
||||
mdb_txn_abort(txn); |
||||
env_close: |
||||
mdb_env_close(env); |
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS; |
||||
} |
||||
@ -0,0 +1,359 @@
|
||||
/** @file midl.c
|
||||
* @brief ldap bdb back-end ID List functions */ |
||||
/* $OpenLDAP$ */ |
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
* |
||||
* Copyright 2000-2021 The OpenLDAP Foundation. |
||||
* Portions Copyright 2001-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
|
||||
#include <limits.h> |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
#include <errno.h> |
||||
#include <sys/types.h> |
||||
#include "midl.h" |
||||
|
||||
/** @defgroup internal LMDB Internals
|
||||
* @{ |
||||
*/ |
||||
/** @defgroup idls ID List Management
|
||||
* @{ |
||||
*/ |
||||
#define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) ) |
||||
|
||||
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id ) |
||||
{ |
||||
/*
|
||||
* binary search of id in ids |
||||
* if found, returns position of id |
||||
* if not found, returns first position greater than id |
||||
*/ |
||||
unsigned base = 0; |
||||
unsigned cursor = 1; |
||||
int val = 0; |
||||
unsigned n = ids[0]; |
||||
|
||||
while( 0 < n ) { |
||||
unsigned pivot = n >> 1; |
||||
cursor = base + pivot + 1; |
||||
val = CMP( ids[cursor], id ); |
||||
|
||||
if( val < 0 ) { |
||||
n = pivot; |
||||
|
||||
} else if ( val > 0 ) { |
||||
base = cursor; |
||||
n -= pivot + 1; |
||||
|
||||
} else { |
||||
return cursor; |
||||
} |
||||
} |
||||
|
||||
if( val > 0 ) { |
||||
++cursor; |
||||
} |
||||
return cursor; |
||||
} |
||||
|
||||
#if 0 /* superseded by append/sort */
|
||||
int mdb_midl_insert( MDB_IDL ids, MDB_ID id ) |
||||
{ |
||||
unsigned x, i; |
||||
|
||||
x = mdb_midl_search( ids, id ); |
||||
assert( x > 0 ); |
||||
|
||||
if( x < 1 ) { |
||||
/* internal error */ |
||||
return -2; |
||||
} |
||||
|
||||
if ( x <= ids[0] && ids[x] == id ) { |
||||
/* duplicate */ |
||||
assert(0); |
||||
return -1; |
||||
} |
||||
|
||||
if ( ++ids[0] >= MDB_IDL_DB_MAX ) { |
||||
/* no room */ |
||||
--ids[0]; |
||||
return -2; |
||||
|
||||
} else { |
||||
/* insert id */ |
||||
for (i=ids[0]; i>x; i--) |
||||
ids[i] = ids[i-1]; |
||||
ids[x] = id; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
#endif |
||||
|
||||
MDB_IDL mdb_midl_alloc(int num) |
||||
{ |
||||
MDB_IDL ids = malloc((num+2) * sizeof(MDB_ID)); |
||||
if (ids) { |
||||
*ids++ = num; |
||||
*ids = 0; |
||||
} |
||||
return ids; |
||||
} |
||||
|
||||
void mdb_midl_free(MDB_IDL ids) |
||||
{ |
||||
if (ids) |
||||
free(ids-1); |
||||
} |
||||
|
||||
void mdb_midl_shrink( MDB_IDL *idp ) |
||||
{ |
||||
MDB_IDL ids = *idp; |
||||
if (*(--ids) > MDB_IDL_UM_MAX && |
||||
(ids = realloc(ids, (MDB_IDL_UM_MAX+2) * sizeof(MDB_ID)))) |
||||
{ |
||||
*ids++ = MDB_IDL_UM_MAX; |
||||
*idp = ids; |
||||
} |
||||
} |
||||
|
||||
static int mdb_midl_grow( MDB_IDL *idp, int num ) |
||||
{ |
||||
MDB_IDL idn = *idp-1; |
||||
/* grow it */ |
||||
idn = realloc(idn, (*idn + num + 2) * sizeof(MDB_ID)); |
||||
if (!idn) |
||||
return ENOMEM; |
||||
*idn++ += num; |
||||
*idp = idn; |
||||
return 0; |
||||
} |
||||
|
||||
int mdb_midl_need( MDB_IDL *idp, unsigned num ) |
||||
{ |
||||
MDB_IDL ids = *idp; |
||||
num += ids[0]; |
||||
if (num > ids[-1]) { |
||||
num = (num + num/4 + (256 + 2)) & -256; |
||||
if (!(ids = realloc(ids-1, num * sizeof(MDB_ID)))) |
||||
return ENOMEM; |
||||
*ids++ = num - 2; |
||||
*idp = ids; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id ) |
||||
{ |
||||
MDB_IDL ids = *idp; |
||||
/* Too big? */ |
||||
if (ids[0] >= ids[-1]) { |
||||
if (mdb_midl_grow(idp, MDB_IDL_UM_MAX)) |
||||
return ENOMEM; |
||||
ids = *idp; |
||||
} |
||||
ids[0]++; |
||||
ids[ids[0]] = id; |
||||
return 0; |
||||
} |
||||
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app ) |
||||
{ |
||||
MDB_IDL ids = *idp; |
||||
/* Too big? */ |
||||
if (ids[0] + app[0] >= ids[-1]) { |
||||
if (mdb_midl_grow(idp, app[0])) |
||||
return ENOMEM; |
||||
ids = *idp; |
||||
} |
||||
memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID)); |
||||
ids[0] += app[0]; |
||||
return 0; |
||||
} |
||||
|
||||
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n ) |
||||
{ |
||||
MDB_ID *ids = *idp, len = ids[0]; |
||||
/* Too big? */ |
||||
if (len + n > ids[-1]) { |
||||
if (mdb_midl_grow(idp, n | MDB_IDL_UM_MAX)) |
||||
return ENOMEM; |
||||
ids = *idp; |
||||
} |
||||
ids[0] = len + n; |
||||
ids += len; |
||||
while (n) |
||||
ids[n--] = id++; |
||||
return 0; |
||||
} |
||||
|
||||
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge ) |
||||
{ |
||||
MDB_ID old_id, merge_id, i = merge[0], j = idl[0], k = i+j, total = k; |
||||
idl[0] = (MDB_ID)-1; /* delimiter for idl scan below */ |
||||
old_id = idl[j]; |
||||
while (i) { |
||||
merge_id = merge[i--]; |
||||
for (; old_id < merge_id; old_id = idl[--j]) |
||||
idl[k--] = old_id; |
||||
idl[k--] = merge_id; |
||||
} |
||||
idl[0] = total; |
||||
} |
||||
|
||||
/* Quicksort + Insertion sort for small arrays */ |
||||
|
||||
#define SMALL 8 |
||||
#define MIDL_SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; } |
||||
|
||||
void |
||||
mdb_midl_sort( MDB_IDL ids ) |
||||
{ |
||||
/* Max possible depth of int-indexed tree * 2 items/level */ |
||||
int istack[sizeof(int)*CHAR_BIT * 2]; |
||||
int i,j,k,l,ir,jstack; |
||||
MDB_ID a, itmp; |
||||
|
||||
ir = (int)ids[0]; |
||||
l = 1; |
||||
jstack = 0; |
||||
for(;;) { |
||||
if (ir - l < SMALL) { /* Insertion sort */ |
||||
for (j=l+1;j<=ir;j++) { |
||||
a = ids[j]; |
||||
for (i=j-1;i>=1;i--) { |
||||
if (ids[i] >= a) break; |
||||
ids[i+1] = ids[i]; |
||||
} |
||||
ids[i+1] = a; |
||||
} |
||||
if (jstack == 0) break; |
||||
ir = istack[jstack--]; |
||||
l = istack[jstack--]; |
||||
} else { |
||||
k = (l + ir) >> 1; /* Choose median of left, center, right */ |
||||
MIDL_SWAP(ids[k], ids[l+1]); |
||||
if (ids[l] < ids[ir]) { |
||||
MIDL_SWAP(ids[l], ids[ir]); |
||||
} |
||||
if (ids[l+1] < ids[ir]) { |
||||
MIDL_SWAP(ids[l+1], ids[ir]); |
||||
} |
||||
if (ids[l] < ids[l+1]) { |
||||
MIDL_SWAP(ids[l], ids[l+1]); |
||||
} |
||||
i = l+1; |
||||
j = ir; |
||||
a = ids[l+1]; |
||||
for(;;) { |
||||
do i++; while(ids[i] > a); |
||||
do j--; while(ids[j] < a); |
||||
if (j < i) break; |
||||
MIDL_SWAP(ids[i],ids[j]); |
||||
} |
||||
ids[l+1] = ids[j]; |
||||
ids[j] = a; |
||||
jstack += 2; |
||||
if (ir-i+1 >= j-l) { |
||||
istack[jstack] = ir; |
||||
istack[jstack-1] = i; |
||||
ir = j-1; |
||||
} else { |
||||
istack[jstack] = j-1; |
||||
istack[jstack-1] = l; |
||||
l = i; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id ) |
||||
{ |
||||
/*
|
||||
* binary search of id in ids |
||||
* if found, returns position of id |
||||
* if not found, returns first position greater than id |
||||
*/ |
||||
unsigned base = 0; |
||||
unsigned cursor = 1; |
||||
int val = 0; |
||||
unsigned n = (unsigned)ids[0].mid; |
||||
|
||||
while( 0 < n ) { |
||||
unsigned pivot = n >> 1; |
||||
cursor = base + pivot + 1; |
||||
val = CMP( id, ids[cursor].mid ); |
||||
|
||||
if( val < 0 ) { |
||||
n = pivot; |
||||
|
||||
} else if ( val > 0 ) { |
||||
base = cursor; |
||||
n -= pivot + 1; |
||||
|
||||
} else { |
||||
return cursor; |
||||
} |
||||
} |
||||
|
||||
if( val > 0 ) { |
||||
++cursor; |
||||
} |
||||
return cursor; |
||||
} |
||||
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id ) |
||||
{ |
||||
unsigned x, i; |
||||
|
||||
x = mdb_mid2l_search( ids, id->mid ); |
||||
|
||||
if( x < 1 ) { |
||||
/* internal error */ |
||||
return -2; |
||||
} |
||||
|
||||
if ( x <= ids[0].mid && ids[x].mid == id->mid ) { |
||||
/* duplicate */ |
||||
return -1; |
||||
} |
||||
|
||||
if ( ids[0].mid >= MDB_IDL_UM_MAX ) { |
||||
/* too big */ |
||||
return -2; |
||||
|
||||
} else { |
||||
/* insert id */ |
||||
ids[0].mid++; |
||||
for (i=(unsigned)ids[0].mid; i>x; i--) |
||||
ids[i] = ids[i-1]; |
||||
ids[x] = *id; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id ) |
||||
{ |
||||
/* Too big? */ |
||||
if (ids[0].mid >= MDB_IDL_UM_MAX) { |
||||
return -2; |
||||
} |
||||
ids[0].mid++; |
||||
ids[ids[0].mid] = *id; |
||||
return 0; |
||||
} |
||||
|
||||
/** @} */ |
||||
/** @} */ |
||||
@ -0,0 +1,188 @@
|
||||
/** @file midl.h
|
||||
* @brief LMDB ID List header file. |
||||
* |
||||
* This file was originally part of back-bdb but has been |
||||
* modified for use in libmdb. Most of the macros defined |
||||
* in this file are unused, just left over from the original. |
||||
* |
||||
* This file is only used internally in libmdb and its definitions |
||||
* are not exposed publicly. |
||||
*/ |
||||
/* $OpenLDAP$ */ |
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
* |
||||
* Copyright 2000-2021 The OpenLDAP Foundation. |
||||
* Portions Copyright 2001-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
|
||||
#ifndef _MDB_MIDL_H_ |
||||
#define _MDB_MIDL_H_ |
||||
|
||||
#include <stddef.h> |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/** @defgroup internal LMDB Internals
|
||||
* @{ |
||||
*/ |
||||
|
||||
/** @defgroup idls ID List Management
|
||||
* @{ |
||||
*/ |
||||
/** A generic unsigned ID number. These were entryIDs in back-bdb.
|
||||
* Preferably it should have the same size as a pointer. |
||||
*/ |
||||
typedef size_t MDB_ID; |
||||
|
||||
/** An IDL is an ID List, a sorted array of IDs. The first
|
||||
* element of the array is a counter for how many actual |
||||
* IDs are in the list. In the original back-bdb code, IDLs are |
||||
* sorted in ascending order. For libmdb IDLs are sorted in |
||||
* descending order. |
||||
*/ |
||||
typedef MDB_ID *MDB_IDL; |
||||
|
||||
/* IDL sizes - likely should be even bigger
|
||||
* limiting factors: sizeof(ID), thread stack size |
||||
*/ |
||||
#ifndef MDB_IDL_LOGN |
||||
#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */ |
||||
#endif |
||||
#define MDB_IDL_DB_SIZE (1<<MDB_IDL_LOGN) |
||||
#define MDB_IDL_UM_SIZE (1<<(MDB_IDL_LOGN+1)) |
||||
|
||||
#define MDB_IDL_DB_MAX (MDB_IDL_DB_SIZE-1) |
||||
#define MDB_IDL_UM_MAX (MDB_IDL_UM_SIZE-1) |
||||
|
||||
#define MDB_IDL_SIZEOF(ids) (((ids)[0]+1) * sizeof(MDB_ID)) |
||||
#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 ) |
||||
#define MDB_IDL_CPY( dst, src ) (memcpy( dst, src, MDB_IDL_SIZEOF( src ) )) |
||||
#define MDB_IDL_FIRST( ids ) ( (ids)[1] ) |
||||
#define MDB_IDL_LAST( ids ) ( (ids)[(ids)[0]] ) |
||||
|
||||
/** Current max length of an #mdb_midl_alloc()ed IDL */ |
||||
#define MDB_IDL_ALLOCLEN( ids ) ( (ids)[-1] ) |
||||
|
||||
/** Append ID to IDL. The IDL must be big enough. */ |
||||
#define mdb_midl_xappend(idl, id) do { \ |
||||
MDB_ID *xidl = (idl), xlen = ++(xidl[0]); \
|
||||
xidl[xlen] = (id); \
|
||||
} while (0) |
||||
|
||||
/** Search for an ID in an IDL.
|
||||
* @param[in] ids The IDL to search. |
||||
* @param[in] id The ID to search for. |
||||
* @return The index of the first ID greater than or equal to \b id. |
||||
*/ |
||||
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id ); |
||||
|
||||
/** Allocate an IDL.
|
||||
* Allocates memory for an IDL of the given size. |
||||
* @return IDL on success, NULL on failure. |
||||
*/ |
||||
MDB_IDL mdb_midl_alloc(int num); |
||||
|
||||
/** Free an IDL.
|
||||
* @param[in] ids The IDL to free. |
||||
*/ |
||||
void mdb_midl_free(MDB_IDL ids); |
||||
|
||||
/** Shrink an IDL.
|
||||
* Return the IDL to the default size if it has grown larger. |
||||
* @param[in,out] idp Address of the IDL to shrink. |
||||
*/ |
||||
void mdb_midl_shrink(MDB_IDL *idp); |
||||
|
||||
/** Make room for num additional elements in an IDL.
|
||||
* @param[in,out] idp Address of the IDL. |
||||
* @param[in] num Number of elements to make room for. |
||||
* @return 0 on success, ENOMEM on failure. |
||||
*/ |
||||
int mdb_midl_need(MDB_IDL *idp, unsigned num); |
||||
|
||||
/** Append an ID onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to. |
||||
* @param[in] id The ID to append. |
||||
* @return 0 on success, ENOMEM if the IDL is too large. |
||||
*/ |
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id ); |
||||
|
||||
/** Append an IDL onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to. |
||||
* @param[in] app The IDL to append. |
||||
* @return 0 on success, ENOMEM if the IDL is too large. |
||||
*/ |
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app ); |
||||
|
||||
/** Append an ID range onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to. |
||||
* @param[in] id The lowest ID to append. |
||||
* @param[in] n Number of IDs to append. |
||||
* @return 0 on success, ENOMEM if the IDL is too large. |
||||
*/ |
||||
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n ); |
||||
|
||||
/** Merge an IDL onto an IDL. The destination IDL must be big enough.
|
||||
* @param[in] idl The IDL to merge into. |
||||
* @param[in] merge The IDL to merge. |
||||
*/ |
||||
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge ); |
||||
|
||||
/** Sort an IDL.
|
||||
* @param[in,out] ids The IDL to sort. |
||||
*/ |
||||
void mdb_midl_sort( MDB_IDL ids ); |
||||
|
||||
/** An ID2 is an ID/pointer pair.
|
||||
*/ |
||||
typedef struct MDB_ID2 { |
||||
MDB_ID mid; /**< The ID */ |
||||
void *mptr; /**< The pointer */ |
||||
} MDB_ID2; |
||||
|
||||
/** An ID2L is an ID2 List, a sorted array of ID2s.
|
||||
* The first element's \b mid member is a count of how many actual |
||||
* elements are in the array. The \b mptr member of the first element is unused. |
||||
* The array is sorted in ascending order by \b mid. |
||||
*/ |
||||
typedef MDB_ID2 *MDB_ID2L; |
||||
|
||||
/** Search for an ID in an ID2L.
|
||||
* @param[in] ids The ID2L to search. |
||||
* @param[in] id The ID to search for. |
||||
* @return The index of the first ID2 whose \b mid member is greater than or equal to \b id. |
||||
*/ |
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id ); |
||||
|
||||
|
||||
/** Insert an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to insert into. |
||||
* @param[in] id The ID2 to insert. |
||||
* @return 0 on success, -1 if the ID was already present in the ID2L. |
||||
*/ |
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id ); |
||||
|
||||
/** Append an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to append into. |
||||
* @param[in] id The ID2 to append. |
||||
* @return 0 on success, -2 if the ID2L is too big. |
||||
*/ |
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id ); |
||||
|
||||
/** @} */ |
||||
/** @} */ |
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif /* _MDB_MIDL_H_ */ |
||||
@ -0,0 +1,580 @@
|
||||
/* mplay.c - memory-mapped database log replay */ |
||||
/*
|
||||
* Copyright 2011-2023 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <unistd.h> |
||||
#include <time.h> |
||||
#include <string.h> |
||||
#include <ctype.h> |
||||
#include <assert.h> |
||||
#include <sys/types.h> |
||||
#include <sys/wait.h> |
||||
|
||||
#include "lmdb.h" |
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) |
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) |
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ |
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) |
||||
|
||||
#define MDB_SCNy(t) "z" #t |
||||
|
||||
#define SCMP(s) s, (sizeof(s)-1) |
||||
char inbuf[8192]; |
||||
char *dbuf, *kbuf; |
||||
size_t dbufsize; |
||||
int maxkey; |
||||
|
||||
#define SOFF(s) (sizeof(s)+1) |
||||
|
||||
#define MAXENVS 16 |
||||
#define MAXTXNS 16 |
||||
#define MAXCRSS 16 |
||||
|
||||
#define MAXPIDS 16 |
||||
|
||||
typedef struct crspair { |
||||
void *tcrs; /* scanned text pointer */ |
||||
MDB_cursor *rcrs; |
||||
} crspair; |
||||
|
||||
typedef struct txnpair { |
||||
void *ttxn; /* scanned text pointer */ |
||||
MDB_txn *rtxn; |
||||
crspair cursors[MAXCRSS]; |
||||
int ncursors; |
||||
} txnpair; |
||||
|
||||
typedef struct envpair { |
||||
void *tenv; |
||||
MDB_env *renv; |
||||
txnpair txns[MAXTXNS]; |
||||
int ntxns; |
||||
} envpair; |
||||
|
||||
envpair envs[MAXENVS]; |
||||
int nenvs; |
||||
|
||||
envpair *lastenv; |
||||
txnpair *lasttxn; |
||||
crspair *lastcrs; |
||||
|
||||
typedef struct pidpair { |
||||
int tpid; |
||||
pid_t rpid; |
||||
int fdout, fdin; |
||||
} pidpair; |
||||
|
||||
pidpair *lastpid; |
||||
|
||||
pidpair pids[MAXPIDS]; |
||||
int npids; |
||||
|
||||
unsigned long lcount; |
||||
|
||||
static int unhex(unsigned char *c2) |
||||
{ |
||||
int x, c; |
||||
x = *c2++ & 0x4f; |
||||
if (x & 0x40) |
||||
x -= 55; |
||||
c = x << 4; |
||||
x = *c2 & 0x4f; |
||||
if (x & 0x40) |
||||
x -= 55; |
||||
c |= x; |
||||
return c; |
||||
} |
||||
|
||||
int inhex(char *in, char *out) |
||||
{ |
||||
char *c2 = out; |
||||
while (isxdigit(*in)) { |
||||
*c2++ = unhex((unsigned char *)in); |
||||
in += 2; |
||||
} |
||||
return c2 - out; |
||||
} |
||||
|
||||
static void addenv(void *tenv, MDB_env *renv) |
||||
{ |
||||
assert(nenvs < MAXENVS); |
||||
envs[nenvs].tenv = tenv; |
||||
envs[nenvs].renv = renv; |
||||
envs[nenvs].ntxns = 0; |
||||
lastenv = envs+nenvs; |
||||
nenvs++; |
||||
} |
||||
|
||||
static envpair *findenv(void *tenv) |
||||
{ |
||||
int i; |
||||
if (!lastenv || lastenv->tenv != tenv) { |
||||
for (i=0; i<nenvs; i++) |
||||
if (envs[i].tenv == tenv) |
||||
break; |
||||
assert(i < nenvs); |
||||
lastenv = &envs[i]; |
||||
} |
||||
return lastenv; |
||||
} |
||||
|
||||
static void delenv(envpair *ep) |
||||
{ |
||||
int i = ep - envs; |
||||
for (; i<nenvs-1; i++) |
||||
envs[i] = envs[i+1]; |
||||
nenvs--; |
||||
lastenv = NULL; |
||||
} |
||||
|
||||
static void addtxn(void *tenv, void *ttxn, MDB_txn *rtxn) |
||||
{ |
||||
envpair *ep; |
||||
txnpair *tp; |
||||
|
||||
ep = findenv(tenv); |
||||
assert(ep->ntxns < MAXTXNS); |
||||
tp = ep->txns+ep->ntxns; |
||||
tp->ttxn = ttxn; |
||||
tp->rtxn = rtxn; |
||||
tp->ncursors = 0; |
||||
ep->ntxns++; |
||||
lasttxn = tp; |
||||
} |
||||
|
||||
static txnpair *findtxn(void *ttxn) |
||||
{ |
||||
int i, j; |
||||
if (lasttxn && lasttxn->ttxn == ttxn) |
||||
return lasttxn; |
||||
if (lastenv) { |
||||
for (i=0; i<lastenv->ntxns; i++) { |
||||
if (lastenv->txns[i].ttxn == ttxn) { |
||||
lasttxn = lastenv->txns+i; |
||||
return lasttxn; |
||||
} |
||||
} |
||||
} |
||||
for (i=0; i<nenvs; i++) { |
||||
if (envs+i == lastenv) continue; |
||||
for (j=0; j<envs[i].ntxns; j++) { |
||||
if (envs[i].txns[j].ttxn == ttxn) { |
||||
lastenv = envs+i; |
||||
lasttxn = envs[i].txns+j; |
||||
return lasttxn; |
||||
} |
||||
} |
||||
} |
||||
assert(0); /* should have found it */ |
||||
} |
||||
|
||||
static void deltxn(txnpair *tp) |
||||
{ |
||||
int i = tp - lastenv->txns; |
||||
for (; i<lastenv->ntxns-1; i++) |
||||
lastenv->txns[i] = lastenv->txns[i+1]; |
||||
lastenv->ntxns--; |
||||
lasttxn = NULL; |
||||
} |
||||
|
||||
static void addcrs(txnpair *tp, void *tcrs, MDB_cursor *rcrs) |
||||
{ |
||||
int j = tp->ncursors; |
||||
assert(tp->ncursors < MAXCRSS); |
||||
|
||||
tp->cursors[j].tcrs = tcrs; |
||||
tp->cursors[j].rcrs = rcrs; |
||||
tp->ncursors++; |
||||
lastcrs = tp->cursors+j; |
||||
} |
||||
|
||||
static crspair *findcrs(void *tcrs) |
||||
{ |
||||
int i, j, k; |
||||
envpair *ep; |
||||
txnpair *tp; |
||||
crspair *cp; |
||||
if (lastcrs && lastcrs->tcrs == tcrs) |
||||
return lastcrs; |
||||
if (lasttxn) { |
||||
for (k=0, cp=lasttxn->cursors; k<lasttxn->ncursors; k++, cp++) { |
||||
if (cp->tcrs == tcrs) { |
||||
lastcrs = cp; |
||||
return lastcrs; |
||||
} |
||||
} |
||||
} |
||||
if (lastenv) { |
||||
for (j=0, tp=lastenv->txns; j<lastenv->ntxns; j++, tp++){ |
||||
if (tp == lasttxn) |
||||
continue; |
||||
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) { |
||||
if (cp->tcrs == tcrs) { |
||||
lastcrs = cp; |
||||
lasttxn = tp; |
||||
return lastcrs; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
for (i=0, ep=envs; i<nenvs; i++, ep++) { |
||||
for (j=0, tp=ep->txns; j<ep->ntxns; j++, tp++) { |
||||
if (tp == lasttxn) |
||||
continue; |
||||
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) { |
||||
if (cp->tcrs == tcrs) { |
||||
lastcrs = cp; |
||||
lasttxn = tp; |
||||
lastenv = ep; |
||||
return lastcrs; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
assert(0); /* should have found it already */ |
||||
} |
||||
|
||||
static void delcrs(void *tcrs) |
||||
{ |
||||
int i; |
||||
crspair *cp = findcrs(tcrs); |
||||
mdb_cursor_close(cp->rcrs); |
||||
for (i = cp - lasttxn->cursors; i<lasttxn->ncursors-1; i++) |
||||
lasttxn->cursors[i] = lasttxn->cursors[i+1]; |
||||
lasttxn->ncursors--; |
||||
lastcrs = NULL; |
||||
} |
||||
|
||||
void child() |
||||
{ |
||||
int rc; |
||||
MDB_val key, data; |
||||
char *ptr; |
||||
|
||||
while (fgets(inbuf, sizeof(inbuf), stdin)) { |
||||
ptr = inbuf; |
||||
if (!strncmp(ptr, SCMP("exit"))) |
||||
break; |
||||
|
||||
if (!strncmp(ptr, SCMP("mdb_env_create"))) { |
||||
void *tenv; |
||||
MDB_env *renv; |
||||
sscanf(ptr+SOFF("mdb_env_create"), "%p", &tenv); |
||||
E(mdb_env_create(&renv)); |
||||
addenv(tenv, renv); |
||||
} else if (!strncmp(ptr, SCMP("mdb_env_set_maxdbs"))) { |
||||
void *tenv; |
||||
envpair *ep; |
||||
unsigned int maxdbs; |
||||
sscanf(ptr+SOFF("mdb_env_set_maxdbs"), "%p, %u", &tenv, &maxdbs); |
||||
ep = findenv(tenv); |
||||
E(mdb_env_set_maxdbs(ep->renv, maxdbs)); |
||||
} else if (!strncmp(ptr, SCMP("mdb_env_set_mapsize"))) { |
||||
void *tenv; |
||||
envpair *ep; |
||||
size_t mapsize; |
||||
sscanf(ptr+SOFF("mdb_env_set_mapsize"), "%p, %"MDB_SCNy(u), &tenv, &mapsize); |
||||
ep = findenv(tenv); |
||||
E(mdb_env_set_mapsize(ep->renv, mapsize)); |
||||
} else if (!strncmp(ptr, SCMP("mdb_env_open"))) { |
||||
void *tenv; |
||||
envpair *ep; |
||||
char *path; |
||||
int len; |
||||
unsigned int flags, mode; |
||||
sscanf(ptr+SOFF("mdb_env_open"), "%p, %n", &tenv, &len); |
||||
path = ptr+SOFF("mdb_env_open")+len; |
||||
ptr = strchr(path, ','); |
||||
*ptr++ = '\0'; |
||||
sscanf(ptr, "%u, %o", &flags, &mode); |
||||
ep = findenv(tenv); |
||||
E(mdb_env_open(ep->renv, path, flags, mode)); |
||||
if (!maxkey) { |
||||
maxkey = mdb_env_get_maxkeysize(ep->renv); |
||||
kbuf = malloc(maxkey+2); |
||||
dbuf = malloc(maxkey+2); |
||||
dbufsize = maxkey; |
||||
} |
||||
} else if (!strncmp(ptr, SCMP("mdb_env_close"))) { |
||||
void *tenv; |
||||
envpair *ep; |
||||
sscanf(ptr+SOFF("mdb_env_close"), "%p", &tenv); |
||||
ep = findenv(tenv); |
||||
mdb_env_close(ep->renv); |
||||
delenv(ep); |
||||
if (!nenvs) /* if no other envs left, this process is done */ |
||||
break; |
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_begin"))) { |
||||
unsigned int flags; |
||||
void *tenv, *ttxn; |
||||
envpair *ep; |
||||
MDB_txn *rtxn; |
||||
sscanf(ptr+SOFF("mdb_txn_begin"), "%p, %*p, %u = %p", &tenv, &flags, &ttxn); |
||||
ep = findenv(tenv); |
||||
E(mdb_txn_begin(ep->renv, NULL, flags, &rtxn)); |
||||
addtxn(tenv, ttxn, rtxn); |
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_commit"))) { |
||||
void *ttxn; |
||||
txnpair *tp; |
||||
sscanf(ptr+SOFF("mdb_txn_commit"), "%p", &ttxn); |
||||
tp = findtxn(ttxn); |
||||
E(mdb_txn_commit(tp->rtxn)); |
||||
deltxn(tp); |
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_abort"))) { |
||||
void *ttxn; |
||||
txnpair *tp; |
||||
sscanf(ptr+SOFF("mdb_txn_abort"), "%p", &ttxn); |
||||
tp = findtxn(ttxn); |
||||
mdb_txn_abort(tp->rtxn); |
||||
deltxn(tp); |
||||
} else if (!strncmp(ptr, SCMP("mdb_dbi_open"))) { |
||||
void *ttxn; |
||||
txnpair *tp; |
||||
char *dbname; |
||||
unsigned int flags; |
||||
unsigned int tdbi; |
||||
MDB_dbi dbi; |
||||
sscanf(ptr+SOFF("mdb_dbi_open"), "%p, ", &ttxn); |
||||
dbname = strchr(ptr+SOFF("mdb_dbi_open"), ','); |
||||
dbname += 2; |
||||
ptr = strchr(dbname, ','); |
||||
*ptr++ = '\0'; |
||||
if (!strcmp(dbname, "(null)")) |
||||
dbname = NULL; |
||||
sscanf(ptr, "%u = %u", &flags, &tdbi); |
||||
tp = findtxn(ttxn); |
||||
E(mdb_dbi_open(tp->rtxn, dbname, flags, &dbi)); |
||||
} else if (!strncmp(ptr, SCMP("mdb_dbi_close"))) { |
||||
void *tenv; |
||||
envpair *ep; |
||||
unsigned int tdbi; |
||||
sscanf(ptr+SOFF("mdb_dbi_close"), "%p, %u", &tenv, &tdbi); |
||||
ep = findenv(tenv); |
||||
mdb_dbi_close(ep->renv, tdbi); |
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_open"))) { |
||||
void *ttxn, *tcrs; |
||||
txnpair *tp; |
||||
MDB_cursor *rcrs; |
||||
unsigned int tdbi; |
||||
sscanf(ptr+SOFF("mdb_cursor_open"), "%p, %u = %p", &ttxn, &tdbi, &tcrs); |
||||
tp = findtxn(ttxn); |
||||
E(mdb_cursor_open(tp->rtxn, tdbi, &rcrs)); |
||||
addcrs(tp, tcrs, rcrs); |
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_close"))) { |
||||
void *tcrs; |
||||
sscanf(ptr+SOFF("mdb_cursor_close"), "%p", &tcrs); |
||||
delcrs(tcrs); |
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_put"))) { |
||||
void *tcrs; |
||||
crspair *cp; |
||||
unsigned int flags; |
||||
int len; |
||||
sscanf(ptr+SOFF("mdb_cursor_put"), "%p, ", &tcrs); |
||||
cp = findcrs(tcrs); |
||||
ptr = strchr(ptr+SOFF("mdb_cursor_put"), ','); |
||||
sscanf(ptr+1, "%"MDB_SCNy(u)",", &key.mv_size); |
||||
if (key.mv_size) { |
||||
ptr = strchr(ptr, '['); |
||||
inhex(ptr+1, kbuf); |
||||
key.mv_data = kbuf; |
||||
ptr += key.mv_size * 2 + 2; |
||||
} |
||||
ptr = strchr(ptr+1, ','); |
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len); |
||||
if (data.mv_size > dbufsize) { |
||||
dbuf = realloc(dbuf, data.mv_size+2); |
||||
assert(dbuf != NULL); |
||||
dbufsize = data.mv_size; |
||||
} |
||||
ptr += len+1; |
||||
if (*ptr == '[') { |
||||
inhex(ptr+1, dbuf); |
||||
data.mv_data = dbuf; |
||||
ptr += data.mv_size * 2 + 2; |
||||
} else { |
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(lasttxn->rtxn)); |
||||
} |
||||
sscanf(ptr+1, "%u", &flags); |
||||
E(mdb_cursor_put(cp->rcrs, &key, &data, flags)); |
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_del"))) { |
||||
void *tcrs; |
||||
crspair *cp; |
||||
unsigned int flags; |
||||
sscanf(ptr+SOFF("mdb_cursor_del"), "%p, %u", &tcrs, &flags); |
||||
cp = findcrs(tcrs); |
||||
E(mdb_cursor_del(cp->rcrs, flags)); |
||||
} else if (!strncmp(ptr, SCMP("mdb_put"))) { |
||||
void *ttxn; |
||||
txnpair *tp; |
||||
unsigned int tdbi, flags; |
||||
int len; |
||||
sscanf(ptr+SOFF("mdb_put"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size); |
||||
tp = findtxn(ttxn); |
||||
ptr = strchr(ptr+SOFF("mdb_put"), '['); |
||||
inhex(ptr+1, kbuf); |
||||
key.mv_data = kbuf; |
||||
ptr += key.mv_size * 2 + 2; |
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len); |
||||
if (data.mv_size > dbufsize) { |
||||
dbuf = realloc(dbuf, data.mv_size+2); |
||||
assert(dbuf != NULL); |
||||
dbufsize = data.mv_size; |
||||
} |
||||
ptr += len+1; |
||||
if (*ptr == '[') { |
||||
inhex(ptr+1, dbuf); |
||||
ptr += data.mv_size * 2 + 2; |
||||
} else { |
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn)); |
||||
} |
||||
data.mv_data = dbuf; |
||||
sscanf(ptr+1, "%u", &flags); |
||||
RES(MDB_KEYEXIST,mdb_put(tp->rtxn, tdbi, &key, &data, flags)); |
||||
} else if (!strncmp(ptr, SCMP("mdb_del"))) { |
||||
void *ttxn; |
||||
txnpair *tp; |
||||
unsigned int tdbi; |
||||
int len; |
||||
sscanf(ptr+SOFF("mdb_del"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size); |
||||
tp = findtxn(ttxn); |
||||
ptr = strchr(ptr+SOFF("mdb_del"), '['); |
||||
inhex(ptr+1, kbuf); |
||||
key.mv_data = kbuf; |
||||
ptr += key.mv_size * 2 + 2; |
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len); |
||||
if (data.mv_size > dbufsize) { |
||||
dbuf = realloc(dbuf, data.mv_size+2); |
||||
assert(dbuf != NULL); |
||||
dbufsize = data.mv_size; |
||||
} |
||||
ptr += len+1; |
||||
if (*ptr == '[') { |
||||
inhex(ptr+1, dbuf); |
||||
} else { |
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn)); |
||||
} |
||||
data.mv_data = dbuf; |
||||
RES(MDB_NOTFOUND,mdb_del(tp->rtxn, tdbi, &key, &data)); |
||||
} |
||||
write(1, "\n", 1); |
||||
} |
||||
exit(0); |
||||
} |
||||
|
||||
static pidpair *addpid(int tpid) |
||||
{ |
||||
int fdout[2], fdin[2]; |
||||
pid_t pid; |
||||
assert(npids < MAXPIDS); |
||||
pids[npids].tpid = tpid; |
||||
pipe(fdout); |
||||
pipe(fdin); |
||||
if ((pid = fork()) == 0) { |
||||
/* child */ |
||||
fflush(stdin); |
||||
fflush(stdout); |
||||
dup2(fdout[0], 0); |
||||
dup2(fdin[1], 1); |
||||
child(); |
||||
return 0; /* NOTREACHED */ |
||||
} else { |
||||
pids[npids].rpid = pid; |
||||
pids[npids].fdout = fdout[1]; |
||||
pids[npids].fdin = fdin[0]; |
||||
lastpid = pids+npids; |
||||
npids++; |
||||
return lastpid; |
||||
} |
||||
} |
||||
|
||||
static pidpair *findpid(int tpid) |
||||
{ |
||||
int i; |
||||
if (!lastpid || lastpid->tpid != tpid) { |
||||
for (i=0; i<npids; i++) |
||||
if (pids[i].tpid == tpid) |
||||
break; |
||||
if (i == npids) |
||||
return NULL; |
||||
lastpid = &pids[i]; |
||||
} |
||||
return lastpid; |
||||
} |
||||
|
||||
volatile pid_t killpid; |
||||
|
||||
static void delpid(int tpid) |
||||
{ |
||||
pidpair *pp = findpid(tpid); |
||||
if (pp) { |
||||
pid_t kpid = pp->rpid; |
||||
killpid = kpid; |
||||
write(pp->fdout, "exit\n", sizeof("exit")); |
||||
while (killpid == kpid) |
||||
usleep(10000); |
||||
} |
||||
} |
||||
|
||||
static void reaper(int sig) |
||||
{ |
||||
int status, i; |
||||
pid_t pid = waitpid(-1, &status, 0); |
||||
if (pid > 0) { |
||||
fprintf(stderr, "# %s %d\n", WIFEXITED(status) ? "exited" : "killed", pid); |
||||
for (i=0; i<npids; i++) |
||||
if (pids[i].rpid == pid) |
||||
break; |
||||
assert(i < npids); |
||||
close(pids[i].fdout); |
||||
close(pids[i].fdin); |
||||
for (;i<npids-1; i++) |
||||
pids[i] = pids[i+1]; |
||||
npids--; |
||||
killpid = 0; |
||||
} |
||||
} |
||||
|
||||
int main(int argc,char * argv[]) |
||||
{ |
||||
signal(SIGCHLD, reaper); |
||||
|
||||
while (fgets(inbuf, sizeof(inbuf), stdin)) { |
||||
pidpair *pp; |
||||
int tpid, len; |
||||
char c, *ptr; |
||||
lcount++; |
||||
|
||||
if (inbuf[0] == '#' && !strncmp(inbuf+1, SCMP(" killed"))) { |
||||
sscanf(inbuf+SOFF("killed"),"%d", &tpid); |
||||
delpid(tpid); |
||||
continue; |
||||
} |
||||
|
||||
if (inbuf[0] != '>') |
||||
continue; |
||||
ptr = inbuf+1; |
||||
sscanf(ptr, "%d:%n", &tpid, &len); |
||||
pp = findpid(tpid); |
||||
if (!pp) |
||||
pp = addpid(tpid); /* new process */ |
||||
|
||||
ptr = inbuf+len+1; |
||||
len = strlen(ptr); |
||||
write(pp->fdout, ptr, len); /* send command and wait for ack */ |
||||
read(pp->fdin, &c, 1); |
||||
} |
||||
while (npids) |
||||
delpid(pids[0].tpid); |
||||
} |
||||
@ -0,0 +1,177 @@
|
||||
/* mtest.c - memory-mapped database tester/toy */ |
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <time.h> |
||||
#include "lmdb.h" |
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) |
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) |
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ |
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) |
||||
|
||||
int main(int argc,char * argv[]) |
||||
{ |
||||
int i = 0, j = 0, rc; |
||||
MDB_env *env; |
||||
MDB_dbi dbi; |
||||
MDB_val key, data; |
||||
MDB_txn *txn; |
||||
MDB_stat mst; |
||||
MDB_cursor *cursor, *cur2; |
||||
MDB_cursor_op op; |
||||
int count; |
||||
int *values; |
||||
char sval[32] = ""; |
||||
|
||||
srand(time(NULL)); |
||||
|
||||
count = (rand()%384) + 64; |
||||
values = (int *)malloc(count*sizeof(int)); |
||||
|
||||
for(i = 0;i<count;i++) { |
||||
values[i] = rand()%1024; |
||||
} |
||||
|
||||
E(mdb_env_create(&env)); |
||||
E(mdb_env_set_maxreaders(env, 1)); |
||||
E(mdb_env_set_mapsize(env, 10485760)); |
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP /*|MDB_NOSYNC*/, 0664)); |
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
E(mdb_dbi_open(txn, NULL, 0, &dbi)); |
||||
|
||||
key.mv_size = sizeof(int); |
||||
key.mv_data = sval; |
||||
|
||||
printf("Adding %d values\n", count); |
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]); |
||||
/* Set <data> in each iteration, since MDB_NOOVERWRITE may modify it */ |
||||
data.mv_size = sizeof(sval); |
||||
data.mv_data = sval; |
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE))) { |
||||
j++; |
||||
data.mv_size = sizeof(sval); |
||||
data.mv_data = sval; |
||||
} |
||||
} |
||||
if (j) printf("%d duplicates skipped\n", j); |
||||
E(mdb_txn_commit(txn)); |
||||
E(mdb_env_stat(env, &mst)); |
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
printf("key: %p %.*s, data: %p %.*s\n", |
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data, |
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
|
||||
j=0; |
||||
key.mv_data = sval; |
||||
for (i= count - 1; i > -1; i-= (rand()%5)) { |
||||
j++; |
||||
txn=NULL; |
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
sprintf(sval, "%03x ", values[i]); |
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) { |
||||
j--; |
||||
mdb_txn_abort(txn); |
||||
} else { |
||||
E(mdb_txn_commit(txn)); |
||||
} |
||||
} |
||||
free(values); |
||||
printf("Deleted %d values\n", j); |
||||
|
||||
E(mdb_env_stat(env, &mst)); |
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
printf("Cursor next\n"); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
printf("Cursor last\n"); |
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST)); |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
printf("Cursor prev\n"); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
printf("Cursor last/prev\n"); |
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST)); |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_PREV)); |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
|
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
|
||||
printf("Deleting with cursor\n"); |
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cur2)); |
||||
for (i=0; i<50; i++) { |
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, MDB_NEXT))) |
||||
break; |
||||
printf("key: %p %.*s, data: %p %.*s\n", |
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data, |
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data); |
||||
E(mdb_del(txn, dbi, &key, NULL)); |
||||
} |
||||
|
||||
printf("Restarting cursor in txn\n"); |
||||
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) { |
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, op))) |
||||
break; |
||||
printf("key: %p %.*s, data: %p %.*s\n", |
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data, |
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
mdb_cursor_close(cur2); |
||||
E(mdb_txn_commit(txn)); |
||||
|
||||
printf("Restarting cursor outside txn\n"); |
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) { |
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cursor, &key, &data, op))) |
||||
break; |
||||
printf("key: %p %.*s, data: %p %.*s\n", |
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data, |
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
|
||||
mdb_dbi_close(env, dbi); |
||||
mdb_env_close(env); |
||||
|
||||
return 0; |
||||
} |
||||
@ -0,0 +1,124 @@
|
||||
/* mtest2.c - memory-mapped database tester/toy */ |
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
|
||||
/* Just like mtest.c, but using a subDB instead of the main DB */ |
||||
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <time.h> |
||||
#include "lmdb.h" |
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) |
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) |
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ |
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) |
||||
|
||||
int main(int argc,char * argv[]) |
||||
{ |
||||
int i = 0, j = 0, rc; |
||||
MDB_env *env; |
||||
MDB_dbi dbi; |
||||
MDB_val key, data; |
||||
MDB_txn *txn; |
||||
MDB_stat mst; |
||||
MDB_cursor *cursor; |
||||
int count; |
||||
int *values; |
||||
char sval[32] = ""; |
||||
|
||||
srand(time(NULL)); |
||||
|
||||
count = (rand()%384) + 64; |
||||
values = (int *)malloc(count*sizeof(int)); |
||||
|
||||
for(i = 0;i<count;i++) { |
||||
values[i] = rand()%1024; |
||||
} |
||||
|
||||
E(mdb_env_create(&env)); |
||||
E(mdb_env_set_maxreaders(env, 1)); |
||||
E(mdb_env_set_mapsize(env, 10485760)); |
||||
E(mdb_env_set_maxdbs(env, 4)); |
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664)); |
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
E(mdb_dbi_open(txn, "id1", MDB_CREATE, &dbi)); |
||||
|
||||
key.mv_size = sizeof(int); |
||||
key.mv_data = sval; |
||||
|
||||
printf("Adding %d values\n", count); |
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]); |
||||
data.mv_size = sizeof(sval); |
||||
data.mv_data = sval; |
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE))) |
||||
j++; |
||||
} |
||||
if (j) printf("%d duplicates skipped\n", j); |
||||
E(mdb_txn_commit(txn)); |
||||
E(mdb_env_stat(env, &mst)); |
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
printf("key: %p %.*s, data: %p %.*s\n", |
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data, |
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
|
||||
j=0; |
||||
key.mv_data = sval; |
||||
for (i= count - 1; i > -1; i-= (rand()%5)) { |
||||
j++; |
||||
txn=NULL; |
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
sprintf(sval, "%03x ", values[i]); |
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) { |
||||
j--; |
||||
mdb_txn_abort(txn); |
||||
} else { |
||||
E(mdb_txn_commit(txn)); |
||||
} |
||||
} |
||||
free(values); |
||||
printf("Deleted %d values\n", j); |
||||
|
||||
E(mdb_env_stat(env, &mst)); |
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
printf("Cursor next\n"); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
printf("Cursor prev\n"); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
|
||||
mdb_dbi_close(env, dbi); |
||||
mdb_env_close(env); |
||||
return 0; |
||||
} |
||||
@ -0,0 +1,133 @@
|
||||
/* mtest3.c - memory-mapped database tester/toy */ |
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
|
||||
/* Tests for sorted duplicate DBs */ |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
#include "lmdb.h" |
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) |
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) |
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ |
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) |
||||
|
||||
int main(int argc,char * argv[]) |
||||
{ |
||||
int i = 0, j = 0, rc; |
||||
MDB_env *env; |
||||
MDB_dbi dbi; |
||||
MDB_val key, data; |
||||
MDB_txn *txn; |
||||
MDB_stat mst; |
||||
MDB_cursor *cursor; |
||||
int count; |
||||
int *values; |
||||
char sval[32]; |
||||
char kval[sizeof(int)]; |
||||
|
||||
srand(time(NULL)); |
||||
|
||||
memset(sval, 0, sizeof(sval)); |
||||
|
||||
count = (rand()%384) + 64; |
||||
values = (int *)malloc(count*sizeof(int)); |
||||
|
||||
for(i = 0;i<count;i++) { |
||||
values[i] = rand()%1024; |
||||
} |
||||
|
||||
E(mdb_env_create(&env)); |
||||
E(mdb_env_set_mapsize(env, 10485760)); |
||||
E(mdb_env_set_maxdbs(env, 4)); |
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664)); |
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi)); |
||||
|
||||
key.mv_size = sizeof(int); |
||||
key.mv_data = kval; |
||||
data.mv_size = sizeof(sval); |
||||
data.mv_data = sval; |
||||
|
||||
printf("Adding %d values\n", count); |
||||
for (i=0;i<count;i++) { |
||||
if (!(i & 0x0f)) |
||||
sprintf(kval, "%03x", values[i]); |
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]); |
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA))) |
||||
j++; |
||||
} |
||||
if (j) printf("%d duplicates skipped\n", j); |
||||
E(mdb_txn_commit(txn)); |
||||
E(mdb_env_stat(env, &mst)); |
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
printf("key: %p %.*s, data: %p %.*s\n", |
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data, |
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
|
||||
j=0; |
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) { |
||||
j++; |
||||
txn=NULL; |
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
sprintf(kval, "%03x", values[i & ~0x0f]); |
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]); |
||||
key.mv_size = sizeof(int); |
||||
key.mv_data = kval; |
||||
data.mv_size = sizeof(sval); |
||||
data.mv_data = sval; |
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) { |
||||
j--; |
||||
mdb_txn_abort(txn); |
||||
} else { |
||||
E(mdb_txn_commit(txn)); |
||||
} |
||||
} |
||||
free(values); |
||||
printf("Deleted %d values\n", j); |
||||
|
||||
E(mdb_env_stat(env, &mst)); |
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
printf("Cursor next\n"); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
printf("Cursor prev\n"); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
|
||||
mdb_dbi_close(env, dbi); |
||||
mdb_env_close(env); |
||||
return 0; |
||||
} |
||||
@ -0,0 +1,168 @@
|
||||
/* mtest4.c - memory-mapped database tester/toy */ |
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
|
||||
/* Tests for sorted duplicate DBs with fixed-size keys */ |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
#include "lmdb.h" |
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) |
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) |
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ |
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) |
||||
|
||||
int main(int argc,char * argv[]) |
||||
{ |
||||
int i = 0, j = 0, rc; |
||||
MDB_env *env; |
||||
MDB_dbi dbi; |
||||
MDB_val key, data; |
||||
MDB_txn *txn; |
||||
MDB_stat mst; |
||||
MDB_cursor *cursor; |
||||
int count; |
||||
int *values; |
||||
char sval[8]; |
||||
char kval[sizeof(int)]; |
||||
|
||||
memset(sval, 0, sizeof(sval)); |
||||
|
||||
count = 510; |
||||
values = (int *)malloc(count*sizeof(int)); |
||||
|
||||
for(i = 0;i<count;i++) { |
||||
values[i] = i*5; |
||||
} |
||||
|
||||
E(mdb_env_create(&env)); |
||||
E(mdb_env_set_mapsize(env, 10485760)); |
||||
E(mdb_env_set_maxdbs(env, 4)); |
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664)); |
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
E(mdb_dbi_open(txn, "id4", MDB_CREATE|MDB_DUPSORT|MDB_DUPFIXED, &dbi)); |
||||
|
||||
key.mv_size = sizeof(int); |
||||
key.mv_data = kval; |
||||
data.mv_size = sizeof(sval); |
||||
data.mv_data = sval; |
||||
|
||||
printf("Adding %d values\n", count); |
||||
strcpy(kval, "001"); |
||||
for (i=0;i<count;i++) { |
||||
sprintf(sval, "%07x", values[i]); |
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA))) |
||||
j++; |
||||
} |
||||
if (j) printf("%d duplicates skipped\n", j); |
||||
E(mdb_txn_commit(txn)); |
||||
E(mdb_env_stat(env, &mst)); |
||||
|
||||
/* there should be one full page of dups now.
|
||||
*/ |
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
printf("key: %p %.*s, data: %p %.*s\n", |
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data, |
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
|
||||
/* test all 3 branches of split code:
|
||||
* 1: new key in lower half |
||||
* 2: new key at split point |
||||
* 3: new key in upper half |
||||
*/ |
||||
|
||||
key.mv_size = sizeof(int); |
||||
key.mv_data = kval; |
||||
data.mv_size = sizeof(sval); |
||||
data.mv_data = sval; |
||||
|
||||
sprintf(sval, "%07x", values[3]+1); |
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)); |
||||
mdb_txn_abort(txn); |
||||
|
||||
sprintf(sval, "%07x", values[255]+1); |
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)); |
||||
mdb_txn_abort(txn); |
||||
|
||||
sprintf(sval, "%07x", values[500]+1); |
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)); |
||||
E(mdb_txn_commit(txn)); |
||||
|
||||
/* Try MDB_NEXT_MULTIPLE */ |
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_MULTIPLE)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
j=0; |
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%3)) { |
||||
j++; |
||||
txn=NULL; |
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
sprintf(sval, "%07x", values[i]); |
||||
key.mv_size = sizeof(int); |
||||
key.mv_data = kval; |
||||
data.mv_size = sizeof(sval); |
||||
data.mv_data = sval; |
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) { |
||||
j--; |
||||
mdb_txn_abort(txn); |
||||
} else { |
||||
E(mdb_txn_commit(txn)); |
||||
} |
||||
} |
||||
free(values); |
||||
printf("Deleted %d values\n", j); |
||||
|
||||
E(mdb_env_stat(env, &mst)); |
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
printf("Cursor next\n"); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
printf("Cursor prev\n"); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
|
||||
mdb_dbi_close(env, dbi); |
||||
mdb_env_close(env); |
||||
return 0; |
||||
} |
||||
@ -0,0 +1,135 @@
|
||||
/* mtest5.c - memory-mapped database tester/toy */ |
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
|
||||
/* Tests for sorted duplicate DBs using cursor_put */ |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
#include "lmdb.h" |
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) |
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) |
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ |
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) |
||||
|
||||
int main(int argc,char * argv[]) |
||||
{ |
||||
int i = 0, j = 0, rc; |
||||
MDB_env *env; |
||||
MDB_dbi dbi; |
||||
MDB_val key, data; |
||||
MDB_txn *txn; |
||||
MDB_stat mst; |
||||
MDB_cursor *cursor; |
||||
int count; |
||||
int *values; |
||||
char sval[32]; |
||||
char kval[sizeof(int)]; |
||||
|
||||
srand(time(NULL)); |
||||
|
||||
memset(sval, 0, sizeof(sval)); |
||||
|
||||
count = (rand()%384) + 64; |
||||
values = (int *)malloc(count*sizeof(int)); |
||||
|
||||
for(i = 0;i<count;i++) { |
||||
values[i] = rand()%1024; |
||||
} |
||||
|
||||
E(mdb_env_create(&env)); |
||||
E(mdb_env_set_mapsize(env, 10485760)); |
||||
E(mdb_env_set_maxdbs(env, 4)); |
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664)); |
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
|
||||
key.mv_size = sizeof(int); |
||||
key.mv_data = kval; |
||||
data.mv_size = sizeof(sval); |
||||
data.mv_data = sval; |
||||
|
||||
printf("Adding %d values\n", count); |
||||
for (i=0;i<count;i++) { |
||||
if (!(i & 0x0f)) |
||||
sprintf(kval, "%03x", values[i]); |
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]); |
||||
if (RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NODUPDATA))) |
||||
j++; |
||||
} |
||||
if (j) printf("%d duplicates skipped\n", j); |
||||
mdb_cursor_close(cursor); |
||||
E(mdb_txn_commit(txn)); |
||||
E(mdb_env_stat(env, &mst)); |
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
printf("key: %p %.*s, data: %p %.*s\n", |
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data, |
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
|
||||
j=0; |
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) { |
||||
j++; |
||||
txn=NULL; |
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
sprintf(kval, "%03x", values[i & ~0x0f]); |
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]); |
||||
key.mv_size = sizeof(int); |
||||
key.mv_data = kval; |
||||
data.mv_size = sizeof(sval); |
||||
data.mv_data = sval; |
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) { |
||||
j--; |
||||
mdb_txn_abort(txn); |
||||
} else { |
||||
E(mdb_txn_commit(txn)); |
||||
} |
||||
} |
||||
free(values); |
||||
printf("Deleted %d values\n", j); |
||||
|
||||
E(mdb_env_stat(env, &mst)); |
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
printf("Cursor next\n"); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
printf("Cursor prev\n"); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
|
||||
mdb_dbi_close(env, dbi); |
||||
mdb_env_close(env); |
||||
return 0; |
||||
} |
||||
@ -0,0 +1,141 @@
|
||||
/* mtest6.c - memory-mapped database tester/toy */ |
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/ |
||||
|
||||
/* Tests for DB splits and merges */ |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
#include "lmdb.h" |
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) |
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) |
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ |
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) |
||||
|
||||
char dkbuf[1024]; |
||||
|
||||
int main(int argc,char * argv[]) |
||||
{ |
||||
int i = 0, j = 0, rc; |
||||
MDB_env *env; |
||||
MDB_dbi dbi; |
||||
MDB_val key, data, sdata; |
||||
MDB_txn *txn; |
||||
MDB_stat mst; |
||||
MDB_cursor *cursor; |
||||
int count; |
||||
int *values; |
||||
long kval; |
||||
char *sval; |
||||
|
||||
srand(time(NULL)); |
||||
|
||||
E(mdb_env_create(&env)); |
||||
E(mdb_env_set_mapsize(env, 10485760)); |
||||
E(mdb_env_set_maxdbs(env, 4)); |
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664)); |
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
E(mdb_dbi_open(txn, "id6", MDB_CREATE|MDB_INTEGERKEY, &dbi)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
E(mdb_stat(txn, dbi, &mst)); |
||||
|
||||
sval = calloc(1, mst.ms_psize / 4); |
||||
key.mv_size = sizeof(long); |
||||
key.mv_data = &kval; |
||||
sdata.mv_size = mst.ms_psize / 4 - 30; |
||||
sdata.mv_data = sval; |
||||
|
||||
printf("Adding 12 values, should yield 3 splits\n"); |
||||
for (i=0;i<12;i++) { |
||||
kval = i*5; |
||||
sprintf(sval, "%08x", kval); |
||||
data = sdata; |
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE)); |
||||
} |
||||
printf("Adding 12 more values, should yield 3 splits\n"); |
||||
for (i=0;i<12;i++) { |
||||
kval = i*5+4; |
||||
sprintf(sval, "%08x", kval); |
||||
data = sdata; |
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE)); |
||||
} |
||||
printf("Adding 12 more values, should yield 3 splits\n"); |
||||
for (i=0;i<12;i++) { |
||||
kval = i*5+1; |
||||
sprintf(sval, "%08x", kval); |
||||
data = sdata; |
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE)); |
||||
} |
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_FIRST)); |
||||
|
||||
do { |
||||
printf("key: %p %s, data: %p %.*s\n", |
||||
key.mv_data, mdb_dkey(&key, dkbuf), |
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data); |
||||
} while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0); |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_commit(txn); |
||||
|
||||
#if 0 |
||||
j=0; |
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) { |
||||
j++; |
||||
txn=NULL; |
||||
E(mdb_txn_begin(env, NULL, 0, &txn)); |
||||
sprintf(kval, "%03x", values[i & ~0x0f]); |
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]); |
||||
key.mv_size = sizeof(int); |
||||
key.mv_data = kval; |
||||
data.mv_size = sizeof(sval); |
||||
data.mv_data = sval; |
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) { |
||||
j--; |
||||
mdb_txn_abort(txn); |
||||
} else { |
||||
E(mdb_txn_commit(txn)); |
||||
} |
||||
} |
||||
free(values); |
||||
printf("Deleted %d values\n", j); |
||||
|
||||
E(mdb_env_stat(env, &mst)); |
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); |
||||
E(mdb_cursor_open(txn, dbi, &cursor)); |
||||
printf("Cursor next\n"); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
printf("Cursor prev\n"); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) { |
||||
printf("key: %.*s, data: %.*s\n", |
||||
(int) key.mv_size, (char *) key.mv_data, |
||||
(int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get"); |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
|
||||
mdb_dbi_close(env, dbi); |
||||
#endif |
||||
mdb_env_close(env); |
||||
|
||||
return 0; |
||||
} |
||||
@ -0,0 +1,73 @@
|
||||
/* sample-bdb.txt - BerkeleyDB toy/sample |
||||
* |
||||
* Do a line-by-line comparison of this and sample-mdb.txt |
||||
*/ |
||||
/* |
||||
* Copyright 2012-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>. |
||||
*/ |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <db.h> |
||||
|
||||
int main(int argc,char * argv[]) |
||||
{ |
||||
int rc; |
||||
DB_ENV *env; |
||||
DB *dbi; |
||||
DBT key, data; |
||||
DB_TXN *txn; |
||||
DBC *cursor; |
||||
char sval[32], kval[32]; |
||||
|
||||
/* Note: Most error checking omitted for simplicity */ |
||||
|
||||
#define FLAGS (DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_TXN|DB_INIT_MPOOL|DB_CREATE|DB_THREAD) |
||||
rc = db_env_create(&env, 0); |
||||
rc = env->open(env, "./testdb", FLAGS, 0664); |
||||
rc = db_create(&dbi, env, 0); |
||||
rc = env->txn_begin(env, NULL, &txn, 0); |
||||
rc = dbi->open(dbi, txn, "test.bdb", NULL, DB_BTREE, DB_CREATE, 0664); |
||||
|
||||
memset(&key, 0, sizeof(DBT)); |
||||
memset(&data, 0, sizeof(DBT)); |
||||
key.size = sizeof(int); |
||||
key.data = sval; |
||||
data.size = sizeof(sval); |
||||
data.data = sval; |
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592); |
||||
rc = dbi->put(dbi, txn, &key, &data, 0); |
||||
rc = txn->commit(txn, 0); |
||||
if (rc) { |
||||
fprintf(stderr, "txn->commit: (%d) %s\n", rc, db_strerror(rc)); |
||||
goto leave; |
||||
} |
||||
rc = env->txn_begin(env, NULL, &txn, 0); |
||||
rc = dbi->cursor(dbi, txn, &cursor, 0); |
||||
key.flags = DB_DBT_USERMEM; |
||||
key.data = kval; |
||||
key.ulen = sizeof(kval); |
||||
data.flags = DB_DBT_USERMEM; |
||||
data.data = sval; |
||||
data.ulen = sizeof(sval); |
||||
while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) { |
||||
printf("key: %p %.*s, data: %p %.*s\n", |
||||
key.data, (int) key.size, (char *) key.data, |
||||
data.data, (int) data.size, (char *) data.data); |
||||
} |
||||
rc = cursor->c_close(cursor); |
||||
rc = txn->abort(txn); |
||||
leave: |
||||
rc = dbi->close(dbi, 0); |
||||
rc = env->close(env, 0); |
||||
return rc; |
||||
} |
||||
@ -0,0 +1,62 @@
|
||||
/* sample-mdb.txt - MDB toy/sample |
||||
* |
||||
* Do a line-by-line comparison of this and sample-bdb.txt |
||||
*/ |
||||
/* |
||||
* Copyright 2012-2021 Howard Chu, Symas Corp. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted only as authorized by the OpenLDAP |
||||
* Public License. |
||||
* |
||||
* A copy of this license is available in the file LICENSE in the |
||||
* top-level directory of the distribution or, alternatively, at |
||||
* <http://www.OpenLDAP.org/license.html>. |
||||
*/ |
||||
#include <stdio.h> |
||||
#include "lmdb.h" |
||||
|
||||
int main(int argc,char * argv[]) |
||||
{ |
||||
int rc; |
||||
MDB_env *env; |
||||
MDB_dbi dbi; |
||||
MDB_val key, data; |
||||
MDB_txn *txn; |
||||
MDB_cursor *cursor; |
||||
char sval[32]; |
||||
|
||||
/* Note: Most error checking omitted for simplicity */ |
||||
|
||||
rc = mdb_env_create(&env); |
||||
rc = mdb_env_open(env, "./testdb", 0, 0664); |
||||
rc = mdb_txn_begin(env, NULL, 0, &txn); |
||||
rc = mdb_dbi_open(txn, NULL, 0, &dbi); |
||||
|
||||
key.mv_size = sizeof(int); |
||||
key.mv_data = sval; |
||||
data.mv_size = sizeof(sval); |
||||
data.mv_data = sval; |
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592); |
||||
rc = mdb_put(txn, dbi, &key, &data, 0); |
||||
rc = mdb_txn_commit(txn); |
||||
if (rc) { |
||||
fprintf(stderr, "mdb_txn_commit: (%d) %s\n", rc, mdb_strerror(rc)); |
||||
goto leave; |
||||
} |
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn); |
||||
rc = mdb_cursor_open(txn, dbi, &cursor); |
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
||||
printf("key: %p %.*s, data: %p %.*s\n", |
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data, |
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data); |
||||
} |
||||
mdb_cursor_close(cursor); |
||||
mdb_txn_abort(txn); |
||||
leave: |
||||
mdb_dbi_close(env, dbi); |
||||
mdb_env_close(env); |
||||
return 0; |
||||
} |
||||
@ -0,0 +1,22 @@
|
||||
<tagfile> |
||||
<compound kind="page"> |
||||
<name>mdb_copy_1</name> |
||||
<title>mdb_copy - environment copy tool</title> |
||||
<filename>mdb_copy.1</filename> |
||||
</compound> |
||||
<compound kind="page"> |
||||
<name>mdb_dump_1</name> |
||||
<title>mdb_dump - environment export tool</title> |
||||
<filename>mdb_dump.1</filename> |
||||
</compound> |
||||
<compound kind="page"> |
||||
<name>mdb_load_1</name> |
||||
<title>mdb_load - environment import tool</title> |
||||
<filename>mdb_load.1</filename> |
||||
</compound> |
||||
<compound kind="page"> |
||||
<name>mdb_stat_1</name> |
||||
<title>mdb_stat - environment status tool</title> |
||||
<filename>mdb_stat.1</filename> |
||||
</compound> |
||||
</tagfile> |
||||
@ -0,0 +1,153 @@
|
||||
noinst_LIBRARIES = libopus_internal.a
|
||||
|
||||
libopus_internal_a_SOURCES = \
|
||||
celt/bands.c \
|
||||
celt/celt.c \
|
||||
celt/celt_encoder.c \
|
||||
celt/celt_decoder.c \
|
||||
celt/celt_lpc.c \
|
||||
celt/cwrs.c \
|
||||
celt/entcode.c \
|
||||
celt/entdec.c \
|
||||
celt/entenc.c \
|
||||
celt/kiss_fft.c \
|
||||
celt/laplace.c \
|
||||
celt/mathops.c \
|
||||
celt/mdct.c \
|
||||
celt/modes.c \
|
||||
celt/pitch.c \
|
||||
celt/quant_bands.c \
|
||||
celt/rate.c \
|
||||
celt/vq.c \
|
||||
silk/CNG.c \
|
||||
silk/code_signs.c \
|
||||
silk/init_decoder.c \
|
||||
silk/decode_core.c \
|
||||
silk/decode_frame.c \
|
||||
silk/decode_parameters.c \
|
||||
silk/decode_indices.c \
|
||||
silk/decode_pulses.c \
|
||||
silk/decoder_set_fs.c \
|
||||
silk/dec_API.c \
|
||||
silk/enc_API.c \
|
||||
silk/encode_indices.c \
|
||||
silk/encode_pulses.c \
|
||||
silk/gain_quant.c \
|
||||
silk/interpolate.c \
|
||||
silk/LP_variable_cutoff.c \
|
||||
silk/NLSF_decode.c \
|
||||
silk/NSQ.c \
|
||||
silk/NSQ_del_dec.c \
|
||||
silk/PLC.c \
|
||||
silk/shell_coder.c \
|
||||
silk/tables_gain.c \
|
||||
silk/tables_LTP.c \
|
||||
silk/tables_NLSF_CB_NB_MB.c \
|
||||
silk/tables_NLSF_CB_WB.c \
|
||||
silk/tables_other.c \
|
||||
silk/tables_pitch_lag.c \
|
||||
silk/tables_pulses_per_block.c \
|
||||
silk/VAD.c \
|
||||
silk/control_audio_bandwidth.c \
|
||||
silk/quant_LTP_gains.c \
|
||||
silk/VQ_WMat_EC.c \
|
||||
silk/HP_variable_cutoff.c \
|
||||
silk/NLSF_encode.c \
|
||||
silk/NLSF_VQ.c \
|
||||
silk/NLSF_unpack.c \
|
||||
silk/NLSF_del_dec_quant.c \
|
||||
silk/process_NLSFs.c \
|
||||
silk/stereo_LR_to_MS.c \
|
||||
silk/stereo_MS_to_LR.c \
|
||||
silk/check_control_input.c \
|
||||
silk/control_SNR.c \
|
||||
silk/init_encoder.c \
|
||||
silk/control_codec.c \
|
||||
silk/A2NLSF.c \
|
||||
silk/ana_filt_bank_1.c \
|
||||
silk/biquad_alt.c \
|
||||
silk/bwexpander_32.c \
|
||||
silk/bwexpander.c \
|
||||
silk/debug.c \
|
||||
silk/decode_pitch.c \
|
||||
silk/inner_prod_aligned.c \
|
||||
silk/lin2log.c \
|
||||
silk/log2lin.c \
|
||||
silk/LPC_analysis_filter.c \
|
||||
silk/LPC_inv_pred_gain.c \
|
||||
silk/table_LSF_cos.c \
|
||||
silk/NLSF2A.c \
|
||||
silk/NLSF_stabilize.c \
|
||||
silk/NLSF_VQ_weights_laroia.c \
|
||||
silk/pitch_est_tables.c \
|
||||
silk/resampler.c \
|
||||
silk/resampler_down2_3.c \
|
||||
silk/resampler_down2.c \
|
||||
silk/resampler_private_AR2.c \
|
||||
silk/resampler_private_down_FIR.c \
|
||||
silk/resampler_private_IIR_FIR.c \
|
||||
silk/resampler_private_up2_HQ.c \
|
||||
silk/resampler_rom.c \
|
||||
silk/sigm_Q15.c \
|
||||
silk/sort.c \
|
||||
silk/sum_sqr_shift.c \
|
||||
silk/stereo_decode_pred.c \
|
||||
silk/stereo_encode_pred.c \
|
||||
silk/stereo_find_predictor.c \
|
||||
silk/stereo_quant_pred.c \
|
||||
silk/LPC_fit.c \
|
||||
silk/float/apply_sine_window_FLP.c \
|
||||
silk/float/corrMatrix_FLP.c \
|
||||
silk/float/encode_frame_FLP.c \
|
||||
silk/float/find_LPC_FLP.c \
|
||||
silk/float/find_LTP_FLP.c \
|
||||
silk/float/find_pitch_lags_FLP.c \
|
||||
silk/float/find_pred_coefs_FLP.c \
|
||||
silk/float/LPC_analysis_filter_FLP.c \
|
||||
silk/float/LTP_analysis_filter_FLP.c \
|
||||
silk/float/LTP_scale_ctrl_FLP.c \
|
||||
silk/float/noise_shape_analysis_FLP.c \
|
||||
silk/float/process_gains_FLP.c \
|
||||
silk/float/regularize_correlations_FLP.c \
|
||||
silk/float/residual_energy_FLP.c \
|
||||
silk/float/warped_autocorrelation_FLP.c \
|
||||
silk/float/wrappers_FLP.c \
|
||||
silk/float/autocorrelation_FLP.c \
|
||||
silk/float/burg_modified_FLP.c \
|
||||
silk/float/bwexpander_FLP.c \
|
||||
silk/float/energy_FLP.c \
|
||||
silk/float/inner_product_FLP.c \
|
||||
silk/float/k2a_FLP.c \
|
||||
silk/float/LPC_inv_pred_gain_FLP.c \
|
||||
silk/float/pitch_analysis_core_FLP.c \
|
||||
silk/float/scale_copy_vector_FLP.c \
|
||||
silk/float/scale_vector_FLP.c \
|
||||
silk/float/schur_FLP.c \
|
||||
silk/float/sort_FLP.c \
|
||||
src/opus.c \
|
||||
src/opus_decoder.c \
|
||||
src/opus_encoder.c \
|
||||
src/extensions.c \
|
||||
src/opus_multistream.c \
|
||||
src/opus_multistream_encoder.c \
|
||||
src/opus_multistream_decoder.c \
|
||||
src/repacketizer.c \
|
||||
src/opus_projection_encoder.c \
|
||||
src/opus_projection_decoder.c \
|
||||
src/mapping_matrix.c \
|
||||
src/analysis.c \
|
||||
src/mlp.c \
|
||||
src/mlp_data.c
|
||||
|
||||
libopus_internal_a_CFLAGS = \
|
||||
-DOPUS_BUILD \
|
||||
-DVAR_ARRAYS \
|
||||
-DUSE_ALLOCA \
|
||||
-g \
|
||||
-I$(srcdir)/include \
|
||||
-I$(srcdir)/celt \
|
||||
-I$(srcdir)/silk \
|
||||
-I$(srcdir)/silk/float \
|
||||
-I$(srcdir)/silk/fixed \
|
||||
-I$(srcdir)/src \
|
||||
-I$(srcdir)
|
||||
@ -0,0 +1,188 @@
|
||||
/*Copyright (c) 2003-2004, Mark Borgerding
|
||||
|
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
* Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above copyright notice, |
||||
this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
POSSIBILITY OF SUCH DAMAGE.*/ |
||||
|
||||
#ifndef KISS_FFT_GUTS_H |
||||
#define KISS_FFT_GUTS_H |
||||
|
||||
#define MIN(a,b) ((a)<(b) ? (a):(b)) |
||||
#define MAX(a,b) ((a)>(b) ? (a):(b)) |
||||
|
||||
/* kiss_fft.h
|
||||
defines kiss_fft_scalar as either short or a float type |
||||
and defines |
||||
typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */ |
||||
#include "kiss_fft.h" |
||||
|
||||
/*
|
||||
Explanation of macros dealing with complex math: |
||||
|
||||
C_MUL(m,a,b) : m = a*b |
||||
C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise |
||||
C_SUB( res, a,b) : res = a - b |
||||
C_SUBFROM( res , a) : res -= a |
||||
C_ADDTO( res , a) : res += a |
||||
* */ |
||||
#ifdef FIXED_POINT |
||||
#include "arch.h" |
||||
|
||||
|
||||
#define SAMP_MAX 2147483647 |
||||
#define TWID_MAX 32767 |
||||
#define TRIG_UPSCALE 1 |
||||
|
||||
#define SAMP_MIN -SAMP_MAX |
||||
|
||||
#ifdef ENABLE_QEXT |
||||
# define S_MUL(a,b) MULT32_32_P31_ovflw(b, a) |
||||
# define S_MUL2(a,b) MULT32_32_P31_ovflw(b, a) |
||||
#else |
||||
# define S_MUL(a,b) MULT16_32_Q15(b, a) |
||||
# define S_MUL2(a,b) MULT16_32_Q16(b, a) |
||||
#endif |
||||
|
||||
# define C_MUL(m,a,b) \ |
||||
do{ (m).r = SUB32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \
|
||||
(m).i = ADD32_ovflw(S_MUL((a).r,(b).i) , S_MUL((a).i,(b).r)); }while(0) |
||||
|
||||
# define C_MULC(m,a,b) \ |
||||
do{ (m).r = ADD32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \
|
||||
(m).i = SUB32_ovflw(S_MUL((a).i,(b).r) , S_MUL((a).r,(b).i)); }while(0) |
||||
|
||||
# define C_MULBYSCALAR( c, s ) \ |
||||
do{ (c).r = S_MUL( (c).r , s ) ;\
|
||||
(c).i = S_MUL( (c).i , s ) ; }while(0) |
||||
|
||||
# define DIVSCALAR(x,k) \ |
||||
(x) = S_MUL( x, (TWID_MAX-((k)>>1))/(k)+1 ) |
||||
|
||||
# define C_FIXDIV(c,div) \ |
||||
do { DIVSCALAR( (c).r , div); \
|
||||
DIVSCALAR( (c).i , div); }while (0) |
||||
|
||||
#define C_ADD( res, a,b)\ |
||||
do {(res).r=ADD32_ovflw((a).r,(b).r); (res).i=ADD32_ovflw((a).i,(b).i); \
|
||||
}while(0) |
||||
#define C_SUB( res, a,b)\ |
||||
do {(res).r=SUB32_ovflw((a).r,(b).r); (res).i=SUB32_ovflw((a).i,(b).i); \
|
||||
}while(0) |
||||
#define C_ADDTO( res , a)\ |
||||
do {(res).r = ADD32_ovflw((res).r, (a).r); (res).i = ADD32_ovflw((res).i,(a).i);\
|
||||
}while(0) |
||||
|
||||
#define C_SUBFROM( res , a)\ |
||||
do {(res).r = ADD32_ovflw((res).r,(a).r); (res).i = SUB32_ovflw((res).i,(a).i); \
|
||||
}while(0) |
||||
|
||||
#if defined(OPUS_ARM_INLINE_ASM) |
||||
#include "arm/kiss_fft_armv4.h" |
||||
#endif |
||||
|
||||
#if defined(OPUS_ARM_INLINE_EDSP) |
||||
#include "arm/kiss_fft_armv5e.h" |
||||
#endif |
||||
#if defined(__mips) |
||||
#include "mips/kiss_fft_mipsr1.h" |
||||
#endif |
||||
|
||||
#else /* not FIXED_POINT*/ |
||||
|
||||
# define S_MUL(a,b) ( (a)*(b) ) |
||||
# define S_MUL2(a,b) ( (a)*(b) ) |
||||
#define C_MUL(m,a,b) \ |
||||
do{ (m).r = (a).r*(b).r - (a).i*(b).i;\
|
||||
(m).i = (a).r*(b).i + (a).i*(b).r; }while(0) |
||||
#define C_MULC(m,a,b) \ |
||||
do{ (m).r = (a).r*(b).r + (a).i*(b).i;\
|
||||
(m).i = (a).i*(b).r - (a).r*(b).i; }while(0) |
||||
|
||||
#define C_MUL4(m,a,b) C_MUL(m,a,b) |
||||
|
||||
# define C_FIXDIV(c,div) /* NOOP */ |
||||
# define C_MULBYSCALAR( c, s ) \ |
||||
do{ (c).r *= (s);\
|
||||
(c).i *= (s); }while(0) |
||||
#endif |
||||
|
||||
#ifndef CHECK_OVERFLOW_OP |
||||
# define CHECK_OVERFLOW_OP(a,op,b) /* noop */ |
||||
#endif |
||||
|
||||
#ifndef C_ADD |
||||
#define C_ADD( res, a,b)\ |
||||
do { \
|
||||
CHECK_OVERFLOW_OP((a).r,+,(b).r)\
|
||||
CHECK_OVERFLOW_OP((a).i,+,(b).i)\
|
||||
(res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \
|
||||
}while(0) |
||||
#define C_SUB( res, a,b)\ |
||||
do { \
|
||||
CHECK_OVERFLOW_OP((a).r,-,(b).r)\
|
||||
CHECK_OVERFLOW_OP((a).i,-,(b).i)\
|
||||
(res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \
|
||||
}while(0) |
||||
#define C_ADDTO( res , a)\ |
||||
do { \
|
||||
CHECK_OVERFLOW_OP((res).r,+,(a).r)\
|
||||
CHECK_OVERFLOW_OP((res).i,+,(a).i)\
|
||||
(res).r += (a).r; (res).i += (a).i;\
|
||||
}while(0) |
||||
|
||||
#define C_SUBFROM( res , a)\ |
||||
do {\
|
||||
CHECK_OVERFLOW_OP((res).r,-,(a).r)\
|
||||
CHECK_OVERFLOW_OP((res).i,-,(a).i)\
|
||||
(res).r -= (a).r; (res).i -= (a).i; \
|
||||
}while(0) |
||||
#endif /* C_ADD defined */ |
||||
|
||||
#ifdef FIXED_POINT |
||||
/*# define KISS_FFT_COS(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * cos (phase))))
|
||||
# define KISS_FFT_SIN(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * sin (phase))))*/ |
||||
# define KISS_FFT_COS(phase) floor(.5+TWID_MAX*cos (phase)) |
||||
# define KISS_FFT_SIN(phase) floor(.5+TWID_MAX*sin (phase)) |
||||
# define HALF_OF(x) ((x)>>1) |
||||
#elif defined(USE_SIMD) |
||||
# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) |
||||
# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) |
||||
# define HALF_OF(x) ((x)*_mm_set1_ps(.5f)) |
||||
#else |
||||
# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) |
||||
# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) |
||||
# define HALF_OF(x) ((x)*.5f) |
||||
#endif |
||||
|
||||
#define kf_cexp(x,phase) \ |
||||
do{ \
|
||||
(x)->r = KISS_FFT_COS(phase);\
|
||||
(x)->i = KISS_FFT_SIN(phase);\
|
||||
}while(0) |
||||
|
||||
#define kf_cexp2(x,phase) \ |
||||
do{ \
|
||||
(x)->r = TRIG_UPSCALE*celt_cos_norm((phase));\
|
||||
(x)->i = TRIG_UPSCALE*celt_cos_norm((phase)-32768);\
|
||||
}while(0) |
||||
|
||||
#endif /* KISS_FFT_GUTS_H */ |
||||
@ -0,0 +1,400 @@
|
||||
/* Copyright (c) 2003-2008 Jean-Marc Valin
|
||||
Copyright (c) 2007-2008 CSIRO |
||||
Copyright (c) 2007-2009 Xiph.Org Foundation |
||||
Written by Jean-Marc Valin */ |
||||
/**
|
||||
@file arch.h |
||||
@brief Various architecture definitions for CELT |
||||
*/ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef ARCH_H |
||||
#define ARCH_H |
||||
|
||||
#include "opus_types.h" |
||||
#include "opus_defines.h" |
||||
|
||||
# if !defined(__GNUC_PREREQ) |
||||
# if defined(__GNUC__)&&defined(__GNUC_MINOR__) |
||||
# define __GNUC_PREREQ(_maj,_min) \ |
||||
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min)) |
||||
# else |
||||
# define __GNUC_PREREQ(_maj,_min) 0 |
||||
# endif |
||||
# endif |
||||
|
||||
#if OPUS_GNUC_PREREQ(3, 0) |
||||
#define opus_likely(x) (__builtin_expect(!!(x), 1)) |
||||
#define opus_unlikely(x) (__builtin_expect(!!(x), 0)) |
||||
#else |
||||
#define opus_likely(x) (!!(x)) |
||||
#define opus_unlikely(x) (!!(x)) |
||||
#endif |
||||
|
||||
#define CELT_SIG_SCALE 32768.f |
||||
|
||||
#define CELT_FATAL(str) celt_fatal(str, __FILE__, __LINE__) |
||||
|
||||
#if defined(ENABLE_ASSERTIONS) || defined(ENABLE_HARDENING) |
||||
#ifdef __GNUC__ |
||||
__attribute__((noreturn)) |
||||
#endif |
||||
void celt_fatal(const char *str, const char *file, int line); |
||||
|
||||
#if defined(CELT_C) && !defined(OVERRIDE_celt_fatal) |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#ifdef __GNUC__ |
||||
__attribute__((noreturn)) |
||||
#endif |
||||
void celt_fatal(const char *str, const char *file, int line) |
||||
{ |
||||
fprintf (stderr, "Fatal (internal) error in %s, line %d: %s\n", file, line, str); |
||||
#if defined(_MSC_VER) |
||||
_set_abort_behavior( 0, _WRITE_ABORT_MSG); |
||||
#endif |
||||
abort(); |
||||
} |
||||
#endif |
||||
|
||||
#define celt_assert(cond) {if (!(cond)) {CELT_FATAL("assertion failed: " #cond);}} |
||||
#define celt_assert2(cond, message) {if (!(cond)) {CELT_FATAL("assertion failed: " #cond "\n" message);}} |
||||
#define MUST_SUCCEED(call) celt_assert((call) == OPUS_OK) |
||||
#else |
||||
#define celt_assert(cond) ((void)(cond)) |
||||
#define celt_assert2(cond, message) ((void)(cond)) |
||||
#define MUST_SUCCEED(call) do {if((call) != OPUS_OK) {RESTORE_STACK; return OPUS_INTERNAL_ERROR;} } while (0) |
||||
#endif |
||||
|
||||
#if defined(ENABLE_ASSERTIONS) |
||||
#define celt_sig_assert(cond) {if (!(cond)) {CELT_FATAL("signal assertion failed: " #cond);}} |
||||
#else |
||||
#define celt_sig_assert(cond) ((void)(cond)) |
||||
#endif |
||||
|
||||
#define IMUL32(a,b) ((a)*(b)) |
||||
|
||||
#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 16-bit value. */ |
||||
#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */ |
||||
#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 32-bit value. */ |
||||
#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */ |
||||
#define IMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum int value. */ |
||||
#define IMAX(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum int value. */ |
||||
#define FMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum float value. */ |
||||
#define FMAX(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum float value. */ |
||||
#define UADD32(a,b) ((a)+(b)) |
||||
#define USUB32(a,b) ((a)-(b)) |
||||
#define MAXG(a,b) MAX32(a, b) |
||||
#define MING(a,b) MIN32(a, b) |
||||
|
||||
/* Throughout the code, we use the following scaling for signals:
|
||||
FLOAT: used for float API, normalized to +/-1. |
||||
INT16: used for 16-bit API, normalized to +/- 32768 |
||||
RES: internal Opus resolution, defined as +/-1. in float builds, or either 16-bit or 24-bit int for fixed-point builds |
||||
SIG: internal CELT resolution: defined as +/- 32768. in float builds, or Q27 in fixed-point builds (int16 shifted by 12) |
||||
*/ |
||||
|
||||
|
||||
/* Set this if opus_int64 is a native type of the CPU. */ |
||||
/* Assume that all LP64 architectures have fast 64-bit types; also x86_64
|
||||
(which can be ILP32 for x32) and Win64 (which is LLP64). */ |
||||
#if defined(__x86_64__) || defined(__LP64__) || defined(_WIN64) || defined (__mips) |
||||
#define OPUS_FAST_INT64 1 |
||||
#else |
||||
#define OPUS_FAST_INT64 0 |
||||
#endif |
||||
|
||||
#ifdef FIXED_POINT |
||||
#define ARG_FIXED(arg) , arg |
||||
#else |
||||
#define ARG_FIXED(arg) |
||||
#endif |
||||
|
||||
#define PRINT_MIPS(file) |
||||
|
||||
#ifdef FIXED_POINT |
||||
|
||||
typedef opus_int16 opus_val16; |
||||
typedef opus_int32 opus_val32; |
||||
typedef opus_int64 opus_val64; |
||||
|
||||
typedef opus_val32 celt_sig; |
||||
typedef opus_val32 celt_norm; |
||||
typedef opus_val32 celt_ener; |
||||
typedef opus_val32 celt_glog; |
||||
|
||||
#ifdef ENABLE_RES24 |
||||
typedef opus_val32 opus_res; |
||||
#define RES_SHIFT 8 |
||||
#define SIG2RES(a) PSHR32(a, SIG_SHIFT-RES_SHIFT) |
||||
#define RES2INT16(a) SAT16(PSHR32(a, RES_SHIFT)) |
||||
#define RES2INT24(a) (a) |
||||
#define RES2FLOAT(a) ((1.f/32768.f/256.f)*(a)) |
||||
#define INT16TORES(a) SHL32(EXTEND32(a), RES_SHIFT) |
||||
#define INT24TORES(a) (a) |
||||
#define ADD_RES(a, b) ADD32(a, b) |
||||
#define FLOAT2RES(a) FLOAT2INT24(a) |
||||
#define RES2SIG(a) SHL32((a), SIG_SHIFT-RES_SHIFT) |
||||
#define MULT16_RES_Q15(a,b) MULT16_32_Q15(a,b) |
||||
#define MAX_ENCODING_DEPTH 24 |
||||
#else |
||||
typedef opus_val16 opus_res; |
||||
#define RES_SHIFT 0 |
||||
#define SIG2RES(a) SIG2WORD16(a) |
||||
#define RES2INT16(a) (a) |
||||
#define RES2INT24(a) SHL32(EXTEND32(a), 8) |
||||
#define RES2FLOAT(a) ((1.f/32768.f)*(a)) |
||||
#define INT16TORES(a) (a) |
||||
#define INT24TORES(a) SAT16(PSHR32(a, 8)) |
||||
#define ADD_RES(a, b) SAT16(ADD32((a), (b))); |
||||
#define FLOAT2RES(a) FLOAT2INT16(a) |
||||
#define RES2SIG(a) SHL32(EXTEND32(a), SIG_SHIFT) |
||||
#define MULT16_RES_Q15(a,b) MULT16_16_Q15(a,b) |
||||
#define MAX_ENCODING_DEPTH 16 |
||||
#endif |
||||
|
||||
#define RES2VAL16(a) RES2INT16(a) |
||||
#define INT16TOSIG(a) SHL32(EXTEND32(a), SIG_SHIFT) |
||||
#define INT24TOSIG(a) SHL32(a, SIG_SHIFT-8) |
||||
|
||||
#define NORM_SHIFT 24 |
||||
#ifdef ENABLE_QEXT |
||||
typedef opus_val32 celt_coef; |
||||
#define COEF_ONE Q31ONE |
||||
#define MULT_COEF_32(a, b) MULT32_32_P31(a,b) |
||||
#define MAC_COEF_32_ARM(c, a, b) ADD32((c), MULT32_32_Q32(a,b)) |
||||
#define MULT_COEF(a, b) MULT32_32_Q31(a,b) |
||||
#define MULT_COEF_TAPS(a, b) SHL32(MULT16_16(a,b), 1) |
||||
#define COEF2VAL16(x) EXTRACT16(SHR32(x, 16)) |
||||
#else |
||||
typedef opus_val16 celt_coef; |
||||
#define COEF_ONE Q15ONE |
||||
#define MULT_COEF_32(a, b) MULT16_32_Q15(a,b) |
||||
#define MAC_COEF_32_ARM(a, b, c) MAC16_32_Q16(a,b,c) |
||||
#define MULT_COEF(a, b) MULT16_16_Q15(a,b) |
||||
#define MULT_COEF_TAPS(a, b) MULT16_16_P15(a,b) |
||||
#define COEF2VAL16(x) (x) |
||||
#endif |
||||
|
||||
#define celt_isnan(x) 0 |
||||
|
||||
#define Q15ONE 32767 |
||||
#define Q31ONE 2147483647 |
||||
|
||||
#define SIG_SHIFT 12 |
||||
/* Safe saturation value for 32-bit signals. We need to make sure that we can
|
||||
add two sig values and that the first stages of the MDCT don't cause an overflow. |
||||
The most constraining is the ARM_ASM comb filter where we shift left by one |
||||
and then add two values. Because of that, we use 2^29-1. SIG_SAT must be large |
||||
enough to fit a full-scale high-freq tone through the prefilter and comb filter, |
||||
meaning 1.85*1.75*2^(15+SIG_SHIFT) = 434529895. |
||||
so the limit should be about 2^31*sqrt(.5). */ |
||||
#define SIG_SAT (536870911) |
||||
|
||||
#define NORM_SCALING (1<<NORM_SHIFT) |
||||
|
||||
#define DB_SHIFT 24 |
||||
|
||||
#define EPSILON 1 |
||||
#define VERY_SMALL 0 |
||||
#define VERY_LARGE16 ((opus_val16)32767) |
||||
#define Q15_ONE ((opus_val16)32767) |
||||
|
||||
|
||||
#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) |
||||
#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) |
||||
|
||||
static OPUS_INLINE opus_int16 SAT16(opus_int32 x) { |
||||
return x > 32767 ? 32767 : x < -32768 ? -32768 : (opus_int16)x; |
||||
} |
||||
|
||||
#ifdef FIXED_DEBUG |
||||
#include "fixed_debug.h" |
||||
#else |
||||
|
||||
#include "fixed_generic.h" |
||||
|
||||
#ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR |
||||
#include "arm/fixed_arm64.h" |
||||
#elif defined (OPUS_ARM_INLINE_EDSP) |
||||
#include "arm/fixed_armv5e.h" |
||||
#elif defined (OPUS_ARM_INLINE_ASM) |
||||
#include "arm/fixed_armv4.h" |
||||
#elif defined (BFIN_ASM) |
||||
#include "fixed_bfin.h" |
||||
#elif defined (TI_C5X_ASM) |
||||
#include "fixed_c5x.h" |
||||
#elif defined (TI_C6X_ASM) |
||||
#include "fixed_c6x.h" |
||||
#endif |
||||
|
||||
#endif |
||||
|
||||
#else /* FIXED_POINT */ |
||||
|
||||
typedef float opus_val16; |
||||
typedef float opus_val32; |
||||
typedef float opus_val64; |
||||
|
||||
typedef float celt_sig; |
||||
typedef float celt_norm; |
||||
typedef float celt_ener; |
||||
typedef float celt_glog; |
||||
|
||||
typedef float opus_res; |
||||
typedef float celt_coef; |
||||
|
||||
#ifdef FLOAT_APPROX |
||||
/* This code should reliably detect NaN/inf even when -ffast-math is used.
|
||||
Assumes IEEE 754 format. */ |
||||
static OPUS_INLINE int celt_isnan(float x) |
||||
{ |
||||
union {float f; opus_uint32 i;} in; |
||||
in.f = x; |
||||
return ((in.i>>23)&0xFF)==0xFF && (in.i&0x007FFFFF)!=0; |
||||
} |
||||
#else |
||||
#ifdef __FAST_MATH__ |
||||
#error Cannot build libopus with -ffast-math unless FLOAT_APPROX is defined. This could result in crashes on extreme (e.g. NaN) input |
||||
#endif |
||||
#define celt_isnan(x) ((x)!=(x)) |
||||
#endif |
||||
|
||||
#define Q15ONE 1.0f |
||||
#define Q31ONE 1.0f |
||||
#define COEF_ONE 1.0f |
||||
#define COEF2VAL16(x) (x) |
||||
|
||||
#define NORM_SCALING 1.f |
||||
|
||||
#define EPSILON 1e-15f |
||||
#define VERY_SMALL 1e-30f |
||||
#define VERY_LARGE16 1e15f |
||||
#define Q15_ONE ((opus_val16)1.f) |
||||
|
||||
/* This appears to be the same speed as C99's fabsf() but it's more portable. */ |
||||
#define ABS16(x) ((float)fabs(x)) |
||||
#define ABS32(x) ((float)fabs(x)) |
||||
|
||||
#define QCONST16(x,bits) (x) |
||||
#define QCONST32(x,bits) (x) |
||||
#define GCONST(x) (x) |
||||
|
||||
#define NEG16(x) (-(x)) |
||||
#define NEG32(x) (-(x)) |
||||
#define NEG32_ovflw(x) (-(x)) |
||||
#define EXTRACT16(x) (x) |
||||
#define EXTEND32(x) (x) |
||||
#define SHR16(a,shift) (a) |
||||
#define SHL16(a,shift) (a) |
||||
#define SHR32(a,shift) (a) |
||||
#define SHL32(a,shift) (a) |
||||
#define PSHR32(a,shift) (a) |
||||
#define VSHR32(a,shift) (a) |
||||
|
||||
#define SHR64(a,shift) (a) |
||||
|
||||
#define PSHR(a,shift) (a) |
||||
#define SHR(a,shift) (a) |
||||
#define SHL(a,shift) (a) |
||||
#define SATURATE(x,a) (x) |
||||
#define SATURATE16(x) (x) |
||||
|
||||
#define ROUND16(a,shift) (a) |
||||
#define SROUND16(a,shift) (a) |
||||
#define HALF16(x) (.5f*(x)) |
||||
#define HALF32(x) (.5f*(x)) |
||||
|
||||
#define ADD16(a,b) ((a)+(b)) |
||||
#define SUB16(a,b) ((a)-(b)) |
||||
#define ADD32(a,b) ((a)+(b)) |
||||
#define SUB32(a,b) ((a)-(b)) |
||||
#define ADD32_ovflw(a,b) ((a)+(b)) |
||||
#define SUB32_ovflw(a,b) ((a)-(b)) |
||||
#define SHL32_ovflw(a,shift) (a) |
||||
#define PSHR32_ovflw(a,shift) (a) |
||||
|
||||
#define MULT16_16_16(a,b) ((a)*(b)) |
||||
#define MULT16_16(a,b) ((opus_val32)(a)*(opus_val32)(b)) |
||||
#define MAC16_16(c,a,b) ((c)+(opus_val32)(a)*(opus_val32)(b)) |
||||
|
||||
#define MULT16_32_Q15(a,b) ((a)*(b)) |
||||
#define MULT16_32_Q16(a,b) ((a)*(b)) |
||||
|
||||
#define MULT32_32_Q16(a,b) ((a)*(b)) |
||||
#define MULT32_32_Q31(a,b) ((a)*(b)) |
||||
#define MULT32_32_P31(a,b) ((a)*(b)) |
||||
#define MULT32_32_P31_ovflw(a,b) ((a)*(b)) |
||||
|
||||
#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b)) |
||||
#define MAC16_32_Q16(c,a,b) ((c)+(a)*(b)) |
||||
#define MAC_COEF_32_ARM(c,a,b) ((c)+(a)*(b)) |
||||
|
||||
#define MULT16_16_Q11_32(a,b) ((a)*(b)) |
||||
#define MULT16_16_Q11(a,b) ((a)*(b)) |
||||
#define MULT16_16_Q13(a,b) ((a)*(b)) |
||||
#define MULT16_16_Q14(a,b) ((a)*(b)) |
||||
#define MULT16_16_Q15(a,b) ((a)*(b)) |
||||
#define MULT16_16_P15(a,b) ((a)*(b)) |
||||
#define MULT16_16_P13(a,b) ((a)*(b)) |
||||
#define MULT16_16_P14(a,b) ((a)*(b)) |
||||
#define MULT16_32_P16(a,b) ((a)*(b)) |
||||
|
||||
#define MULT_COEF_32(a, b) ((a)*(b)) |
||||
#define MULT_COEF(a, b) ((a)*(b)) |
||||
#define MULT_COEF_TAPS(a, b) ((a)*(b)) |
||||
|
||||
#define DIV32_16(a,b) (((opus_val32)(a))/(opus_val16)(b)) |
||||
#define DIV32(a,b) (((opus_val32)(a))/(opus_val32)(b)) |
||||
|
||||
#define SIG2RES(a) ((1/CELT_SIG_SCALE)*(a)) |
||||
#define RES2INT16(a) FLOAT2INT16(a) |
||||
#define RES2INT24(a) float2int(32768.f*256.f*(a)) |
||||
#define RES2FLOAT(a) (a) |
||||
#define INT16TORES(a) ((a)*(1/CELT_SIG_SCALE)) |
||||
#define INT24TORES(a) ((1.f/32768.f/256.f)*(a)) |
||||
#define ADD_RES(a, b) ADD32(a, b) |
||||
#define FLOAT2RES(a) (a) |
||||
#define RES2SIG(a) (CELT_SIG_SCALE*(a)) |
||||
#define MULT16_RES_Q15(a,b) MULT16_16_Q15(a,b) |
||||
|
||||
#define RES2VAL16(a) (a) |
||||
#define FLOAT2SIG(a) ((a)*CELT_SIG_SCALE) |
||||
#define INT16TOSIG(a) ((float)(a)) |
||||
#define INT24TOSIG(a) ((float)(a)*(1.f/256.f)) |
||||
#define MAX_ENCODING_DEPTH 24 |
||||
|
||||
#endif /* !FIXED_POINT */ |
||||
|
||||
#ifndef GLOBAL_STACK_SIZE |
||||
#ifdef FIXED_POINT |
||||
#define GLOBAL_STACK_SIZE 120000 |
||||
#else |
||||
#define GLOBAL_STACK_SIZE 120000 |
||||
#endif |
||||
#endif |
||||
|
||||
#endif /* ARCH_H */ |
||||
@ -0,0 +1,125 @@
|
||||
/* Copyright (c) 2007-2008 CSIRO
|
||||
Copyright (c) 2007-2009 Xiph.Org Foundation |
||||
Copyright (c) 2008-2009 Gregory Maxwell |
||||
Written by Jean-Marc Valin and Gregory Maxwell */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef BANDS_H |
||||
#define BANDS_H |
||||
|
||||
#include "arch.h" |
||||
#include "modes.h" |
||||
#include "entenc.h" |
||||
#include "entdec.h" |
||||
#include "rate.h" |
||||
|
||||
opus_int16 bitexact_cos(opus_int16 x); |
||||
int bitexact_log2tan(int isin,int icos); |
||||
|
||||
/** Compute the amplitude (sqrt energy) in each of the bands
|
||||
* @param m Mode data |
||||
* @param X Spectrum |
||||
* @param bandE Square root of the energy for each band (returned) |
||||
*/ |
||||
void compute_band_energies(const CELTMode *m, const celt_sig *X, celt_ener *bandE, int end, int C, int LM, int arch); |
||||
|
||||
/*void compute_noise_energies(const CELTMode *m, const celt_sig *X, const opus_val16 *tonality, celt_ener *bandE);*/ |
||||
|
||||
/** Normalise each band of X such that the energy in each band is
|
||||
equal to 1 |
||||
* @param m Mode data |
||||
* @param X Spectrum (returned normalised) |
||||
* @param bandE Square root of the energy for each band |
||||
*/ |
||||
void normalise_bands(const CELTMode *m, const celt_sig * OPUS_RESTRICT freq, celt_norm * OPUS_RESTRICT X, const celt_ener *bandE, int end, int C, int M); |
||||
|
||||
/** Denormalise each band of X to restore full amplitude
|
||||
* @param m Mode data |
||||
* @param X Spectrum (returned de-normalised) |
||||
* @param bandE Square root of the energy for each band |
||||
*/ |
||||
void denormalise_bands(const CELTMode *m, const celt_norm * OPUS_RESTRICT X, |
||||
celt_sig * OPUS_RESTRICT freq, const celt_glog *bandE, int start, |
||||
int end, int M, int downsample, int silence); |
||||
|
||||
#define SPREAD_NONE (0) |
||||
#define SPREAD_LIGHT (1) |
||||
#define SPREAD_NORMAL (2) |
||||
#define SPREAD_AGGRESSIVE (3) |
||||
|
||||
int spreading_decision(const CELTMode *m, const celt_norm *X, int *average, |
||||
int last_decision, int *hf_average, int *tapset_decision, int update_hf, |
||||
int end, int C, int M, const int *spread_weight); |
||||
|
||||
#ifdef MEASURE_NORM_MSE |
||||
void measure_norm_mse(const CELTMode *m, float *X, float *X0, float *bandE, float *bandE0, int M, int N, int C); |
||||
#endif |
||||
|
||||
void haar1(celt_norm *X, int N0, int stride); |
||||
|
||||
/** Quantisation/encoding of the residual spectrum
|
||||
* @param encode flag that indicates whether we're encoding (1) or decoding (0) |
||||
* @param m Mode data |
||||
* @param start First band to process |
||||
* @param end Last band to process + 1 |
||||
* @param X Residual (normalised) |
||||
* @param Y Residual (normalised) for second channel (or NULL for mono) |
||||
* @param collapse_masks Anti-collapse tracking mask |
||||
* @param bandE Square root of the energy for each band |
||||
* @param pulses Bit allocation (per band) for PVQ |
||||
* @param shortBlocks Zero for long blocks, non-zero for short blocks |
||||
* @param spread Amount of spreading to use |
||||
* @param dual_stereo Zero for MS stereo, non-zero for dual stereo |
||||
* @param intensity First band to use intensity stereo |
||||
* @param tf_res Time-frequency resolution change |
||||
* @param total_bits Total number of bits that can be used for the frame (including the ones already spent) |
||||
* @param balance Number of unallocated bits |
||||
* @param en Entropy coder state |
||||
* @param LM log2() of the number of 2.5 subframes in the frame |
||||
* @param codedBands Last band to receive bits + 1 |
||||
* @param seed Random generator seed |
||||
* @param arch Run-time architecture (see opus_select_arch()) |
||||
*/ |
||||
void quant_all_bands(int encode, const CELTMode *m, int start, int end, |
||||
celt_norm * X, celt_norm * Y, unsigned char *collapse_masks, |
||||
const celt_ener *bandE, int *pulses, int shortBlocks, int spread, |
||||
int dual_stereo, int intensity, int *tf_res, opus_int32 total_bits, |
||||
opus_int32 balance, ec_ctx *ec, int M, int codedBands, opus_uint32 *seed, |
||||
int complexity, int arch, int disable_inv |
||||
ARG_QEXT(ec_ctx *ext_ec) ARG_QEXT(int *extra_pulses) |
||||
ARG_QEXT(opus_int32 total_ext_bits) ARG_QEXT(const int *cap)); |
||||
|
||||
void anti_collapse(const CELTMode *m, celt_norm *X_, |
||||
unsigned char *collapse_masks, int LM, int C, int size, int start, |
||||
int end, const celt_glog *logE, const celt_glog *prev1logE, |
||||
const celt_glog *prev2logE, const int *pulses, opus_uint32 seed, |
||||
int encode, int arch); |
||||
|
||||
opus_uint32 celt_lcg_rand(opus_uint32 seed); |
||||
|
||||
int hysteresis_decision(opus_val16 val, const opus_val16 *thresholds, const opus_val16 *hysteresis, int N, int prev); |
||||
|
||||
#endif /* BANDS_H */ |
||||
@ -0,0 +1,373 @@
|
||||
/* Copyright (c) 2007-2008 CSIRO
|
||||
Copyright (c) 2007-2010 Xiph.Org Foundation |
||||
Copyright (c) 2008 Gregory Maxwell |
||||
Written by Jean-Marc Valin and Gregory Maxwell */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifdef HAVE_CONFIG_H |
||||
#include "config.h" |
||||
#endif |
||||
|
||||
#define CELT_C |
||||
|
||||
#include "os_support.h" |
||||
#include "mdct.h" |
||||
#include <math.h> |
||||
#include "celt.h" |
||||
#include "pitch.h" |
||||
#include "bands.h" |
||||
#include "modes.h" |
||||
#include "entcode.h" |
||||
#include "quant_bands.h" |
||||
#include "rate.h" |
||||
#include "stack_alloc.h" |
||||
#include "mathops.h" |
||||
#include "float_cast.h" |
||||
#include <stdarg.h> |
||||
#include "celt_lpc.h" |
||||
#include "vq.h" |
||||
|
||||
#ifndef PACKAGE_VERSION |
||||
#define PACKAGE_VERSION "unknown" |
||||
#endif |
||||
|
||||
#if defined(FIXED_POINT) && defined(__mips) |
||||
#include "mips/celt_mipsr1.h" |
||||
#endif |
||||
|
||||
|
||||
int resampling_factor(opus_int32 rate) |
||||
{ |
||||
int ret; |
||||
switch (rate) |
||||
{ |
||||
#ifdef ENABLE_QEXT |
||||
case 96000: |
||||
#endif |
||||
case 48000: |
||||
ret = 1; |
||||
break; |
||||
case 24000: |
||||
ret = 2; |
||||
break; |
||||
case 16000: |
||||
ret = 3; |
||||
break; |
||||
case 12000: |
||||
ret = 4; |
||||
break; |
||||
case 8000: |
||||
ret = 6; |
||||
break; |
||||
default: |
||||
#ifndef CUSTOM_MODES |
||||
celt_assert(0); |
||||
#endif |
||||
ret = 0; |
||||
break; |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
|
||||
#if !defined(OVERRIDE_COMB_FILTER_CONST) || defined(NON_STATIC_COMB_FILTER_CONST_C) |
||||
/* This version should be faster on ARM */ |
||||
#ifdef OPUS_ARM_ASM |
||||
#ifndef NON_STATIC_COMB_FILTER_CONST_C |
||||
static |
||||
#endif |
||||
void comb_filter_const_c(opus_val32 *y, opus_val32 *x, int T, int N, |
||||
celt_coef g10, celt_coef g11, celt_coef g12) |
||||
{ |
||||
opus_val32 x0, x1, x2, x3, x4; |
||||
int i; |
||||
x4 = SHL32(x[-T-2], 1); |
||||
x3 = SHL32(x[-T-1], 1); |
||||
x2 = SHL32(x[-T], 1); |
||||
x1 = SHL32(x[-T+1], 1); |
||||
for (i=0;i<N-4;i+=5) |
||||
{ |
||||
opus_val32 t; |
||||
x0=SHL32(x[i-T+2],1); |
||||
t = MAC_COEF_32_ARM(x[i], g10, x2); |
||||
t = MAC_COEF_32_ARM(t, g11, ADD32(x1,x3)); |
||||
t = MAC_COEF_32_ARM(t, g12, ADD32(x0,x4)); |
||||
t = SATURATE(t, SIG_SAT); |
||||
y[i] = t; |
||||
x4=SHL32(x[i-T+3],1); |
||||
t = MAC_COEF_32_ARM(x[i+1], g10, x1); |
||||
t = MAC_COEF_32_ARM(t, g11, ADD32(x0,x2)); |
||||
t = MAC_COEF_32_ARM(t, g12, ADD32(x4,x3)); |
||||
t = SATURATE(t, SIG_SAT); |
||||
y[i+1] = t; |
||||
x3=SHL32(x[i-T+4],1); |
||||
t = MAC_COEF_32_ARM(x[i+2], g10, x0); |
||||
t = MAC_COEF_32_ARM(t, g11, ADD32(x4,x1)); |
||||
t = MAC_COEF_32_ARM(t, g12, ADD32(x3,x2)); |
||||
t = SATURATE(t, SIG_SAT); |
||||
y[i+2] = t; |
||||
x2=SHL32(x[i-T+5],1); |
||||
t = MAC_COEF_32_ARM(x[i+3], g10, x4); |
||||
t = MAC_COEF_32_ARM(t, g11, ADD32(x3,x0)); |
||||
t = MAC_COEF_32_ARM(t, g12, ADD32(x2,x1)); |
||||
t = SATURATE(t, SIG_SAT); |
||||
y[i+3] = t; |
||||
x1=SHL32(x[i-T+6],1); |
||||
t = MAC_COEF_32_ARM(x[i+4], g10, x3); |
||||
t = MAC_COEF_32_ARM(t, g11, ADD32(x2,x4)); |
||||
t = MAC_COEF_32_ARM(t, g12, ADD32(x1,x0)); |
||||
t = SATURATE(t, SIG_SAT); |
||||
y[i+4] = t; |
||||
} |
||||
#ifdef CUSTOM_MODES |
||||
for (;i<N;i++) |
||||
{ |
||||
opus_val32 t; |
||||
x0=SHL32(x[i-T+2],1); |
||||
t = MAC_COEF_32_ARM(x[i], g10, x2); |
||||
t = MAC_COEF_32_ARM(t, g11, ADD32(x1,x3)); |
||||
t = MAC_COEF_32_ARM(t, g12, ADD32(x0,x4)); |
||||
t = SATURATE(t, SIG_SAT); |
||||
y[i] = t; |
||||
x4=x3; |
||||
x3=x2; |
||||
x2=x1; |
||||
x1=x0; |
||||
} |
||||
#endif |
||||
} |
||||
#else |
||||
#ifndef NON_STATIC_COMB_FILTER_CONST_C |
||||
static |
||||
#endif |
||||
void comb_filter_const_c(opus_val32 *y, opus_val32 *x, int T, int N, |
||||
celt_coef g10, celt_coef g11, celt_coef g12) |
||||
{ |
||||
opus_val32 x0, x1, x2, x3, x4; |
||||
int i; |
||||
x4 = x[-T-2]; |
||||
x3 = x[-T-1]; |
||||
x2 = x[-T]; |
||||
x1 = x[-T+1]; |
||||
for (i=0;i<N;i++) |
||||
{ |
||||
x0=x[i-T+2]; |
||||
y[i] = x[i] |
||||
+ MULT_COEF_32(g10,x2) |
||||
+ MULT_COEF_32(g11,ADD32(x1,x3)) |
||||
+ MULT_COEF_32(g12,ADD32(x0,x4)); |
||||
#ifdef FIXED_POINT |
||||
/* A bit of bias seems to help here. */ |
||||
y[i] = SUB32(y[i], 1); |
||||
#endif |
||||
y[i] = SATURATE(y[i], SIG_SAT); |
||||
x4=x3; |
||||
x3=x2; |
||||
x2=x1; |
||||
x1=x0; |
||||
} |
||||
|
||||
} |
||||
#endif |
||||
#endif |
||||
|
||||
#ifdef ENABLE_QEXT |
||||
void comb_filter_qext(opus_val32 *y, opus_val32 *x, int T0, int T1, int N, |
||||
opus_val16 g0, opus_val16 g1, int tapset0, int tapset1, |
||||
const celt_coef *window, int overlap, int arch) |
||||
{ |
||||
VARDECL(opus_val32, mem_buf); |
||||
VARDECL(opus_val32, buf); |
||||
celt_coef new_window[120]; |
||||
int s; |
||||
int i; |
||||
int N2; |
||||
int overlap2; |
||||
SAVE_STACK; |
||||
/* Using ALLOC() instead of a regular stack allocation to minimize real stack use when using the pseudostack.
|
||||
This is useful on some embedded systems. */ |
||||
ALLOC(mem_buf, COMBFILTER_MAXPERIOD+960, opus_val32); |
||||
ALLOC(buf, COMBFILTER_MAXPERIOD+960, opus_val32); |
||||
N2 = N/2; |
||||
overlap2=overlap/2; |
||||
/* At 96 kHz, we double the period and the spacing between taps, which is equivalent
|
||||
to creating a mirror image of the filter around 24 kHz. It also means we can process |
||||
the even and odd samples completely independently. */ |
||||
for (s=0;s<2;s++) { |
||||
opus_val32 *yptr; |
||||
for (i=0;i<overlap2;i++) new_window[i] = window[2*i+s]; |
||||
for (i=0;i<COMBFILTER_MAXPERIOD+N2;i++) mem_buf[i] = x[2*i+s-2*COMBFILTER_MAXPERIOD]; |
||||
if (x==y) { |
||||
yptr = mem_buf+COMBFILTER_MAXPERIOD; |
||||
} else { |
||||
for (i=0;i<N2;i++) buf[i] = y[2*i+s]; |
||||
yptr = buf; |
||||
} |
||||
comb_filter(yptr, mem_buf+COMBFILTER_MAXPERIOD, T0, T1, N2, g0, g1, tapset0, tapset1, new_window, overlap2, arch); |
||||
for (i=0;i<N2;i++) y[2*i+s] = yptr[i]; |
||||
} |
||||
RESTORE_STACK; |
||||
return; |
||||
} |
||||
#endif |
||||
|
||||
#ifndef OVERRIDE_comb_filter |
||||
void comb_filter(opus_val32 *y, opus_val32 *x, int T0, int T1, int N, |
||||
opus_val16 g0, opus_val16 g1, int tapset0, int tapset1, |
||||
const celt_coef *window, int overlap, int arch) |
||||
{ |
||||
int i; |
||||
/* printf ("%d %d %f %f\n", T0, T1, g0, g1); */ |
||||
celt_coef g00, g01, g02, g10, g11, g12; |
||||
opus_val32 x0, x1, x2, x3, x4; |
||||
static const opus_val16 gains[3][3] = { |
||||
{QCONST16(0.3066406250f, 15), QCONST16(0.2170410156f, 15), QCONST16(0.1296386719f, 15)}, |
||||
{QCONST16(0.4638671875f, 15), QCONST16(0.2680664062f, 15), QCONST16(0.f, 15)}, |
||||
{QCONST16(0.7998046875f, 15), QCONST16(0.1000976562f, 15), QCONST16(0.f, 15)}}; |
||||
#ifdef ENABLE_QEXT |
||||
if (overlap==240) { |
||||
comb_filter_qext(y, x, T0, T1, N, g0, g1, tapset0, tapset1, window, overlap, arch); |
||||
return; |
||||
} |
||||
#endif |
||||
if (g0==0 && g1==0) |
||||
{ |
||||
/* OPT: Happens to work without the OPUS_MOVE(), but only because the current encoder already copies x to y */ |
||||
if (x!=y) |
||||
OPUS_MOVE(y, x, N); |
||||
return; |
||||
} |
||||
/* When the gain is zero, T0 and/or T1 is set to zero. We need
|
||||
to have then be at least 2 to avoid processing garbage data. */ |
||||
T0 = IMAX(T0, COMBFILTER_MINPERIOD); |
||||
T1 = IMAX(T1, COMBFILTER_MINPERIOD); |
||||
g00 = MULT_COEF_TAPS(g0, gains[tapset0][0]); |
||||
g01 = MULT_COEF_TAPS(g0, gains[tapset0][1]); |
||||
g02 = MULT_COEF_TAPS(g0, gains[tapset0][2]); |
||||
g10 = MULT_COEF_TAPS(g1, gains[tapset1][0]); |
||||
g11 = MULT_COEF_TAPS(g1, gains[tapset1][1]); |
||||
g12 = MULT_COEF_TAPS(g1, gains[tapset1][2]); |
||||
x1 = x[-T1+1]; |
||||
x2 = x[-T1 ]; |
||||
x3 = x[-T1-1]; |
||||
x4 = x[-T1-2]; |
||||
/* If the filter didn't change, we don't need the overlap */ |
||||
if (g0==g1 && T0==T1 && tapset0==tapset1) |
||||
overlap=0; |
||||
for (i=0;i<overlap;i++) |
||||
{ |
||||
celt_coef f; |
||||
x0=x[i-T1+2]; |
||||
f = MULT_COEF(window[i],window[i]); |
||||
y[i] = x[i] |
||||
+ MULT_COEF_32(MULT_COEF((COEF_ONE-f),g00),x[i-T0]) |
||||
+ MULT_COEF_32(MULT_COEF((COEF_ONE-f),g01),ADD32(x[i-T0+1],x[i-T0-1])) |
||||
+ MULT_COEF_32(MULT_COEF((COEF_ONE-f),g02),ADD32(x[i-T0+2],x[i-T0-2])) |
||||
+ MULT_COEF_32(MULT_COEF(f,g10),x2) |
||||
+ MULT_COEF_32(MULT_COEF(f,g11),ADD32(x1,x3)) |
||||
+ MULT_COEF_32(MULT_COEF(f,g12),ADD32(x0,x4)); |
||||
#ifdef FIXED_POINT |
||||
/* A bit of bias seems to help here. */ |
||||
y[i] = SUB32(y[i], 3); |
||||
#endif |
||||
y[i] = SATURATE(y[i], SIG_SAT); |
||||
x4=x3; |
||||
x3=x2; |
||||
x2=x1; |
||||
x1=x0; |
||||
|
||||
} |
||||
if (g1==0) |
||||
{ |
||||
/* OPT: Happens to work without the OPUS_MOVE(), but only because the current encoder already copies x to y */ |
||||
if (x!=y) |
||||
OPUS_MOVE(y+overlap, x+overlap, N-overlap); |
||||
return; |
||||
} |
||||
|
||||
/* Compute the part with the constant filter. */ |
||||
comb_filter_const(y+i, x+i, T1, N-i, g10, g11, g12, arch); |
||||
} |
||||
#endif /* OVERRIDE_comb_filter */ |
||||
|
||||
/* TF change table. Positive values mean better frequency resolution (longer
|
||||
effective window), whereas negative values mean better time resolution |
||||
(shorter effective window). The second index is computed as: |
||||
4*isTransient + 2*tf_select + per_band_flag */ |
||||
const signed char tf_select_table[4][8] = { |
||||
/*isTransient=0 isTransient=1 */ |
||||
{0, -1, 0, -1, 0,-1, 0,-1}, /* 2.5 ms */ |
||||
{0, -1, 0, -2, 1, 0, 1,-1}, /* 5 ms */ |
||||
{0, -2, 0, -3, 2, 0, 1,-1}, /* 10 ms */ |
||||
{0, -2, 0, -3, 3, 0, 1,-1}, /* 20 ms */ |
||||
}; |
||||
|
||||
|
||||
void init_caps(const CELTMode *m,int *cap,int LM,int C) |
||||
{ |
||||
int i; |
||||
for (i=0;i<m->nbEBands;i++) |
||||
{ |
||||
int N; |
||||
N=(m->eBands[i+1]-m->eBands[i])<<LM; |
||||
cap[i] = (m->cache.caps[m->nbEBands*(2*LM+C-1)+i]+64)*C*N>>2; |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
const char *opus_strerror(int error) |
||||
{ |
||||
static const char * const error_strings[8] = { |
||||
"success", |
||||
"invalid argument", |
||||
"buffer too small", |
||||
"internal error", |
||||
"corrupted stream", |
||||
"request not implemented", |
||||
"invalid state", |
||||
"memory allocation failed" |
||||
}; |
||||
if (error > 0 || error < -7) |
||||
return "unknown error"; |
||||
else |
||||
return error_strings[-error]; |
||||
} |
||||
|
||||
const char *opus_get_version_string(void) |
||||
{ |
||||
return "libopus " PACKAGE_VERSION |
||||
/* Applications may rely on the presence of this substring in the version
|
||||
string to determine if they have a fixed-point or floating-point build |
||||
at runtime. */ |
||||
#ifdef FIXED_POINT |
||||
"-fixed" |
||||
#endif |
||||
#ifdef FUZZING |
||||
"-fuzzing" |
||||
#endif |
||||
; |
||||
} |
||||
@ -0,0 +1,274 @@
|
||||
/* Copyright (c) 2007-2008 CSIRO
|
||||
Copyright (c) 2007-2009 Xiph.Org Foundation |
||||
Copyright (c) 2008 Gregory Maxwell |
||||
Written by Jean-Marc Valin and Gregory Maxwell */ |
||||
/**
|
||||
@file celt.h |
||||
@brief Contains all the functions for encoding and decoding audio |
||||
*/ |
||||
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef CELT_H |
||||
#define CELT_H |
||||
|
||||
#include "opus_types.h" |
||||
#include "opus_defines.h" |
||||
#include "opus_custom.h" |
||||
#include "entenc.h" |
||||
#include "entdec.h" |
||||
#include "arch.h" |
||||
#include "kiss_fft.h" |
||||
|
||||
#ifdef ENABLE_QEXT |
||||
#define ARG_QEXT(arg) , arg |
||||
#else |
||||
#define ARG_QEXT(arg) |
||||
#endif |
||||
|
||||
|
||||
#ifdef ENABLE_DEEP_PLC |
||||
#include "lpcnet.h" |
||||
#endif |
||||
|
||||
#define CELTEncoder OpusCustomEncoder |
||||
#define CELTDecoder OpusCustomDecoder |
||||
#define CELTMode OpusCustomMode |
||||
|
||||
#define QEXT_EXTENSION_ID 124 |
||||
|
||||
#define LEAK_BANDS 19 |
||||
|
||||
typedef struct { |
||||
int valid; |
||||
float tonality; |
||||
float tonality_slope; |
||||
float noisiness; |
||||
float activity; |
||||
float music_prob; |
||||
float music_prob_min; |
||||
float music_prob_max; |
||||
int bandwidth; |
||||
float activity_probability; |
||||
float max_pitch_ratio; |
||||
/* Store as Q6 char to save space. */ |
||||
unsigned char leak_boost[LEAK_BANDS]; |
||||
} AnalysisInfo; |
||||
|
||||
typedef struct { |
||||
int signalType; |
||||
int offset; |
||||
} SILKInfo; |
||||
|
||||
#define celt_check_mode_ptr_ptr(ptr) ((ptr) + ((ptr) - (const CELTMode**)(ptr))) |
||||
|
||||
#define celt_check_analysis_ptr(ptr) ((ptr) + ((ptr) - (const AnalysisInfo*)(ptr))) |
||||
|
||||
#define celt_check_silkinfo_ptr(ptr) ((ptr) + ((ptr) - (const SILKInfo*)(ptr))) |
||||
|
||||
#define celt_check_glog_ptr(ptr) ((ptr) + ((ptr) - (celt_glog*)(ptr))) |
||||
|
||||
/* Encoder/decoder Requests */ |
||||
|
||||
|
||||
#define CELT_SET_PREDICTION_REQUEST 10002 |
||||
/** Controls the use of interframe prediction.
|
||||
0=Independent frames |
||||
1=Short term interframe prediction allowed |
||||
2=Long term prediction allowed |
||||
*/ |
||||
#define CELT_SET_PREDICTION(x) CELT_SET_PREDICTION_REQUEST, opus_check_int(x) |
||||
|
||||
#define CELT_SET_INPUT_CLIPPING_REQUEST 10004 |
||||
#define CELT_SET_INPUT_CLIPPING(x) CELT_SET_INPUT_CLIPPING_REQUEST, opus_check_int(x) |
||||
|
||||
#define CELT_GET_AND_CLEAR_ERROR_REQUEST 10007 |
||||
#define CELT_GET_AND_CLEAR_ERROR(x) CELT_GET_AND_CLEAR_ERROR_REQUEST, opus_check_int_ptr(x) |
||||
|
||||
#define CELT_SET_CHANNELS_REQUEST 10008 |
||||
#define CELT_SET_CHANNELS(x) CELT_SET_CHANNELS_REQUEST, opus_check_int(x) |
||||
|
||||
|
||||
/* Internal */ |
||||
#define CELT_SET_START_BAND_REQUEST 10010 |
||||
#define CELT_SET_START_BAND(x) CELT_SET_START_BAND_REQUEST, opus_check_int(x) |
||||
|
||||
#define CELT_SET_END_BAND_REQUEST 10012 |
||||
#define CELT_SET_END_BAND(x) CELT_SET_END_BAND_REQUEST, opus_check_int(x) |
||||
|
||||
#define CELT_GET_MODE_REQUEST 10015 |
||||
/** Get the CELTMode used by an encoder or decoder */ |
||||
#define CELT_GET_MODE(x) CELT_GET_MODE_REQUEST, celt_check_mode_ptr_ptr(x) |
||||
|
||||
#define CELT_SET_SIGNALLING_REQUEST 10016 |
||||
#define CELT_SET_SIGNALLING(x) CELT_SET_SIGNALLING_REQUEST, opus_check_int(x) |
||||
|
||||
#define CELT_SET_TONALITY_REQUEST 10018 |
||||
#define CELT_SET_TONALITY(x) CELT_SET_TONALITY_REQUEST, opus_check_int(x) |
||||
#define CELT_SET_TONALITY_SLOPE_REQUEST 10020 |
||||
#define CELT_SET_TONALITY_SLOPE(x) CELT_SET_TONALITY_SLOPE_REQUEST, opus_check_int(x) |
||||
|
||||
#define CELT_SET_ANALYSIS_REQUEST 10022 |
||||
#define CELT_SET_ANALYSIS(x) CELT_SET_ANALYSIS_REQUEST, celt_check_analysis_ptr(x) |
||||
|
||||
#define OPUS_SET_LFE_REQUEST 10024 |
||||
#define OPUS_SET_LFE(x) OPUS_SET_LFE_REQUEST, opus_check_int(x) |
||||
|
||||
#define OPUS_SET_ENERGY_MASK_REQUEST 10026 |
||||
#define OPUS_SET_ENERGY_MASK(x) OPUS_SET_ENERGY_MASK_REQUEST, celt_check_glog_ptr(x) |
||||
|
||||
#define CELT_SET_SILK_INFO_REQUEST 10028 |
||||
#define CELT_SET_SILK_INFO(x) CELT_SET_SILK_INFO_REQUEST, celt_check_silkinfo_ptr(x) |
||||
|
||||
|
||||
static OPUS_INLINE opus_int32 bits_to_bitrate(opus_int32 bits, opus_int32 Fs, opus_int32 frame_size) { |
||||
return bits*(6*Fs/frame_size)/6; |
||||
} |
||||
|
||||
static OPUS_INLINE opus_int32 bitrate_to_bits(opus_int32 bitrate, opus_int32 Fs, opus_int32 frame_size) { |
||||
return bitrate*6/(6*Fs/frame_size); |
||||
} |
||||
|
||||
/* Encoder stuff */ |
||||
|
||||
int celt_encoder_get_size(int channels); |
||||
|
||||
int celt_encode_with_ec(OpusCustomEncoder * OPUS_RESTRICT st, const opus_res * pcm, int frame_size, unsigned char *compressed, int nbCompressedBytes, ec_enc *enc); |
||||
|
||||
int celt_encoder_init(CELTEncoder *st, opus_int32 sampling_rate, int channels, |
||||
int arch); |
||||
|
||||
|
||||
|
||||
/* Decoder stuff */ |
||||
|
||||
int celt_decoder_get_size(int channels); |
||||
|
||||
|
||||
int celt_decoder_init(CELTDecoder *st, opus_int32 sampling_rate, int channels); |
||||
|
||||
int celt_decode_with_ec_dred(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, |
||||
int len, opus_res * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum |
||||
#ifdef ENABLE_DEEP_PLC |
||||
,LPCNetPLCState *lpcnet |
||||
#endif |
||||
ARG_QEXT(const unsigned char *qext_payload) ARG_QEXT(int qext_payload_len) |
||||
); |
||||
|
||||
int celt_decode_with_ec(OpusCustomDecoder * OPUS_RESTRICT st, const unsigned char *data, |
||||
int len, opus_res * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum); |
||||
|
||||
#define celt_encoder_ctl opus_custom_encoder_ctl |
||||
#define celt_decoder_ctl opus_custom_decoder_ctl |
||||
|
||||
|
||||
#if defined(CUSTOM_MODES) || defined(ENABLE_OPUS_CUSTOM_API) |
||||
#define OPUS_CUSTOM_NOSTATIC |
||||
#else |
||||
#define OPUS_CUSTOM_NOSTATIC static OPUS_INLINE |
||||
#endif |
||||
|
||||
static const unsigned char trim_icdf[11] = {126, 124, 119, 109, 87, 41, 19, 9, 4, 2, 0}; |
||||
/* Probs: NONE: 21.875%, LIGHT: 6.25%, NORMAL: 65.625%, AGGRESSIVE: 6.25% */ |
||||
static const unsigned char spread_icdf[4] = {25, 23, 2, 0}; |
||||
|
||||
static const unsigned char tapset_icdf[3]={2,1,0}; |
||||
|
||||
#if defined(CUSTOM_MODES) || defined(ENABLE_OPUS_CUSTOM_API) |
||||
static const unsigned char toOpusTable[20] = { |
||||
0xE0, 0xE8, 0xF0, 0xF8, |
||||
0xC0, 0xC8, 0xD0, 0xD8, |
||||
0xA0, 0xA8, 0xB0, 0xB8, |
||||
0x00, 0x00, 0x00, 0x00, |
||||
0x80, 0x88, 0x90, 0x98, |
||||
}; |
||||
|
||||
static const unsigned char fromOpusTable[16] = { |
||||
0x80, 0x88, 0x90, 0x98, |
||||
0x40, 0x48, 0x50, 0x58, |
||||
0x20, 0x28, 0x30, 0x38, |
||||
0x00, 0x08, 0x10, 0x18 |
||||
}; |
||||
|
||||
static OPUS_INLINE int toOpus(unsigned char c) |
||||
{ |
||||
int ret=0; |
||||
if (c<0xA0) |
||||
ret = toOpusTable[c>>3]; |
||||
if (ret == 0) |
||||
return -1; |
||||
else |
||||
return ret|(c&0x7); |
||||
} |
||||
|
||||
static OPUS_INLINE int fromOpus(unsigned char c) |
||||
{ |
||||
if (c<0x80) |
||||
return -1; |
||||
else |
||||
return fromOpusTable[(c>>3)-16] | (c&0x7); |
||||
} |
||||
#endif /* CUSTOM_MODES */ |
||||
|
||||
#define COMBFILTER_MAXPERIOD 1024 |
||||
#define COMBFILTER_MINPERIOD 15 |
||||
|
||||
extern const signed char tf_select_table[4][8]; |
||||
|
||||
#if defined(ENABLE_HARDENING) || defined(ENABLE_ASSERTIONS) |
||||
void validate_celt_decoder(CELTDecoder *st); |
||||
#define VALIDATE_CELT_DECODER(st) validate_celt_decoder(st) |
||||
#else |
||||
#define VALIDATE_CELT_DECODER(st) |
||||
#endif |
||||
|
||||
int resampling_factor(opus_int32 rate); |
||||
|
||||
void celt_preemphasis(const opus_res * OPUS_RESTRICT pcmp, celt_sig * OPUS_RESTRICT inp, |
||||
int N, int CC, int upsample, const opus_val16 *coef, celt_sig *mem, int clip); |
||||
|
||||
void comb_filter(opus_val32 *y, opus_val32 *x, int T0, int T1, int N, |
||||
opus_val16 g0, opus_val16 g1, int tapset0, int tapset1, |
||||
const celt_coef *window, int overlap, int arch); |
||||
|
||||
void init_caps(const CELTMode *m,int *cap,int LM,int C); |
||||
|
||||
#ifdef RESYNTH |
||||
void deemphasis(celt_sig *in[], opus_res *pcm, int N, int C, int downsample, const opus_val16 *coef, celt_sig *mem, int accum); |
||||
void celt_synthesis(const CELTMode *mode, celt_norm *X, celt_sig * out_syn[], |
||||
celt_glog *oldBandE, int start, int effEnd, int C, int CC, int isTransient, |
||||
int LM, int downsample, int silence, int arch ARG_QEXT(const CELTMode *qext_mode) ARG_QEXT(const celt_glog *qext_bandLogE) ARG_QEXT(int qext_end)); |
||||
#endif |
||||
|
||||
#ifdef ENABLE_QEXT |
||||
#define QEXT_SCALE(x) ((qext_scale)*(x)) |
||||
#define QEXT_SCALE2(x, qext_scale) ((qext_scale)*(x)) |
||||
#else |
||||
#define QEXT_SCALE(x) (x) |
||||
#define QEXT_SCALE2(x, qext_scale) (x) |
||||
#endif |
||||
|
||||
#endif /* CELT_H */ |
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,374 @@
|
||||
/* Copyright (c) 2009-2010 Xiph.Org Foundation
|
||||
Written by Jean-Marc Valin */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifdef HAVE_CONFIG_H |
||||
#include "config.h" |
||||
#endif |
||||
|
||||
#include "celt_lpc.h" |
||||
#include "stack_alloc.h" |
||||
#include "mathops.h" |
||||
#include "pitch.h" |
||||
|
||||
void _celt_lpc( |
||||
opus_val16 *_lpc, /* out: [0...p-1] LPC coefficients */ |
||||
const opus_val32 *ac, /* in: [0...p] autocorrelation values */ |
||||
int p |
||||
) |
||||
{ |
||||
int i, j; |
||||
opus_val32 r; |
||||
opus_val32 error = ac[0]; |
||||
#ifdef FIXED_POINT |
||||
opus_val32 lpc[CELT_LPC_ORDER]; |
||||
#else |
||||
float *lpc = _lpc; |
||||
#endif |
||||
|
||||
OPUS_CLEAR(lpc, p); |
||||
#ifdef FIXED_POINT |
||||
if (ac[0] != 0) |
||||
#else |
||||
if (ac[0] > 1e-10f) |
||||
#endif |
||||
{ |
||||
for (i = 0; i < p; i++) { |
||||
/* Sum up this iteration's reflection coefficient */ |
||||
opus_val32 rr = 0; |
||||
#if defined (FIXED_POINT) && OPUS_FAST_INT64 |
||||
opus_int64 acc = 0; |
||||
for (j = 0; j < i; j++) |
||||
acc += (opus_int64)(lpc[j]) * (opus_int64)(ac[i - j]); |
||||
rr = (opus_val32)SHR64(acc, 31); |
||||
#else |
||||
for (j = 0; j < i; j++) |
||||
rr += MULT32_32_Q31(lpc[j],ac[i - j]); |
||||
#endif |
||||
rr += SHR32(ac[i + 1],6); |
||||
r = -frac_div32(SHL32(rr,6), error); |
||||
/* Update LPC coefficients and total error */ |
||||
lpc[i] = SHR32(r,6); |
||||
for (j = 0; j < (i+1)>>1; j++) |
||||
{ |
||||
opus_val32 tmp1, tmp2; |
||||
tmp1 = lpc[j]; |
||||
tmp2 = lpc[i-1-j]; |
||||
lpc[j] = tmp1 + MULT32_32_Q31(r,tmp2); |
||||
lpc[i-1-j] = tmp2 + MULT32_32_Q31(r,tmp1); |
||||
} |
||||
|
||||
error = error - MULT32_32_Q31(MULT32_32_Q31(r,r),error); |
||||
/* Bail out once we get 30 dB gain */ |
||||
#ifdef FIXED_POINT |
||||
if (error<=SHR32(ac[0],10)) |
||||
break; |
||||
#else |
||||
if (error<=.001f*ac[0]) |
||||
break; |
||||
#endif |
||||
} |
||||
} |
||||
#ifdef FIXED_POINT |
||||
{ |
||||
/* Convert the int32 lpcs to int16 and ensure there are no wrap-arounds.
|
||||
This reuses the logic in silk_LPC_fit() and silk_bwexpander_32(). Any bug |
||||
fixes should also be applied there. */ |
||||
int iter, idx = 0; |
||||
opus_val32 maxabs, absval, chirp_Q16, chirp_minus_one_Q16; |
||||
|
||||
for (iter = 0; iter < 10; iter++) { |
||||
maxabs = 0; |
||||
for (i = 0; i < p; i++) { |
||||
absval = ABS32(lpc[i]); |
||||
if (absval > maxabs) { |
||||
maxabs = absval; |
||||
idx = i; |
||||
} |
||||
} |
||||
maxabs = PSHR32(maxabs, 13); /* Q25->Q12 */ |
||||
|
||||
if (maxabs > 32767) { |
||||
maxabs = MIN32(maxabs, 163838); |
||||
chirp_Q16 = QCONST32(0.999, 16) - DIV32(SHL32(maxabs - 32767, 14), |
||||
SHR32(MULT32_32_32(maxabs, idx + 1), 2)); |
||||
chirp_minus_one_Q16 = chirp_Q16 - 65536; |
||||
|
||||
/* Apply bandwidth expansion. */ |
||||
for (i = 0; i < p - 1; i++) { |
||||
lpc[i] = MULT32_32_Q16(chirp_Q16, lpc[i]); |
||||
chirp_Q16 += PSHR32(MULT32_32_32(chirp_Q16, chirp_minus_one_Q16), 16); |
||||
} |
||||
lpc[p - 1] = MULT32_32_Q16(chirp_Q16, lpc[p - 1]); |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (iter == 10) { |
||||
/* If the coeffs still do not fit into the 16 bit range after 10 iterations,
|
||||
fall back to the A(z)=1 filter. */ |
||||
OPUS_CLEAR(lpc, p); |
||||
_lpc[0] = 4096; /* Q12 */ |
||||
} else { |
||||
for (i = 0; i < p; i++) { |
||||
_lpc[i] = EXTRACT16(PSHR32(lpc[i], 13)); /* Q25->Q12 */ |
||||
} |
||||
} |
||||
} |
||||
#endif |
||||
} |
||||
|
||||
|
||||
void celt_fir_c( |
||||
const opus_val16 *x, |
||||
const opus_val16 *num, |
||||
opus_val16 *y, |
||||
int N, |
||||
int ord, |
||||
int arch) |
||||
{ |
||||
int i,j; |
||||
VARDECL(opus_val16, rnum); |
||||
SAVE_STACK; |
||||
celt_assert(x != y); |
||||
ALLOC(rnum, ord, opus_val16); |
||||
for(i=0;i<ord;i++) |
||||
rnum[i] = num[ord-i-1]; |
||||
for (i=0;i<N-3;i+=4) |
||||
{ |
||||
opus_val32 sum[4]; |
||||
sum[0] = SHL32(EXTEND32(x[i ]), SIG_SHIFT); |
||||
sum[1] = SHL32(EXTEND32(x[i+1]), SIG_SHIFT); |
||||
sum[2] = SHL32(EXTEND32(x[i+2]), SIG_SHIFT); |
||||
sum[3] = SHL32(EXTEND32(x[i+3]), SIG_SHIFT); |
||||
#if defined(OPUS_CHECK_ASM) && defined(FIXED_POINT) |
||||
{ |
||||
opus_val32 sum_c[4]; |
||||
memcpy(sum_c, sum, sizeof(sum_c)); |
||||
xcorr_kernel_c(rnum, x+i-ord, sum_c, ord); |
||||
#endif |
||||
xcorr_kernel(rnum, x+i-ord, sum, ord, arch); |
||||
#if defined(OPUS_CHECK_ASM) && defined(FIXED_POINT) |
||||
celt_assert(memcmp(sum, sum_c, sizeof(sum)) == 0); |
||||
} |
||||
#endif |
||||
y[i ] = SROUND16(sum[0], SIG_SHIFT); |
||||
y[i+1] = SROUND16(sum[1], SIG_SHIFT); |
||||
y[i+2] = SROUND16(sum[2], SIG_SHIFT); |
||||
y[i+3] = SROUND16(sum[3], SIG_SHIFT); |
||||
} |
||||
for (;i<N;i++) |
||||
{ |
||||
opus_val32 sum = SHL32(EXTEND32(x[i]), SIG_SHIFT); |
||||
for (j=0;j<ord;j++) |
||||
sum = MAC16_16(sum,rnum[j],x[i+j-ord]); |
||||
y[i] = SROUND16(sum, SIG_SHIFT); |
||||
} |
||||
RESTORE_STACK; |
||||
} |
||||
|
||||
void celt_iir(const opus_val32 *_x, |
||||
const opus_val16 *den, |
||||
opus_val32 *_y, |
||||
int N, |
||||
int ord, |
||||
opus_val16 *mem, |
||||
int arch) |
||||
{ |
||||
#ifdef SMALL_FOOTPRINT |
||||
int i,j; |
||||
(void)arch; |
||||
for (i=0;i<N;i++) |
||||
{ |
||||
opus_val32 sum = _x[i]; |
||||
for (j=0;j<ord;j++) |
||||
{ |
||||
sum -= MULT16_16(den[j],mem[j]); |
||||
} |
||||
for (j=ord-1;j>=1;j--) |
||||
{ |
||||
mem[j]=mem[j-1]; |
||||
} |
||||
mem[0] = SROUND16(sum, SIG_SHIFT); |
||||
_y[i] = sum; |
||||
} |
||||
#else |
||||
int i,j; |
||||
VARDECL(opus_val16, rden); |
||||
VARDECL(opus_val16, y); |
||||
SAVE_STACK; |
||||
|
||||
celt_assert((ord&3)==0); |
||||
ALLOC(rden, ord, opus_val16); |
||||
ALLOC(y, N+ord, opus_val16); |
||||
for(i=0;i<ord;i++) |
||||
rden[i] = den[ord-i-1]; |
||||
for(i=0;i<ord;i++) |
||||
y[i] = -mem[ord-i-1]; |
||||
for(;i<N+ord;i++) |
||||
y[i]=0; |
||||
for (i=0;i<N-3;i+=4) |
||||
{ |
||||
/* Unroll by 4 as if it were an FIR filter */ |
||||
opus_val32 sum[4]; |
||||
sum[0]=_x[i]; |
||||
sum[1]=_x[i+1]; |
||||
sum[2]=_x[i+2]; |
||||
sum[3]=_x[i+3]; |
||||
#if defined(OPUS_CHECK_ASM) && defined(FIXED_POINT) |
||||
{ |
||||
opus_val32 sum_c[4]; |
||||
memcpy(sum_c, sum, sizeof(sum_c)); |
||||
xcorr_kernel_c(rden, y+i, sum_c, ord); |
||||
#endif |
||||
xcorr_kernel(rden, y+i, sum, ord, arch); |
||||
#if defined(OPUS_CHECK_ASM) && defined(FIXED_POINT) |
||||
celt_assert(memcmp(sum, sum_c, sizeof(sum)) == 0); |
||||
} |
||||
#endif |
||||
/* Patch up the result to compensate for the fact that this is an IIR */ |
||||
y[i+ord ] = -SROUND16(sum[0],SIG_SHIFT); |
||||
_y[i ] = sum[0]; |
||||
sum[1] = MAC16_16(sum[1], y[i+ord ], den[0]); |
||||
y[i+ord+1] = -SROUND16(sum[1],SIG_SHIFT); |
||||
_y[i+1] = sum[1]; |
||||
sum[2] = MAC16_16(sum[2], y[i+ord+1], den[0]); |
||||
sum[2] = MAC16_16(sum[2], y[i+ord ], den[1]); |
||||
y[i+ord+2] = -SROUND16(sum[2],SIG_SHIFT); |
||||
_y[i+2] = sum[2]; |
||||
|
||||
sum[3] = MAC16_16(sum[3], y[i+ord+2], den[0]); |
||||
sum[3] = MAC16_16(sum[3], y[i+ord+1], den[1]); |
||||
sum[3] = MAC16_16(sum[3], y[i+ord ], den[2]); |
||||
y[i+ord+3] = -SROUND16(sum[3],SIG_SHIFT); |
||||
_y[i+3] = sum[3]; |
||||
} |
||||
for (;i<N;i++) |
||||
{ |
||||
opus_val32 sum = _x[i]; |
||||
for (j=0;j<ord;j++) |
||||
sum -= MULT16_16(rden[j],y[i+j]); |
||||
y[i+ord] = SROUND16(sum,SIG_SHIFT); |
||||
_y[i] = sum; |
||||
} |
||||
for(i=0;i<ord;i++) |
||||
mem[i] = _y[N-i-1]; |
||||
RESTORE_STACK; |
||||
#endif |
||||
} |
||||
|
||||
int _celt_autocorr( |
||||
const opus_val16 *x, /* in: [0...n-1] samples x */ |
||||
opus_val32 *ac, /* out: [0...lag-1] ac values */ |
||||
const celt_coef *window, |
||||
int overlap, |
||||
int lag, |
||||
int n, |
||||
int arch |
||||
) |
||||
{ |
||||
opus_val32 d; |
||||
int i, k; |
||||
int fastN=n-lag; |
||||
int shift; |
||||
const opus_val16 *xptr; |
||||
VARDECL(opus_val16, xx); |
||||
SAVE_STACK; |
||||
ALLOC(xx, n, opus_val16); |
||||
celt_assert(n>0); |
||||
celt_assert(overlap>=0); |
||||
if (overlap == 0) |
||||
{ |
||||
xptr = x; |
||||
} else { |
||||
for (i=0;i<n;i++) |
||||
xx[i] = x[i]; |
||||
for (i=0;i<overlap;i++) |
||||
{ |
||||
opus_val16 w = COEF2VAL16(window[i]); |
||||
xx[i] = MULT16_16_Q15(x[i],w); |
||||
xx[n-i-1] = MULT16_16_Q15(x[n-i-1],w); |
||||
} |
||||
xptr = xx; |
||||
} |
||||
shift=0; |
||||
#ifdef FIXED_POINT |
||||
{ |
||||
opus_val32 ac0; |
||||
int ac0_shift = celt_ilog2(n + (n>>4)); |
||||
ac0 = 1+(n<<7); |
||||
if (n&1) ac0 += SHR32(MULT16_16(xptr[0],xptr[0]),ac0_shift); |
||||
for(i=(n&1);i<n;i+=2) |
||||
{ |
||||
ac0 += SHR32(MULT16_16(xptr[i],xptr[i]),ac0_shift); |
||||
ac0 += SHR32(MULT16_16(xptr[i+1],xptr[i+1]),ac0_shift); |
||||
} |
||||
/* Consider the effect of rounding-to-nearest when scaling the signal. */ |
||||
ac0 += SHR32(ac0,7); |
||||
|
||||
shift = celt_ilog2(ac0)-30+ac0_shift+1; |
||||
shift = (shift)/2; |
||||
if (shift>0) |
||||
{ |
||||
for(i=0;i<n;i++) |
||||
xx[i] = PSHR32(xptr[i], shift); |
||||
xptr = xx; |
||||
} else |
||||
shift = 0; |
||||
} |
||||
#endif |
||||
celt_pitch_xcorr(xptr, xptr, ac, fastN, lag+1, arch); |
||||
for (k=0;k<=lag;k++) |
||||
{ |
||||
for (i = k+fastN, d = 0; i < n; i++) |
||||
d = MAC16_16(d, xptr[i], xptr[i-k]); |
||||
ac[k] += d; |
||||
} |
||||
#ifdef FIXED_POINT |
||||
shift = 2*shift; |
||||
if (shift<=0) |
||||
ac[0] += SHL32((opus_int32)1, -shift); |
||||
if (ac[0] < 268435456) |
||||
{ |
||||
int shift2 = 29 - EC_ILOG(ac[0]); |
||||
for (i=0;i<=lag;i++) |
||||
ac[i] = SHL32(ac[i], shift2); |
||||
shift -= shift2; |
||||
} else if (ac[0] >= 536870912) |
||||
{ |
||||
int shift2=1; |
||||
if (ac[0] >= 1073741824) |
||||
shift2++; |
||||
for (i=0;i<=lag;i++) |
||||
ac[i] = SHR32(ac[i], shift2); |
||||
shift += shift2; |
||||
} |
||||
#endif |
||||
|
||||
RESTORE_STACK; |
||||
return shift; |
||||
} |
||||
@ -0,0 +1,66 @@
|
||||
/* Copyright (c) 2009-2010 Xiph.Org Foundation
|
||||
Written by Jean-Marc Valin */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef CELT_LPC_H |
||||
#define CELT_LPC_H |
||||
|
||||
#include "arch.h" |
||||
#include "cpu_support.h" |
||||
|
||||
#if defined(OPUS_X86_MAY_HAVE_SSE4_1) |
||||
#include "x86/celt_lpc_sse.h" |
||||
#endif |
||||
|
||||
#define CELT_LPC_ORDER 24 |
||||
|
||||
void _celt_lpc(opus_val16 *_lpc, const opus_val32 *ac, int p); |
||||
|
||||
void celt_fir_c( |
||||
const opus_val16 *x, |
||||
const opus_val16 *num, |
||||
opus_val16 *y, |
||||
int N, |
||||
int ord, |
||||
int arch); |
||||
|
||||
#if !defined(OVERRIDE_CELT_FIR) |
||||
#define celt_fir(x, num, y, N, ord, arch) \ |
||||
(celt_fir_c(x, num, y, N, ord, arch)) |
||||
#endif |
||||
|
||||
void celt_iir(const opus_val32 *x, |
||||
const opus_val16 *den, |
||||
opus_val32 *y, |
||||
int N, |
||||
int ord, |
||||
opus_val16 *mem, |
||||
int arch); |
||||
|
||||
int _celt_autocorr(const opus_val16 *x, opus_val32 *ac, |
||||
const celt_coef *window, int overlap, int lag, int n, int arch); |
||||
|
||||
#endif /* CELT_LPC_H */ |
||||
@ -0,0 +1,72 @@
|
||||
/* Copyright (c) 2010 Xiph.Org Foundation
|
||||
* Copyright (c) 2013 Parrot */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef CPU_SUPPORT_H |
||||
#define CPU_SUPPORT_H |
||||
|
||||
#include "opus_types.h" |
||||
#include "opus_defines.h" |
||||
|
||||
#if defined(OPUS_HAVE_RTCD) && \ |
||||
(defined(OPUS_ARM_ASM) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)) |
||||
#include "arm/armcpu.h" |
||||
|
||||
/* We currently support 5 ARM variants:
|
||||
* arch[0] -> ARMv4 |
||||
* arch[1] -> ARMv5E |
||||
* arch[2] -> ARMv6 |
||||
* arch[3] -> NEON |
||||
* arch[4] -> NEON+DOTPROD |
||||
*/ |
||||
#define OPUS_ARCHMASK 7 |
||||
|
||||
#elif defined(OPUS_HAVE_RTCD) && \ |
||||
((defined(OPUS_X86_MAY_HAVE_SSE) && !defined(OPUS_X86_PRESUME_SSE)) || \
|
||||
(defined(OPUS_X86_MAY_HAVE_SSE2) && !defined(OPUS_X86_PRESUME_SSE2)) || \
|
||||
(defined(OPUS_X86_MAY_HAVE_SSE4_1) && !defined(OPUS_X86_PRESUME_SSE4_1)) || \
|
||||
(defined(OPUS_X86_MAY_HAVE_AVX2) && !defined(OPUS_X86_PRESUME_AVX2))) |
||||
|
||||
#include "x86/x86cpu.h" |
||||
/* We currently support 5 x86 variants:
|
||||
* arch[0] -> non-sse |
||||
* arch[1] -> sse |
||||
* arch[2] -> sse2 |
||||
* arch[3] -> sse4.1 |
||||
* arch[4] -> avx |
||||
*/ |
||||
#define OPUS_ARCHMASK 7 |
||||
int opus_select_arch(void); |
||||
|
||||
#else |
||||
#define OPUS_ARCHMASK 0 |
||||
|
||||
static OPUS_INLINE int opus_select_arch(void) |
||||
{ |
||||
return 0; |
||||
} |
||||
#endif |
||||
#endif |
||||
@ -0,0 +1,719 @@
|
||||
/* Copyright (c) 2007-2008 CSIRO
|
||||
Copyright (c) 2007-2009 Xiph.Org Foundation |
||||
Copyright (c) 2007-2009 Timothy B. Terriberry |
||||
Written by Timothy B. Terriberry and Jean-Marc Valin */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifdef HAVE_CONFIG_H |
||||
#include "config.h" |
||||
#endif |
||||
|
||||
#include "os_support.h" |
||||
#include "cwrs.h" |
||||
#include "mathops.h" |
||||
#include "arch.h" |
||||
|
||||
#if defined(CUSTOM_MODES) || defined(ENABLE_QEXT) |
||||
#define CWRS_EXTRA_ROWS |
||||
#endif |
||||
|
||||
#if defined(CUSTOM_MODES) |
||||
|
||||
/*Guaranteed to return a conservatively large estimate of the binary logarithm
|
||||
with frac bits of fractional precision. |
||||
Tested for all possible 32-bit inputs with frac=4, where the maximum |
||||
overestimation is 0.06254243 bits.*/ |
||||
int log2_frac(opus_uint32 val, int frac) |
||||
{ |
||||
int l; |
||||
l=EC_ILOG(val); |
||||
if(val&(val-1)){ |
||||
/*This is (val>>l-16), but guaranteed to round up, even if adding a bias
|
||||
before the shift would cause overflow (e.g., for 0xFFFFxxxx). |
||||
Doesn't work for val=0, but that case fails the test above.*/ |
||||
if(l>16)val=((val-1)>>(l-16))+1; |
||||
else val<<=16-l; |
||||
l=(l-1)<<frac; |
||||
/*Note that we always need one iteration, since the rounding up above means
|
||||
that we might need to adjust the integer part of the logarithm.*/ |
||||
do{ |
||||
int b; |
||||
b=(int)(val>>16); |
||||
l+=b<<frac; |
||||
val=(val+b)>>b; |
||||
val=(val*val+0x7FFF)>>15; |
||||
} |
||||
while(frac-->0); |
||||
/*If val is not exactly 0x8000, then we have to round up the remainder.*/ |
||||
return l+(val>0x8000); |
||||
} |
||||
/*Exact powers of two require no rounding.*/ |
||||
else return (l-1)<<frac; |
||||
} |
||||
#endif |
||||
|
||||
/*Although derived separately, the pulse vector coding scheme is equivalent to
|
||||
a Pyramid Vector Quantizer \cite{Fis86}. |
||||
Some additional notes about an early version appear at |
||||
https://people.xiph.org/~tterribe/notes/cwrs.html, but the codebook ordering
|
||||
and the definitions of some terms have evolved since that was written. |
||||
|
||||
The conversion from a pulse vector to an integer index (encoding) and back |
||||
(decoding) is governed by two related functions, V(N,K) and U(N,K). |
||||
|
||||
V(N,K) = the number of combinations, with replacement, of N items, taken K |
||||
at a time, when a sign bit is added to each item taken at least once (i.e., |
||||
the number of N-dimensional unit pulse vectors with K pulses). |
||||
One way to compute this is via |
||||
V(N,K) = K>0 ? sum(k=1...K,2**k*choose(N,k)*choose(K-1,k-1)) : 1, |
||||
where choose() is the binomial function. |
||||
A table of values for N<10 and K<10 looks like: |
||||
V[10][10] = { |
||||
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
||||
{1, 2, 2, 2, 2, 2, 2, 2, 2, 2}, |
||||
{1, 4, 8, 12, 16, 20, 24, 28, 32, 36}, |
||||
{1, 6, 18, 38, 66, 102, 146, 198, 258, 326}, |
||||
{1, 8, 32, 88, 192, 360, 608, 952, 1408, 1992}, |
||||
{1, 10, 50, 170, 450, 1002, 1970, 3530, 5890, 9290}, |
||||
{1, 12, 72, 292, 912, 2364, 5336, 10836, 20256, 35436}, |
||||
{1, 14, 98, 462, 1666, 4942, 12642, 28814, 59906, 115598}, |
||||
{1, 16, 128, 688, 2816, 9424, 27008, 68464, 157184, 332688}, |
||||
{1, 18, 162, 978, 4482, 16722, 53154, 148626, 374274, 864146} |
||||
}; |
||||
|
||||
U(N,K) = the number of such combinations wherein N-1 objects are taken at |
||||
most K-1 at a time. |
||||
This is given by |
||||
U(N,K) = sum(k=0...K-1,V(N-1,k)) |
||||
= K>0 ? (V(N-1,K-1) + V(N,K-1))/2 : 0. |
||||
The latter expression also makes clear that U(N,K) is half the number of such |
||||
combinations wherein the first object is taken at least once. |
||||
Although it may not be clear from either of these definitions, U(N,K) is the |
||||
natural function to work with when enumerating the pulse vector codebooks, |
||||
not V(N,K). |
||||
U(N,K) is not well-defined for N=0, but with the extension |
||||
U(0,K) = K>0 ? 0 : 1, |
||||
the function becomes symmetric: U(N,K) = U(K,N), with a similar table: |
||||
U[10][10] = { |
||||
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
||||
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1}, |
||||
{0, 1, 3, 5, 7, 9, 11, 13, 15, 17}, |
||||
{0, 1, 5, 13, 25, 41, 61, 85, 113, 145}, |
||||
{0, 1, 7, 25, 63, 129, 231, 377, 575, 833}, |
||||
{0, 1, 9, 41, 129, 321, 681, 1289, 2241, 3649}, |
||||
{0, 1, 11, 61, 231, 681, 1683, 3653, 7183, 13073}, |
||||
{0, 1, 13, 85, 377, 1289, 3653, 8989, 19825, 40081}, |
||||
{0, 1, 15, 113, 575, 2241, 7183, 19825, 48639, 108545}, |
||||
{0, 1, 17, 145, 833, 3649, 13073, 40081, 108545, 265729} |
||||
}; |
||||
|
||||
With this extension, V(N,K) may be written in terms of U(N,K): |
||||
V(N,K) = U(N,K) + U(N,K+1) |
||||
for all N>=0, K>=0. |
||||
Thus U(N,K+1) represents the number of combinations where the first element |
||||
is positive or zero, and U(N,K) represents the number of combinations where |
||||
it is negative. |
||||
With a large enough table of U(N,K) values, we could write O(N) encoding |
||||
and O(min(N*log(K),N+K)) decoding routines, but such a table would be |
||||
prohibitively large for small embedded devices (K may be as large as 32767 |
||||
for small N, and N may be as large as 200). |
||||
|
||||
Both functions obey the same recurrence relation: |
||||
V(N,K) = V(N-1,K) + V(N,K-1) + V(N-1,K-1), |
||||
U(N,K) = U(N-1,K) + U(N,K-1) + U(N-1,K-1), |
||||
for all N>0, K>0, with different initial conditions at N=0 or K=0. |
||||
This allows us to construct a row of one of the tables above given the |
||||
previous row or the next row. |
||||
Thus we can derive O(NK) encoding and decoding routines with O(K) memory |
||||
using only addition and subtraction. |
||||
|
||||
When encoding, we build up from the U(2,K) row and work our way forwards. |
||||
When decoding, we need to start at the U(N,K) row and work our way backwards, |
||||
which requires a means of computing U(N,K). |
||||
U(N,K) may be computed from two previous values with the same N: |
||||
U(N,K) = ((2*N-1)*U(N,K-1) - U(N,K-2))/(K-1) + U(N,K-2) |
||||
for all N>1, and since U(N,K) is symmetric, a similar relation holds for two |
||||
previous values with the same K: |
||||
U(N,K>1) = ((2*K-1)*U(N-1,K) - U(N-2,K))/(N-1) + U(N-2,K) |
||||
for all K>1. |
||||
This allows us to construct an arbitrary row of the U(N,K) table by starting |
||||
with the first two values, which are constants. |
||||
This saves roughly 2/3 the work in our O(NK) decoding routine, but costs O(K) |
||||
multiplications. |
||||
Similar relations can be derived for V(N,K), but are not used here. |
||||
|
||||
For N>0 and K>0, U(N,K) and V(N,K) take on the form of an (N-1)-degree |
||||
polynomial for fixed N. |
||||
The first few are |
||||
U(1,K) = 1, |
||||
U(2,K) = 2*K-1, |
||||
U(3,K) = (2*K-2)*K+1, |
||||
U(4,K) = (((4*K-6)*K+8)*K-3)/3, |
||||
U(5,K) = ((((2*K-4)*K+10)*K-8)*K+3)/3, |
||||
and |
||||
V(1,K) = 2, |
||||
V(2,K) = 4*K, |
||||
V(3,K) = 4*K*K+2, |
||||
V(4,K) = 8*(K*K+2)*K/3, |
||||
V(5,K) = ((4*K*K+20)*K*K+6)/3, |
||||
for all K>0. |
||||
This allows us to derive O(N) encoding and O(N*log(K)) decoding routines for |
||||
small N (and indeed decoding is also O(N) for N<3). |
||||
|
||||
@ARTICLE{Fis86, |
||||
author="Thomas R. Fischer", |
||||
title="A Pyramid Vector Quantizer", |
||||
journal="IEEE Transactions on Information Theory", |
||||
volume="IT-32", |
||||
number=4, |
||||
pages="568--583", |
||||
month=Jul, |
||||
year=1986 |
||||
}*/ |
||||
|
||||
#if !defined(SMALL_FOOTPRINT) |
||||
|
||||
/*U(N,K) = U(K,N) := N>0?K>0?U(N-1,K)+U(N,K-1)+U(N-1,K-1):0:K>0?1:0*/ |
||||
# define CELT_PVQ_U(_n,_k) (CELT_PVQ_U_ROW[IMIN(_n,_k)][IMAX(_n,_k)]) |
||||
/*V(N,K) := U(N,K)+U(N,K+1) = the number of PVQ codewords for a band of size N
|
||||
with K pulses allocated to it.*/ |
||||
# define CELT_PVQ_V(_n,_k) (CELT_PVQ_U(_n,_k)+CELT_PVQ_U(_n,(_k)+1)) |
||||
|
||||
/*For each V(N,K) supported, we will access element U(min(N,K+1),max(N,K+1)).
|
||||
Thus, the number of entries in row I is the larger of the maximum number of |
||||
pulses we will ever allocate for a given N=I (K=128, or however many fit in |
||||
32 bits, whichever is smaller), plus one, and the maximum N for which |
||||
K=I-1 pulses fit in 32 bits. |
||||
The largest band size in an Opus Custom mode is 208. |
||||
Otherwise, we can limit things to the set of N which can be achieved by |
||||
splitting a band from a standard Opus mode: 176, 144, 96, 88, 72, 64, 48, |
||||
44, 36, 32, 24, 22, 18, 16, 8, 4, 2).*/ |
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
static const opus_uint32 CELT_PVQ_U_DATA[1488]={ |
||||
#else |
||||
static const opus_uint32 CELT_PVQ_U_DATA[1272]={ |
||||
#endif |
||||
/*N=0, K=0...176:*/ |
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
/*...208:*/ |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, |
||||
#endif |
||||
/*N=1, K=1...176:*/ |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
/*...208:*/ |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
1, 1, 1, 1, 1, 1, |
||||
#endif |
||||
/*N=2, K=2...176:*/ |
||||
3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, |
||||
43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, |
||||
81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, |
||||
115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, |
||||
145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, |
||||
175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, |
||||
205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, |
||||
235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, 257, 259, 261, 263, |
||||
265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287, 289, 291, 293, |
||||
295, 297, 299, 301, 303, 305, 307, 309, 311, 313, 315, 317, 319, 321, 323, |
||||
325, 327, 329, 331, 333, 335, 337, 339, 341, 343, 345, 347, 349, 351, |
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
/*...208:*/ |
||||
353, 355, 357, 359, 361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, |
||||
383, 385, 387, 389, 391, 393, 395, 397, 399, 401, 403, 405, 407, 409, 411, |
||||
413, 415, |
||||
#endif |
||||
/*N=3, K=3...176:*/ |
||||
13, 25, 41, 61, 85, 113, 145, 181, 221, 265, 313, 365, 421, 481, 545, 613, |
||||
685, 761, 841, 925, 1013, 1105, 1201, 1301, 1405, 1513, 1625, 1741, 1861, |
||||
1985, 2113, 2245, 2381, 2521, 2665, 2813, 2965, 3121, 3281, 3445, 3613, 3785, |
||||
3961, 4141, 4325, 4513, 4705, 4901, 5101, 5305, 5513, 5725, 5941, 6161, 6385, |
||||
6613, 6845, 7081, 7321, 7565, 7813, 8065, 8321, 8581, 8845, 9113, 9385, 9661, |
||||
9941, 10225, 10513, 10805, 11101, 11401, 11705, 12013, 12325, 12641, 12961, |
||||
13285, 13613, 13945, 14281, 14621, 14965, 15313, 15665, 16021, 16381, 16745, |
||||
17113, 17485, 17861, 18241, 18625, 19013, 19405, 19801, 20201, 20605, 21013, |
||||
21425, 21841, 22261, 22685, 23113, 23545, 23981, 24421, 24865, 25313, 25765, |
||||
26221, 26681, 27145, 27613, 28085, 28561, 29041, 29525, 30013, 30505, 31001, |
||||
31501, 32005, 32513, 33025, 33541, 34061, 34585, 35113, 35645, 36181, 36721, |
||||
37265, 37813, 38365, 38921, 39481, 40045, 40613, 41185, 41761, 42341, 42925, |
||||
43513, 44105, 44701, 45301, 45905, 46513, 47125, 47741, 48361, 48985, 49613, |
||||
50245, 50881, 51521, 52165, 52813, 53465, 54121, 54781, 55445, 56113, 56785, |
||||
57461, 58141, 58825, 59513, 60205, 60901, 61601, |
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
/*...208:*/ |
||||
62305, 63013, 63725, 64441, 65161, 65885, 66613, 67345, 68081, 68821, 69565, |
||||
70313, 71065, 71821, 72581, 73345, 74113, 74885, 75661, 76441, 77225, 78013, |
||||
78805, 79601, 80401, 81205, 82013, 82825, 83641, 84461, 85285, 86113, |
||||
#endif |
||||
/*N=4, K=4...176:*/ |
||||
63, 129, 231, 377, 575, 833, 1159, 1561, 2047, 2625, 3303, 4089, 4991, 6017, |
||||
7175, 8473, 9919, 11521, 13287, 15225, 17343, 19649, 22151, 24857, 27775, |
||||
30913, 34279, 37881, 41727, 45825, 50183, 54809, 59711, 64897, 70375, 76153, |
||||
82239, 88641, 95367, 102425, 109823, 117569, 125671, 134137, 142975, 152193, |
||||
161799, 171801, 182207, 193025, 204263, 215929, 228031, 240577, 253575, |
||||
267033, 280959, 295361, 310247, 325625, 341503, 357889, 374791, 392217, |
||||
410175, 428673, 447719, 467321, 487487, 508225, 529543, 551449, 573951, |
||||
597057, 620775, 645113, 670079, 695681, 721927, 748825, 776383, 804609, |
||||
833511, 863097, 893375, 924353, 956039, 988441, 1021567, 1055425, 1090023, |
||||
1125369, 1161471, 1198337, 1235975, 1274393, 1313599, 1353601, 1394407, |
||||
1436025, 1478463, 1521729, 1565831, 1610777, 1656575, 1703233, 1750759, |
||||
1799161, 1848447, 1898625, 1949703, 2001689, 2054591, 2108417, 2163175, |
||||
2218873, 2275519, 2333121, 2391687, 2451225, 2511743, 2573249, 2635751, |
||||
2699257, 2763775, 2829313, 2895879, 2963481, 3032127, 3101825, 3172583, |
||||
3244409, 3317311, 3391297, 3466375, 3542553, 3619839, 3698241, 3777767, |
||||
3858425, 3940223, 4023169, 4107271, 4192537, 4278975, 4366593, 4455399, |
||||
4545401, 4636607, 4729025, 4822663, 4917529, 5013631, 5110977, 5209575, |
||||
5309433, 5410559, 5512961, 5616647, 5721625, 5827903, 5935489, 6044391, |
||||
6154617, 6266175, 6379073, 6493319, 6608921, 6725887, 6844225, 6963943, |
||||
7085049, 7207551, |
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
/*...208:*/ |
||||
7331457, 7456775, 7583513, 7711679, 7841281, 7972327, 8104825, 8238783, |
||||
8374209, 8511111, 8649497, 8789375, 8930753, 9073639, 9218041, 9363967, |
||||
9511425, 9660423, 9810969, 9963071, 10116737, 10271975, 10428793, 10587199, |
||||
10747201, 10908807, 11072025, 11236863, 11403329, 11571431, 11741177, |
||||
11912575, |
||||
#endif |
||||
/*N=5, K=5...176:*/ |
||||
321, 681, 1289, 2241, 3649, 5641, 8361, 11969, 16641, 22569, 29961, 39041, |
||||
50049, 63241, 78889, 97281, 118721, 143529, 172041, 204609, 241601, 283401, |
||||
330409, 383041, 441729, 506921, 579081, 658689, 746241, 842249, 947241, |
||||
1061761, 1186369, 1321641, 1468169, 1626561, 1797441, 1981449, 2179241, |
||||
2391489, 2618881, 2862121, 3121929, 3399041, 3694209, 4008201, 4341801, |
||||
4695809, 5071041, 5468329, 5888521, 6332481, 6801089, 7295241, 7815849, |
||||
8363841, 8940161, 9545769, 10181641, 10848769, 11548161, 12280841, 13047849, |
||||
13850241, 14689089, 15565481, 16480521, 17435329, 18431041, 19468809, |
||||
20549801, 21675201, 22846209, 24064041, 25329929, 26645121, 28010881, |
||||
29428489, 30899241, 32424449, 34005441, 35643561, 37340169, 39096641, |
||||
40914369, 42794761, 44739241, 46749249, 48826241, 50971689, 53187081, |
||||
55473921, 57833729, 60268041, 62778409, 65366401, 68033601, 70781609, |
||||
73612041, 76526529, 79526721, 82614281, 85790889, 89058241, 92418049, |
||||
95872041, 99421961, 103069569, 106816641, 110664969, 114616361, 118672641, |
||||
122835649, 127107241, 131489289, 135983681, 140592321, 145317129, 150160041, |
||||
155123009, 160208001, 165417001, 170752009, 176215041, 181808129, 187533321, |
||||
193392681, 199388289, 205522241, 211796649, 218213641, 224775361, 231483969, |
||||
238341641, 245350569, 252512961, 259831041, 267307049, 274943241, 282741889, |
||||
290705281, 298835721, 307135529, 315607041, 324252609, 333074601, 342075401, |
||||
351257409, 360623041, 370174729, 379914921, 389846081, 399970689, 410291241, |
||||
420810249, 431530241, 442453761, 453583369, 464921641, 476471169, 488234561, |
||||
500214441, 512413449, 524834241, 537479489, 550351881, 563454121, 576788929, |
||||
590359041, 604167209, 618216201, 632508801, |
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
/*...208:*/ |
||||
647047809, 661836041, 676876329, 692171521, 707724481, 723538089, 739615241, |
||||
755958849, 772571841, 789457161, 806617769, 824056641, 841776769, 859781161, |
||||
878072841, 896654849, 915530241, 934702089, 954173481, 973947521, 994027329, |
||||
1014416041, 1035116809, 1056132801, 1077467201, 1099123209, 1121104041, |
||||
1143412929, 1166053121, 1189027881, 1212340489, 1235994241, |
||||
#endif |
||||
/*N=6, K=6...96:*/ |
||||
1683, 3653, 7183, 13073, 22363, 36365, 56695, 85305, 124515, 177045, 246047, |
||||
335137, 448427, 590557, 766727, 982729, 1244979, 1560549, 1937199, 2383409, |
||||
2908411, 3522221, 4235671, 5060441, 6009091, 7095093, 8332863, 9737793, |
||||
11326283, 13115773, 15124775, 17372905, 19880915, 22670725, 25765455, |
||||
29189457, 32968347, 37129037, 41699767, 46710137, 52191139, 58175189, |
||||
64696159, 71789409, 79491819, 87841821, 96879431, 106646281, 117185651, |
||||
128542501, 140763503, 153897073, 167993403, 183104493, 199284183, 216588185, |
||||
235074115, 254801525, 275831935, 298228865, 322057867, 347386557, 374284647, |
||||
402823977, 433078547, 465124549, 499040399, 534906769, 572806619, 612825229, |
||||
655050231, 699571641, 746481891, 795875861, 847850911, 902506913, 959946283, |
||||
1020274013, 1083597703, 1150027593, 1219676595, 1292660325, 1369097135, |
||||
1449108145, 1532817275, 1620351277, 1711839767, 1807415257, 1907213187, |
||||
2011371957, 2120032959, |
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
/*...109:*/ |
||||
2233340609U, 2351442379U, 2474488829U, 2602633639U, 2736033641U, 2874848851U, |
||||
3019242501U, 3169381071U, 3325434321U, 3487575323U, 3655980493U, 3830829623U, |
||||
4012305913U, |
||||
#endif |
||||
/*N=7, K=7...54*/ |
||||
8989, 19825, 40081, 75517, 134245, 227305, 369305, 579125, 880685, 1303777, |
||||
1884961, 2668525, 3707509, 5064793, 6814249, 9041957, 11847485, 15345233, |
||||
19665841, 24957661, 31388293, 39146185, 48442297, 59511829, 72616013, |
||||
88043969, 106114625, 127178701, 151620757, 179861305, 212358985, 249612805, |
||||
292164445, 340600625, 395555537, 457713341, 527810725, 606639529, 695049433, |
||||
793950709, 904317037, 1027188385, 1163673953, 1314955181, 1482288821, |
||||
1667010073, 1870535785, 2094367717, |
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
/*...60:*/ |
||||
2340095869U, 2609401873U, 2904062449U, 3225952925U, 3577050821U, 3959439497U, |
||||
#endif |
||||
/*N=8, K=8...37*/ |
||||
48639, 108545, 224143, 433905, 795455, 1392065, 2340495, 3800305, 5984767, |
||||
9173505, 13726991, 20103025, 28875327, 40754369, 56610575, 77500017, |
||||
104692735, 139703809, 184327311, 240673265, 311207743, 398796225, 506750351, |
||||
638878193, 799538175, 993696769, 1226990095, 1505789553, 1837271615, |
||||
2229491905U, |
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
/*...40:*/ |
||||
2691463695U, 3233240945U, 3866006015U, |
||||
#endif |
||||
/*N=9, K=9...28:*/ |
||||
265729, 598417, 1256465, 2485825, 4673345, 8405905, 14546705, 24331777, |
||||
39490049, 62390545, 96220561, 145198913, 214828609, 312193553, 446304145, |
||||
628496897, 872893441, 1196924561, 1621925137, 2173806145U, |
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
/*...29:*/ |
||||
2883810113U, |
||||
#endif |
||||
/*N=10, K=10...24:*/ |
||||
1462563, 3317445, 7059735, 14218905, 27298155, 50250765, 89129247, 152951073, |
||||
254831667, 413442773, 654862247, 1014889769, 1541911931, 2300409629U, |
||||
3375210671U, |
||||
/*N=11, K=11...19:*/ |
||||
8097453, 18474633, 39753273, 81270333, 158819253, 298199265, 540279585, |
||||
948062325, 1616336765, |
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
/*...20:*/ |
||||
2684641785U, |
||||
#endif |
||||
/*N=12, K=12...18:*/ |
||||
45046719, 103274625, 224298231, 464387817, 921406335, 1759885185, |
||||
3248227095U, |
||||
/*N=13, K=13...16:*/ |
||||
251595969, 579168825, 1267854873, 2653649025U, |
||||
/*N=14, K=14:*/ |
||||
1409933619 |
||||
}; |
||||
|
||||
#if defined(CWRS_EXTRA_ROWS) |
||||
static const opus_uint32 *const CELT_PVQ_U_ROW[15]={ |
||||
CELT_PVQ_U_DATA+ 0,CELT_PVQ_U_DATA+ 208,CELT_PVQ_U_DATA+ 415, |
||||
CELT_PVQ_U_DATA+ 621,CELT_PVQ_U_DATA+ 826,CELT_PVQ_U_DATA+1030, |
||||
CELT_PVQ_U_DATA+1233,CELT_PVQ_U_DATA+1336,CELT_PVQ_U_DATA+1389, |
||||
CELT_PVQ_U_DATA+1421,CELT_PVQ_U_DATA+1441,CELT_PVQ_U_DATA+1455, |
||||
CELT_PVQ_U_DATA+1464,CELT_PVQ_U_DATA+1470,CELT_PVQ_U_DATA+1473 |
||||
}; |
||||
#else |
||||
static const opus_uint32 *const CELT_PVQ_U_ROW[15]={ |
||||
CELT_PVQ_U_DATA+ 0,CELT_PVQ_U_DATA+ 176,CELT_PVQ_U_DATA+ 351, |
||||
CELT_PVQ_U_DATA+ 525,CELT_PVQ_U_DATA+ 698,CELT_PVQ_U_DATA+ 870, |
||||
CELT_PVQ_U_DATA+1041,CELT_PVQ_U_DATA+1131,CELT_PVQ_U_DATA+1178, |
||||
CELT_PVQ_U_DATA+1207,CELT_PVQ_U_DATA+1226,CELT_PVQ_U_DATA+1240, |
||||
CELT_PVQ_U_DATA+1248,CELT_PVQ_U_DATA+1254,CELT_PVQ_U_DATA+1257 |
||||
}; |
||||
#endif |
||||
|
||||
#if defined(CUSTOM_MODES) |
||||
void get_required_bits(opus_int16 *_bits,int _n,int _maxk,int _frac){ |
||||
int k; |
||||
/*_maxk==0 => there's nothing to do.*/ |
||||
celt_assert(_maxk>0); |
||||
_bits[0]=0; |
||||
for(k=1;k<=_maxk;k++)_bits[k]=log2_frac(CELT_PVQ_V(_n,k),_frac); |
||||
} |
||||
#endif |
||||
|
||||
static opus_uint32 icwrs(int _n,const int *_y){ |
||||
opus_uint32 i; |
||||
int j; |
||||
int k; |
||||
celt_assert(_n>=2); |
||||
j=_n-1; |
||||
i=_y[j]<0; |
||||
k=abs(_y[j]); |
||||
do{ |
||||
j--; |
||||
i+=CELT_PVQ_U(_n-j,k); |
||||
k+=abs(_y[j]); |
||||
if(_y[j]<0)i+=CELT_PVQ_U(_n-j,k+1); |
||||
} |
||||
while(j>0); |
||||
return i; |
||||
} |
||||
|
||||
void encode_pulses(const int *_y,int _n,int _k,ec_enc *_enc){ |
||||
celt_assert(_k>0); |
||||
ec_enc_uint(_enc,icwrs(_n,_y),CELT_PVQ_V(_n,_k)); |
||||
} |
||||
|
||||
static opus_val32 cwrsi(int _n,int _k,opus_uint32 _i,int *_y){ |
||||
opus_uint32 p; |
||||
int s; |
||||
int k0; |
||||
opus_int16 val; |
||||
opus_val32 yy=0; |
||||
celt_assert(_k>0); |
||||
celt_assert(_n>1); |
||||
while(_n>2){ |
||||
opus_uint32 q; |
||||
/*Lots of pulses case:*/ |
||||
if(_k>=_n){ |
||||
const opus_uint32 *row; |
||||
row=CELT_PVQ_U_ROW[_n]; |
||||
/*Are the pulses in this dimension negative?*/ |
||||
p=row[_k+1]; |
||||
s=-(_i>=p); |
||||
_i-=p&s; |
||||
/*Count how many pulses were placed in this dimension.*/ |
||||
k0=_k; |
||||
q=row[_n]; |
||||
if(q>_i){ |
||||
celt_sig_assert(p>q); |
||||
_k=_n; |
||||
do p=CELT_PVQ_U_ROW[--_k][_n]; |
||||
while(p>_i); |
||||
} |
||||
else for(p=row[_k];p>_i;p=row[_k])_k--; |
||||
_i-=p; |
||||
val=(k0-_k+s)^s; |
||||
*_y++=val; |
||||
yy=MAC16_16(yy,val,val); |
||||
} |
||||
/*Lots of dimensions case:*/ |
||||
else{ |
||||
/*Are there any pulses in this dimension at all?*/ |
||||
p=CELT_PVQ_U_ROW[_k][_n]; |
||||
q=CELT_PVQ_U_ROW[_k+1][_n]; |
||||
if(p<=_i&&_i<q){ |
||||
_i-=p; |
||||
*_y++=0; |
||||
} |
||||
else{ |
||||
/*Are the pulses in this dimension negative?*/ |
||||
s=-(_i>=q); |
||||
_i-=q&s; |
||||
/*Count how many pulses were placed in this dimension.*/ |
||||
k0=_k; |
||||
do p=CELT_PVQ_U_ROW[--_k][_n]; |
||||
while(p>_i); |
||||
_i-=p; |
||||
val=(k0-_k+s)^s; |
||||
*_y++=val; |
||||
yy=MAC16_16(yy,val,val); |
||||
} |
||||
} |
||||
_n--; |
||||
} |
||||
/*_n==2*/ |
||||
p=2*_k+1; |
||||
s=-(_i>=p); |
||||
_i-=p&s; |
||||
k0=_k; |
||||
_k=(_i+1)>>1; |
||||
if(_k)_i-=2*_k-1; |
||||
val=(k0-_k+s)^s; |
||||
*_y++=val; |
||||
yy=MAC16_16(yy,val,val); |
||||
/*_n==1*/ |
||||
s=-(int)_i; |
||||
val=(_k+s)^s; |
||||
*_y=val; |
||||
yy=MAC16_16(yy,val,val); |
||||
return yy; |
||||
} |
||||
|
||||
opus_val32 decode_pulses(int *_y,int _n,int _k,ec_dec *_dec){ |
||||
return cwrsi(_n,_k,ec_dec_uint(_dec,CELT_PVQ_V(_n,_k)),_y); |
||||
} |
||||
|
||||
#else /* SMALL_FOOTPRINT */ |
||||
|
||||
/*Computes the next row/column of any recurrence that obeys the relation
|
||||
u[i][j]=u[i-1][j]+u[i][j-1]+u[i-1][j-1]. |
||||
_ui0 is the base case for the new row/column.*/ |
||||
static OPUS_INLINE void unext(opus_uint32 *_ui,unsigned _len,opus_uint32 _ui0){ |
||||
opus_uint32 ui1; |
||||
unsigned j; |
||||
/*This do-while will overrun the array if we don't have storage for at least
|
||||
2 values.*/ |
||||
j=1; do { |
||||
ui1=UADD32(UADD32(_ui[j],_ui[j-1]),_ui0); |
||||
_ui[j-1]=_ui0; |
||||
_ui0=ui1; |
||||
} while (++j<_len); |
||||
_ui[j-1]=_ui0; |
||||
} |
||||
|
||||
/*Computes the previous row/column of any recurrence that obeys the relation
|
||||
u[i-1][j]=u[i][j]-u[i][j-1]-u[i-1][j-1]. |
||||
_ui0 is the base case for the new row/column.*/ |
||||
static OPUS_INLINE void uprev(opus_uint32 *_ui,unsigned _n,opus_uint32 _ui0){ |
||||
opus_uint32 ui1; |
||||
unsigned j; |
||||
/*This do-while will overrun the array if we don't have storage for at least
|
||||
2 values.*/ |
||||
j=1; do { |
||||
ui1=USUB32(USUB32(_ui[j],_ui[j-1]),_ui0); |
||||
_ui[j-1]=_ui0; |
||||
_ui0=ui1; |
||||
} while (++j<_n); |
||||
_ui[j-1]=_ui0; |
||||
} |
||||
|
||||
/*Compute V(_n,_k), as well as U(_n,0..._k+1).
|
||||
_u: On exit, _u[i] contains U(_n,i) for i in [0..._k+1].*/ |
||||
static opus_uint32 ncwrs_urow(unsigned _n,unsigned _k,opus_uint32 *_u){ |
||||
opus_uint32 um2; |
||||
unsigned len; |
||||
unsigned k; |
||||
len=_k+2; |
||||
/*We require storage at least 3 values (e.g., _k>0).*/ |
||||
celt_assert(len>=3); |
||||
_u[0]=0; |
||||
_u[1]=um2=1; |
||||
/*If _n==0, _u[0] should be 1 and the rest should be 0.*/ |
||||
/*If _n==1, _u[i] should be 1 for i>1.*/ |
||||
celt_assert(_n>=2); |
||||
/*If _k==0, the following do-while loop will overflow the buffer.*/ |
||||
celt_assert(_k>0); |
||||
k=2; |
||||
do _u[k]=(k<<1)-1; |
||||
while(++k<len); |
||||
for(k=2;k<_n;k++)unext(_u+1,_k+1,1); |
||||
return _u[_k]+_u[_k+1]; |
||||
} |
||||
|
||||
/*Returns the _i'th combination of _k elements chosen from a set of size _n
|
||||
with associated sign bits. |
||||
_y: Returns the vector of pulses. |
||||
_u: Must contain entries [0..._k+1] of row _n of U() on input. |
||||
Its contents will be destructively modified.*/ |
||||
static opus_val32 cwrsi(int _n,int _k,opus_uint32 _i,int *_y,opus_uint32 *_u){ |
||||
int j; |
||||
opus_int16 val; |
||||
opus_val32 yy=0; |
||||
celt_assert(_n>0); |
||||
j=0; |
||||
do{ |
||||
opus_uint32 p; |
||||
int s; |
||||
int yj; |
||||
p=_u[_k+1]; |
||||
s=-(_i>=p); |
||||
_i-=p&s; |
||||
yj=_k; |
||||
p=_u[_k]; |
||||
while(p>_i)p=_u[--_k]; |
||||
_i-=p; |
||||
yj-=_k; |
||||
val=(yj+s)^s; |
||||
_y[j]=val; |
||||
yy=MAC16_16(yy,val,val); |
||||
uprev(_u,_k+2,0); |
||||
} |
||||
while(++j<_n); |
||||
return yy; |
||||
} |
||||
|
||||
/*Returns the index of the given combination of K elements chosen from a set
|
||||
of size 1 with associated sign bits. |
||||
_y: The vector of pulses, whose sum of absolute values is K. |
||||
_k: Returns K.*/ |
||||
static OPUS_INLINE opus_uint32 icwrs1(const int *_y,int *_k){ |
||||
*_k=abs(_y[0]); |
||||
return _y[0]<0; |
||||
} |
||||
|
||||
/*Returns the index of the given combination of K elements chosen from a set
|
||||
of size _n with associated sign bits. |
||||
_y: The vector of pulses, whose sum of absolute values must be _k. |
||||
_nc: Returns V(_n,_k).*/ |
||||
static OPUS_INLINE opus_uint32 icwrs(int _n,int _k,opus_uint32 *_nc,const int *_y, |
||||
opus_uint32 *_u){ |
||||
opus_uint32 i; |
||||
int j; |
||||
int k; |
||||
/*We can't unroll the first two iterations of the loop unless _n>=2.*/ |
||||
celt_assert(_n>=2); |
||||
_u[0]=0; |
||||
for(k=1;k<=_k+1;k++)_u[k]=(k<<1)-1; |
||||
i=icwrs1(_y+_n-1,&k); |
||||
j=_n-2; |
||||
i+=_u[k]; |
||||
k+=abs(_y[j]); |
||||
if(_y[j]<0)i+=_u[k+1]; |
||||
while(j-->0){ |
||||
unext(_u,_k+2,0); |
||||
i+=_u[k]; |
||||
k+=abs(_y[j]); |
||||
if(_y[j]<0)i+=_u[k+1]; |
||||
} |
||||
*_nc=_u[k]+_u[k+1]; |
||||
return i; |
||||
} |
||||
|
||||
#if defined(CUSTOM_MODES) |
||||
void get_required_bits(opus_int16 *_bits,int _n,int _maxk,int _frac){ |
||||
int k; |
||||
/*_maxk==0 => there's nothing to do.*/ |
||||
celt_assert(_maxk>0); |
||||
_bits[0]=0; |
||||
if (_n==1) |
||||
{ |
||||
for (k=1;k<=_maxk;k++) |
||||
_bits[k] = 1<<_frac; |
||||
} |
||||
else { |
||||
VARDECL(opus_uint32,u); |
||||
SAVE_STACK; |
||||
ALLOC(u,_maxk+2U,opus_uint32); |
||||
ncwrs_urow(_n,_maxk,u); |
||||
for(k=1;k<=_maxk;k++) |
||||
_bits[k]=log2_frac(u[k]+u[k+1],_frac); |
||||
RESTORE_STACK; |
||||
} |
||||
} |
||||
#endif /* CUSTOM_MODES */ |
||||
|
||||
void encode_pulses(const int *_y,int _n,int _k,ec_enc *_enc){ |
||||
opus_uint32 i; |
||||
VARDECL(opus_uint32,u); |
||||
opus_uint32 nc; |
||||
SAVE_STACK; |
||||
celt_assert(_k>0); |
||||
ALLOC(u,_k+2U,opus_uint32); |
||||
i=icwrs(_n,_k,&nc,_y,u); |
||||
ec_enc_uint(_enc,i,nc); |
||||
RESTORE_STACK; |
||||
} |
||||
|
||||
opus_val32 decode_pulses(int *_y,int _n,int _k,ec_dec *_dec){ |
||||
VARDECL(opus_uint32,u); |
||||
int ret; |
||||
SAVE_STACK; |
||||
celt_assert(_k>0); |
||||
ALLOC(u,_k+2U,opus_uint32); |
||||
ret = cwrsi(_n,_k,ec_dec_uint(_dec,ncwrs_urow(_n,_k,u)),_y,u); |
||||
RESTORE_STACK; |
||||
return ret; |
||||
} |
||||
|
||||
#endif /* SMALL_FOOTPRINT */ |
||||
@ -0,0 +1,48 @@
|
||||
/* Copyright (c) 2007-2008 CSIRO
|
||||
Copyright (c) 2007-2009 Xiph.Org Foundation |
||||
Copyright (c) 2007-2009 Timothy B. Terriberry |
||||
Written by Timothy B. Terriberry and Jean-Marc Valin */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef CWRS_H |
||||
#define CWRS_H |
||||
|
||||
#include "arch.h" |
||||
#include "stack_alloc.h" |
||||
#include "entenc.h" |
||||
#include "entdec.h" |
||||
|
||||
#ifdef CUSTOM_MODES |
||||
int log2_frac(opus_uint32 val, int frac); |
||||
#endif |
||||
|
||||
void get_required_bits(opus_int16 *bits, int N, int K, int frac); |
||||
|
||||
void encode_pulses(const int *_y, int N, int K, ec_enc *enc); |
||||
|
||||
opus_val32 decode_pulses(int *_y, int N, int K, ec_dec *dec); |
||||
|
||||
#endif /* CWRS_H */ |
||||
@ -0,0 +1,91 @@
|
||||
/* Copyright (c) 2003-2008 Timothy B. Terriberry
|
||||
Copyright (c) 2008 Xiph.Org Foundation */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
/*Some common macros for potential platform-specific optimization.*/ |
||||
#include "opus_types.h" |
||||
#include <math.h> |
||||
#include <limits.h> |
||||
#include "arch.h" |
||||
#if !defined(_ecintrin_H) |
||||
# define _ecintrin_H (1) |
||||
|
||||
/*Some specific platforms may have optimized intrinsic or OPUS_INLINE assembly
|
||||
versions of these functions which can substantially improve performance. |
||||
We define macros for them to allow easy incorporation of these non-ANSI |
||||
features.*/ |
||||
|
||||
/*Modern gcc (4.x) can compile the naive versions of min and max with cmov if
|
||||
given an appropriate architecture, but the branchless bit-twiddling versions |
||||
are just as fast, and do not require any special target architecture. |
||||
Earlier gcc versions (3.x) compiled both code to the same assembly |
||||
instructions, because of the way they represented ((_b)>(_a)) internally.*/ |
||||
# define EC_MINI(_a,_b) ((_a)+(((_b)-(_a))&-((_b)<(_a)))) |
||||
|
||||
/*Count leading zeros.
|
||||
This macro should only be used for implementing ec_ilog(), if it is defined. |
||||
All other code should use EC_ILOG() instead.*/ |
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1400) |
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1910) |
||||
# include <intrin0.h> /* Improve compiler throughput. */ |
||||
#else |
||||
# include <intrin.h> |
||||
#endif |
||||
/*In _DEBUG mode this is not an intrinsic by default.*/ |
||||
# pragma intrinsic(_BitScanReverse) |
||||
|
||||
static __inline int ec_bsr(unsigned long _x){ |
||||
unsigned long ret; |
||||
_BitScanReverse(&ret,_x); |
||||
return (int)ret; |
||||
} |
||||
# define EC_CLZ0 (1) |
||||
# define EC_CLZ(_x) (-ec_bsr(_x)) |
||||
#elif defined(ENABLE_TI_DSPLIB) |
||||
# include "dsplib.h" |
||||
# define EC_CLZ0 (31) |
||||
# define EC_CLZ(_x) (_lnorm(_x)) |
||||
#elif __GNUC_PREREQ(3,4) |
||||
# if INT_MAX>=2147483647 |
||||
# define EC_CLZ0 ((int)sizeof(unsigned)*CHAR_BIT) |
||||
# define EC_CLZ(_x) (__builtin_clz(_x)) |
||||
# elif LONG_MAX>=2147483647L |
||||
# define EC_CLZ0 ((int)sizeof(unsigned long)*CHAR_BIT) |
||||
# define EC_CLZ(_x) (__builtin_clzl(_x)) |
||||
# endif |
||||
#endif |
||||
|
||||
#if defined(EC_CLZ) |
||||
/*Note that __builtin_clz is not defined when _x==0, according to the gcc
|
||||
documentation (and that of the BSR instruction that implements it on x86). |
||||
The majority of the time we can never pass it zero. |
||||
When we need to, it can be special cased.*/ |
||||
# define EC_ILOG(_x) (EC_CLZ0-EC_CLZ(_x)) |
||||
#else |
||||
int ec_ilog(opus_uint32 _v); |
||||
# define EC_ILOG(_x) (ec_ilog(_x)) |
||||
#endif |
||||
#endif |
||||
@ -0,0 +1,153 @@
|
||||
/* Copyright (c) 2001-2011 Timothy B. Terriberry
|
||||
*/ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifdef HAVE_CONFIG_H |
||||
#include "config.h" |
||||
#endif |
||||
|
||||
#include "entcode.h" |
||||
#include "arch.h" |
||||
|
||||
#if !defined(EC_CLZ) |
||||
/*This is a fallback for systems where we don't know how to access
|
||||
a BSR or CLZ instruction (see ecintrin.h). |
||||
If you are optimizing Opus on a new platform and it has a native CLZ or |
||||
BZR (e.g. cell, MIPS, x86, etc) then making it available to Opus will be |
||||
an easy performance win.*/ |
||||
int ec_ilog(opus_uint32 _v){ |
||||
/*On a Pentium M, this branchless version tested as the fastest on
|
||||
1,000,000,000 random 32-bit integers, edging out a similar version with |
||||
branches, and a 256-entry LUT version.*/ |
||||
int ret; |
||||
int m; |
||||
ret=!!_v; |
||||
m=!!(_v&0xFFFF0000)<<4; |
||||
_v>>=m; |
||||
ret|=m; |
||||
m=!!(_v&0xFF00)<<3; |
||||
_v>>=m; |
||||
ret|=m; |
||||
m=!!(_v&0xF0)<<2; |
||||
_v>>=m; |
||||
ret|=m; |
||||
m=!!(_v&0xC)<<1; |
||||
_v>>=m; |
||||
ret|=m; |
||||
ret+=!!(_v&0x2); |
||||
return ret; |
||||
} |
||||
#endif |
||||
|
||||
#if 1 |
||||
/* This is a faster version of ec_tell_frac() that takes advantage
|
||||
of the low (1/8 bit) resolution to use just a linear function |
||||
followed by a lookup to determine the exact transition thresholds. */ |
||||
opus_uint32 ec_tell_frac(ec_ctx *_this){ |
||||
static const unsigned correction[8] = |
||||
{35733, 38967, 42495, 46340, |
||||
50535, 55109, 60097, 65535}; |
||||
opus_uint32 nbits; |
||||
opus_uint32 r; |
||||
int l; |
||||
unsigned b; |
||||
nbits=_this->nbits_total<<BITRES; |
||||
l=EC_ILOG(_this->rng); |
||||
r=_this->rng>>(l-16); |
||||
b = (r>>12)-8; |
||||
b += r>correction[b]; |
||||
l = (l<<3)+b; |
||||
return nbits-l; |
||||
} |
||||
#else |
||||
opus_uint32 ec_tell_frac(ec_ctx *_this){ |
||||
opus_uint32 nbits; |
||||
opus_uint32 r; |
||||
int l; |
||||
int i; |
||||
/*To handle the non-integral number of bits still left in the encoder/decoder
|
||||
state, we compute the worst-case number of bits of val that must be |
||||
encoded to ensure that the value is inside the range for any possible |
||||
subsequent bits. |
||||
The computation here is independent of val itself (the decoder does not |
||||
even track that value), even though the real number of bits used after |
||||
ec_enc_done() may be 1 smaller if rng is a power of two and the |
||||
corresponding trailing bits of val are all zeros. |
||||
If we did try to track that special case, then coding a value with a |
||||
probability of 1/(1<<n) might sometimes appear to use more than n bits. |
||||
This may help explain the surprising result that a newly initialized |
||||
encoder or decoder claims to have used 1 bit.*/ |
||||
nbits=_this->nbits_total<<BITRES; |
||||
l=EC_ILOG(_this->rng); |
||||
r=_this->rng>>(l-16); |
||||
for(i=BITRES;i-->0;){ |
||||
int b; |
||||
r=r*r>>15; |
||||
b=(int)(r>>16); |
||||
l=l<<1|b; |
||||
r>>=b; |
||||
} |
||||
return nbits-l; |
||||
} |
||||
#endif |
||||
|
||||
#ifdef USE_SMALL_DIV_TABLE |
||||
/* Result of 2^32/(2*i+1), except for i=0. */ |
||||
const opus_uint32 SMALL_DIV_TABLE[129] = { |
||||
0xFFFFFFFF, 0x55555555, 0x33333333, 0x24924924, |
||||
0x1C71C71C, 0x1745D174, 0x13B13B13, 0x11111111, |
||||
0x0F0F0F0F, 0x0D79435E, 0x0C30C30C, 0x0B21642C, |
||||
0x0A3D70A3, 0x097B425E, 0x08D3DCB0, 0x08421084, |
||||
0x07C1F07C, 0x07507507, 0x06EB3E45, 0x06906906, |
||||
0x063E7063, 0x05F417D0, 0x05B05B05, 0x0572620A, |
||||
0x05397829, 0x05050505, 0x04D4873E, 0x04A7904A, |
||||
0x047DC11F, 0x0456C797, 0x04325C53, 0x04104104, |
||||
0x03F03F03, 0x03D22635, 0x03B5CC0E, 0x039B0AD1, |
||||
0x0381C0E0, 0x0369D036, 0x03531DEC, 0x033D91D2, |
||||
0x0329161F, 0x03159721, 0x03030303, 0x02F14990, |
||||
0x02E05C0B, 0x02D02D02, 0x02C0B02C, 0x02B1DA46, |
||||
0x02A3A0FD, 0x0295FAD4, 0x0288DF0C, 0x027C4597, |
||||
0x02702702, 0x02647C69, 0x02593F69, 0x024E6A17, |
||||
0x0243F6F0, 0x0239E0D5, 0x02302302, 0x0226B902, |
||||
0x021D9EAD, 0x0214D021, 0x020C49BA, 0x02040810, |
||||
0x01FC07F0, 0x01F44659, 0x01ECC07B, 0x01E573AC, |
||||
0x01DE5D6E, 0x01D77B65, 0x01D0CB58, 0x01CA4B30, |
||||
0x01C3F8F0, 0x01BDD2B8, 0x01B7D6C3, 0x01B20364, |
||||
0x01AC5701, 0x01A6D01A, 0x01A16D3F, 0x019C2D14, |
||||
0x01970E4F, 0x01920FB4, 0x018D3018, 0x01886E5F, |
||||
0x0183C977, 0x017F405F, 0x017AD220, 0x01767DCE, |
||||
0x01724287, 0x016E1F76, 0x016A13CD, 0x01661EC6, |
||||
0x01623FA7, 0x015E75BB, 0x015AC056, 0x01571ED3, |
||||
0x01539094, 0x01501501, 0x014CAB88, 0x0149539E, |
||||
0x01460CBC, 0x0142D662, 0x013FB013, 0x013C995A, |
||||
0x013991C2, 0x013698DF, 0x0133AE45, 0x0130D190, |
||||
0x012E025C, 0x012B404A, 0x01288B01, 0x0125E227, |
||||
0x01234567, 0x0120B470, 0x011E2EF3, 0x011BB4A4, |
||||
0x01194538, 0x0116E068, 0x011485F0, 0x0112358E, |
||||
0x010FEF01, 0x010DB20A, 0x010B7E6E, 0x010953F3, |
||||
0x01073260, 0x0105197F, 0x0103091B, 0x01010101 |
||||
}; |
||||
#endif |
||||
@ -0,0 +1,152 @@
|
||||
/* Copyright (c) 2001-2011 Timothy B. Terriberry
|
||||
Copyright (c) 2008-2009 Xiph.Org Foundation */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#include "opus_types.h" |
||||
#include "opus_defines.h" |
||||
|
||||
#if !defined(_entcode_H) |
||||
# define _entcode_H (1) |
||||
# include <limits.h> |
||||
# include <stddef.h> |
||||
# include "ecintrin.h" |
||||
|
||||
extern const opus_uint32 SMALL_DIV_TABLE[129]; |
||||
|
||||
#ifdef OPUS_ARM_ASM |
||||
#define USE_SMALL_DIV_TABLE |
||||
#endif |
||||
|
||||
/*OPT: ec_window must be at least 32 bits, but if you have fast arithmetic on a
|
||||
larger type, you can speed up the decoder by using it here.*/ |
||||
typedef opus_uint32 ec_window; |
||||
typedef struct ec_ctx ec_ctx; |
||||
typedef struct ec_ctx ec_enc; |
||||
typedef struct ec_ctx ec_dec; |
||||
|
||||
# define EC_WINDOW_SIZE ((int)sizeof(ec_window)*CHAR_BIT) |
||||
|
||||
/*The number of bits to use for the range-coded part of unsigned integers.*/ |
||||
# define EC_UINT_BITS (8) |
||||
|
||||
/*The resolution of fractional-precision bit usage measurements, i.e.,
|
||||
3 => 1/8th bits.*/ |
||||
# define BITRES 3 |
||||
|
||||
/*The entropy encoder/decoder context.
|
||||
We use the same structure for both, so that common functions like ec_tell() |
||||
can be used on either one.*/ |
||||
struct ec_ctx{ |
||||
/*Buffered input/output.*/ |
||||
unsigned char *buf; |
||||
/*The size of the buffer.*/ |
||||
opus_uint32 storage; |
||||
/*The offset at which the last byte containing raw bits was read/written.*/ |
||||
opus_uint32 end_offs; |
||||
/*Bits that will be read from/written at the end.*/ |
||||
ec_window end_window; |
||||
/*Number of valid bits in end_window.*/ |
||||
int nend_bits; |
||||
/*The total number of whole bits read/written.
|
||||
This does not include partial bits currently in the range coder.*/ |
||||
int nbits_total; |
||||
/*The offset at which the next range coder byte will be read/written.*/ |
||||
opus_uint32 offs; |
||||
/*The number of values in the current range.*/ |
||||
opus_uint32 rng; |
||||
/*In the decoder: the difference between the top of the current range and
|
||||
the input value, minus one. |
||||
In the encoder: the low end of the current range.*/ |
||||
opus_uint32 val; |
||||
/*In the decoder: the saved normalization factor from ec_decode().
|
||||
In the encoder: the number of outstanding carry propagating symbols.*/ |
||||
opus_uint32 ext; |
||||
/*A buffered input/output symbol, awaiting carry propagation.*/ |
||||
int rem; |
||||
/*Nonzero if an error occurred.*/ |
||||
int error; |
||||
}; |
||||
|
||||
static OPUS_INLINE opus_uint32 ec_range_bytes(ec_ctx *_this){ |
||||
return _this->offs; |
||||
} |
||||
|
||||
static OPUS_INLINE unsigned char *ec_get_buffer(ec_ctx *_this){ |
||||
return _this->buf; |
||||
} |
||||
|
||||
static OPUS_INLINE int ec_get_error(ec_ctx *_this){ |
||||
return _this->error; |
||||
} |
||||
|
||||
/*Returns the number of bits "used" by the encoded or decoded symbols so far.
|
||||
This same number can be computed in either the encoder or the decoder, and is |
||||
suitable for making coding decisions. |
||||
Return: The number of bits. |
||||
This will always be slightly larger than the exact value (e.g., all |
||||
rounding error is in the positive direction).*/ |
||||
static OPUS_INLINE int ec_tell(ec_ctx *_this){ |
||||
return _this->nbits_total-EC_ILOG(_this->rng); |
||||
} |
||||
|
||||
/*Returns the number of bits "used" by the encoded or decoded symbols so far.
|
||||
This same number can be computed in either the encoder or the decoder, and is |
||||
suitable for making coding decisions. |
||||
Return: The number of bits scaled by 2**BITRES. |
||||
This will always be slightly larger than the exact value (e.g., all |
||||
rounding error is in the positive direction).*/ |
||||
opus_uint32 ec_tell_frac(ec_ctx *_this); |
||||
|
||||
/* Tested exhaustively for all n and for 1<=d<=256 */ |
||||
static OPUS_INLINE opus_uint32 celt_udiv(opus_uint32 n, opus_uint32 d) { |
||||
celt_sig_assert(d>0); |
||||
#ifdef USE_SMALL_DIV_TABLE |
||||
if (d>256) |
||||
return n/d; |
||||
else { |
||||
opus_uint32 t, q; |
||||
t = EC_ILOG(d&-d); |
||||
q = (opus_uint64)SMALL_DIV_TABLE[d>>t]*(n>>(t-1))>>32; |
||||
return q+(n-q*d >= d); |
||||
} |
||||
#else |
||||
return n/d; |
||||
#endif |
||||
} |
||||
|
||||
static OPUS_INLINE opus_int32 celt_sudiv(opus_int32 n, opus_int32 d) { |
||||
celt_sig_assert(d>0); |
||||
#ifdef USE_SMALL_DIV_TABLE |
||||
if (n<0) |
||||
return -(opus_int32)celt_udiv(-n, d); |
||||
else |
||||
return celt_udiv(n, d); |
||||
#else |
||||
return n/d; |
||||
#endif |
||||
} |
||||
|
||||
#endif |
||||
@ -0,0 +1,266 @@
|
||||
/* Copyright (c) 2001-2011 Timothy B. Terriberry
|
||||
Copyright (c) 2008-2009 Xiph.Org Foundation */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifdef HAVE_CONFIG_H |
||||
#include "config.h" |
||||
#endif |
||||
|
||||
#include <stddef.h> |
||||
#include "os_support.h" |
||||
#include "arch.h" |
||||
#include "entdec.h" |
||||
#include "mfrngcod.h" |
||||
|
||||
/*A range decoder.
|
||||
This is an entropy decoder based upon \cite{Mar79}, which is itself a |
||||
rediscovery of the FIFO arithmetic code introduced by \cite{Pas76}. |
||||
It is very similar to arithmetic encoding, except that encoding is done with |
||||
digits in any base, instead of with bits, and so it is faster when using |
||||
larger bases (i.e.: a byte). |
||||
The author claims an average waste of $\frac{1}{2}\log_b(2b)$ bits, where $b$ |
||||
is the base, longer than the theoretical optimum, but to my knowledge there |
||||
is no published justification for this claim. |
||||
This only seems true when using near-infinite precision arithmetic so that |
||||
the process is carried out with no rounding errors. |
||||
|
||||
An excellent description of implementation details is available at |
||||
http://www.arturocampos.com/ac_range.html
|
||||
A recent work \cite{MNW98} which proposes several changes to arithmetic |
||||
encoding for efficiency actually re-discovers many of the principles |
||||
behind range encoding, and presents a good theoretical analysis of them. |
||||
|
||||
End of stream is handled by writing out the smallest number of bits that |
||||
ensures that the stream will be correctly decoded regardless of the value of |
||||
any subsequent bits. |
||||
ec_tell() can be used to determine how many bits were needed to decode |
||||
all the symbols thus far; other data can be packed in the remaining bits of |
||||
the input buffer. |
||||
@PHDTHESIS{Pas76, |
||||
author="Richard Clark Pasco", |
||||
title="Source coding algorithms for fast data compression", |
||||
school="Dept. of Electrical Engineering, Stanford University", |
||||
address="Stanford, CA", |
||||
month=May, |
||||
year=1976 |
||||
} |
||||
@INPROCEEDINGS{Mar79, |
||||
author="Martin, G.N.N.", |
||||
title="Range encoding: an algorithm for removing redundancy from a digitised |
||||
message", |
||||
booktitle="Video & Data Recording Conference", |
||||
year=1979, |
||||
address="Southampton", |
||||
month=Jul |
||||
} |
||||
@ARTICLE{MNW98, |
||||
author="Alistair Moffat and Radford Neal and Ian H. Witten", |
||||
title="Arithmetic Coding Revisited", |
||||
journal="{ACM} Transactions on Information Systems", |
||||
year=1998, |
||||
volume=16, |
||||
number=3, |
||||
pages="256--294", |
||||
month=Jul, |
||||
URL="http://www.stanford.edu/class/ee398a/handouts/papers/Moffat98ArithmCoding.pdf" |
||||
}*/ |
||||
|
||||
static int ec_read_byte(ec_dec *_this){ |
||||
return _this->offs<_this->storage?_this->buf[_this->offs++]:0; |
||||
} |
||||
|
||||
static int ec_read_byte_from_end(ec_dec *_this){ |
||||
return _this->end_offs<_this->storage? |
||||
_this->buf[_this->storage-++(_this->end_offs)]:0; |
||||
} |
||||
|
||||
/*Normalizes the contents of val and rng so that rng lies entirely in the
|
||||
high-order symbol.*/ |
||||
static void ec_dec_normalize(ec_dec *_this){ |
||||
/*If the range is too small, rescale it and input some bits.*/ |
||||
while(_this->rng<=EC_CODE_BOT){ |
||||
int sym; |
||||
_this->nbits_total+=EC_SYM_BITS; |
||||
_this->rng<<=EC_SYM_BITS; |
||||
/*Use up the remaining bits from our last symbol.*/ |
||||
sym=_this->rem; |
||||
/*Read the next value from the input.*/ |
||||
_this->rem=ec_read_byte(_this); |
||||
/*Take the rest of the bits we need from this new symbol.*/ |
||||
sym=(sym<<EC_SYM_BITS|_this->rem)>>(EC_SYM_BITS-EC_CODE_EXTRA); |
||||
/*And subtract them from val, capped to be less than EC_CODE_TOP.*/ |
||||
_this->val=((_this->val<<EC_SYM_BITS)+(EC_SYM_MAX&~sym))&(EC_CODE_TOP-1); |
||||
} |
||||
} |
||||
|
||||
void ec_dec_init(ec_dec *_this,unsigned char *_buf,opus_uint32 _storage){ |
||||
_this->buf=_buf; |
||||
_this->storage=_storage; |
||||
_this->end_offs=0; |
||||
_this->end_window=0; |
||||
_this->nend_bits=0; |
||||
/*This is the offset from which ec_tell() will subtract partial bits.
|
||||
The final value after the ec_dec_normalize() call will be the same as in |
||||
the encoder, but we have to compensate for the bits that are added there.*/ |
||||
_this->nbits_total=EC_CODE_BITS+1 |
||||
-((EC_CODE_BITS-EC_CODE_EXTRA)/EC_SYM_BITS)*EC_SYM_BITS; |
||||
_this->offs=0; |
||||
_this->rng=1U<<EC_CODE_EXTRA; |
||||
_this->rem=ec_read_byte(_this); |
||||
_this->val=_this->rng-1-(_this->rem>>(EC_SYM_BITS-EC_CODE_EXTRA)); |
||||
_this->error=0; |
||||
/*Normalize the interval.*/ |
||||
ec_dec_normalize(_this); |
||||
} |
||||
|
||||
unsigned ec_decode(ec_dec *_this,unsigned _ft){ |
||||
unsigned s; |
||||
_this->ext=celt_udiv(_this->rng,_ft); |
||||
s=(unsigned)(_this->val/_this->ext); |
||||
return _ft-EC_MINI(s+1,_ft); |
||||
} |
||||
|
||||
unsigned ec_decode_bin(ec_dec *_this,unsigned _bits){ |
||||
unsigned s; |
||||
_this->ext=_this->rng>>_bits; |
||||
s=(unsigned)(_this->val/_this->ext); |
||||
return (1U<<_bits)-EC_MINI(s+1U,1U<<_bits); |
||||
} |
||||
|
||||
void ec_dec_update(ec_dec *_this,unsigned _fl,unsigned _fh,unsigned _ft){ |
||||
opus_uint32 s; |
||||
s=IMUL32(_this->ext,_ft-_fh); |
||||
_this->val-=s; |
||||
_this->rng=_fl>0?IMUL32(_this->ext,_fh-_fl):_this->rng-s; |
||||
ec_dec_normalize(_this); |
||||
} |
||||
|
||||
/*The probability of having a "one" is 1/(1<<_logp).*/ |
||||
int ec_dec_bit_logp(ec_dec *_this,unsigned _logp){ |
||||
opus_uint32 r; |
||||
opus_uint32 d; |
||||
opus_uint32 s; |
||||
int ret; |
||||
r=_this->rng; |
||||
d=_this->val; |
||||
s=r>>_logp; |
||||
ret=d<s; |
||||
if(!ret)_this->val=d-s; |
||||
_this->rng=ret?s:r-s; |
||||
ec_dec_normalize(_this); |
||||
return ret; |
||||
} |
||||
|
||||
int ec_dec_icdf(ec_dec *_this,const unsigned char *_icdf,unsigned _ftb){ |
||||
opus_uint32 r; |
||||
opus_uint32 d; |
||||
opus_uint32 s; |
||||
opus_uint32 t; |
||||
int ret; |
||||
s=_this->rng; |
||||
d=_this->val; |
||||
r=s>>_ftb; |
||||
ret=-1; |
||||
do{ |
||||
t=s; |
||||
s=IMUL32(r,_icdf[++ret]); |
||||
} |
||||
while(d<s); |
||||
_this->val=d-s; |
||||
_this->rng=t-s; |
||||
ec_dec_normalize(_this); |
||||
return ret; |
||||
} |
||||
|
||||
int ec_dec_icdf16(ec_dec *_this,const opus_uint16 *_icdf,unsigned _ftb){ |
||||
opus_uint32 r; |
||||
opus_uint32 d; |
||||
opus_uint32 s; |
||||
opus_uint32 t; |
||||
int ret; |
||||
s=_this->rng; |
||||
d=_this->val; |
||||
r=s>>_ftb; |
||||
ret=-1; |
||||
do{ |
||||
t=s; |
||||
s=IMUL32(r,_icdf[++ret]); |
||||
} |
||||
while(d<s); |
||||
_this->val=d-s; |
||||
_this->rng=t-s; |
||||
ec_dec_normalize(_this); |
||||
return ret; |
||||
} |
||||
|
||||
opus_uint32 ec_dec_uint(ec_dec *_this,opus_uint32 _ft){ |
||||
unsigned ft; |
||||
unsigned s; |
||||
int ftb; |
||||
/*In order to optimize EC_ILOG(), it is undefined for the value 0.*/ |
||||
celt_assert(_ft>1); |
||||
_ft--; |
||||
ftb=EC_ILOG(_ft); |
||||
if(ftb>EC_UINT_BITS){ |
||||
opus_uint32 t; |
||||
ftb-=EC_UINT_BITS; |
||||
ft=(unsigned)(_ft>>ftb)+1; |
||||
s=ec_decode(_this,ft); |
||||
ec_dec_update(_this,s,s+1,ft); |
||||
t=(opus_uint32)s<<ftb|ec_dec_bits(_this,ftb); |
||||
if(t<=_ft)return t; |
||||
_this->error=1; |
||||
return _ft; |
||||
} |
||||
else{ |
||||
_ft++; |
||||
s=ec_decode(_this,(unsigned)_ft); |
||||
ec_dec_update(_this,s,s+1,(unsigned)_ft); |
||||
return s; |
||||
} |
||||
} |
||||
|
||||
opus_uint32 ec_dec_bits(ec_dec *_this,unsigned _bits){ |
||||
ec_window window; |
||||
int available; |
||||
opus_uint32 ret; |
||||
window=_this->end_window; |
||||
available=_this->nend_bits; |
||||
if((unsigned)available<_bits){ |
||||
do{ |
||||
window|=(ec_window)ec_read_byte_from_end(_this)<<available; |
||||
available+=EC_SYM_BITS; |
||||
} |
||||
while(available<=EC_WINDOW_SIZE-EC_SYM_BITS); |
||||
} |
||||
ret=(opus_uint32)window&(((opus_uint32)1<<_bits)-1U); |
||||
window>>=_bits; |
||||
available-=_bits; |
||||
_this->end_window=window; |
||||
_this->nend_bits=available; |
||||
_this->nbits_total+=_bits; |
||||
return ret; |
||||
} |
||||
@ -0,0 +1,110 @@
|
||||
/* Copyright (c) 2001-2011 Timothy B. Terriberry
|
||||
Copyright (c) 2008-2009 Xiph.Org Foundation */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#if !defined(_entdec_H) |
||||
# define _entdec_H (1) |
||||
# include <limits.h> |
||||
# include "entcode.h" |
||||
|
||||
/*Initializes the decoder.
|
||||
_buf: The input buffer to use. |
||||
Return: 0 on success, or a negative value on error.*/ |
||||
void ec_dec_init(ec_dec *_this,unsigned char *_buf,opus_uint32 _storage); |
||||
|
||||
/*Calculates the cumulative frequency for the next symbol.
|
||||
This can then be fed into the probability model to determine what that |
||||
symbol is, and the additional frequency information required to advance to |
||||
the next symbol. |
||||
This function cannot be called more than once without a corresponding call to |
||||
ec_dec_update(), or decoding will not proceed correctly. |
||||
_ft: The total frequency of the symbols in the alphabet the next symbol was |
||||
encoded with. |
||||
Return: A cumulative frequency representing the encoded symbol. |
||||
If the cumulative frequency of all the symbols before the one that |
||||
was encoded was fl, and the cumulative frequency of all the symbols |
||||
up to and including the one encoded is fh, then the returned value |
||||
will fall in the range [fl,fh).*/ |
||||
unsigned ec_decode(ec_dec *_this,unsigned _ft); |
||||
|
||||
/*Equivalent to ec_decode() with _ft==1<<_bits.*/ |
||||
unsigned ec_decode_bin(ec_dec *_this,unsigned _bits); |
||||
|
||||
/*Advance the decoder past the next symbol using the frequency information the
|
||||
symbol was encoded with. |
||||
Exactly one call to ec_decode() must have been made so that all necessary |
||||
intermediate calculations are performed. |
||||
_fl: The cumulative frequency of all symbols that come before the symbol |
||||
decoded. |
||||
_fh: The cumulative frequency of all symbols up to and including the symbol |
||||
decoded. |
||||
Together with _fl, this defines the range [_fl,_fh) in which the value |
||||
returned above must fall. |
||||
_ft: The total frequency of the symbols in the alphabet the symbol decoded |
||||
was encoded in. |
||||
This must be the same as passed to the preceding call to ec_decode().*/ |
||||
void ec_dec_update(ec_dec *_this,unsigned _fl,unsigned _fh,unsigned _ft); |
||||
|
||||
/* Decode a bit that has a 1/(1<<_logp) probability of being a one */ |
||||
int ec_dec_bit_logp(ec_dec *_this,unsigned _logp); |
||||
|
||||
/*Decodes a symbol given an "inverse" CDF table.
|
||||
No call to ec_dec_update() is necessary after this call. |
||||
_icdf: The "inverse" CDF, such that symbol s falls in the range |
||||
[s>0?ft-_icdf[s-1]:0,ft-_icdf[s]), where ft=1<<_ftb. |
||||
The values must be monotonically non-increasing, and the last value |
||||
must be 0. |
||||
_ftb: The number of bits of precision in the cumulative distribution. |
||||
Return: The decoded symbol s.*/ |
||||
int ec_dec_icdf(ec_dec *_this,const unsigned char *_icdf,unsigned _ftb); |
||||
|
||||
/*Decodes a symbol given an "inverse" CDF table.
|
||||
No call to ec_dec_update() is necessary after this call. |
||||
_icdf: The "inverse" CDF, such that symbol s falls in the range |
||||
[s>0?ft-_icdf[s-1]:0,ft-_icdf[s]), where ft=1<<_ftb. |
||||
The values must be monotonically non-increasing, and the last value |
||||
must be 0. |
||||
_ftb: The number of bits of precision in the cumulative distribution. |
||||
Return: The decoded symbol s.*/ |
||||
int ec_dec_icdf16(ec_dec *_this,const opus_uint16 *_icdf,unsigned _ftb); |
||||
|
||||
/*Extracts a raw unsigned integer with a non-power-of-2 range from the stream.
|
||||
The bits must have been encoded with ec_enc_uint(). |
||||
No call to ec_dec_update() is necessary after this call. |
||||
_ft: The number of integers that can be decoded (one more than the max). |
||||
This must be at least 2, and no more than 2**32-1. |
||||
Return: The decoded bits.*/ |
||||
opus_uint32 ec_dec_uint(ec_dec *_this,opus_uint32 _ft); |
||||
|
||||
/*Extracts a sequence of raw bits from the stream.
|
||||
The bits must have been encoded with ec_enc_bits(). |
||||
No call to ec_dec_update() is necessary after this call. |
||||
_ftb: The number of bits to extract. |
||||
This must be between 0 and 25, inclusive. |
||||
Return: The decoded bits.*/ |
||||
opus_uint32 ec_dec_bits(ec_dec *_this,unsigned _ftb); |
||||
|
||||
#endif |
||||
@ -0,0 +1,305 @@
|
||||
/* Copyright (c) 2001-2011 Timothy B. Terriberry
|
||||
Copyright (c) 2008-2009 Xiph.Org Foundation */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#if defined(HAVE_CONFIG_H) |
||||
# include "config.h" |
||||
#endif |
||||
#include "os_support.h" |
||||
#include "arch.h" |
||||
#include "entenc.h" |
||||
#include "mfrngcod.h" |
||||
|
||||
/*A range encoder.
|
||||
See entdec.c and the references for implementation details \cite{Mar79,MNW98}. |
||||
|
||||
@INPROCEEDINGS{Mar79, |
||||
author="Martin, G.N.N.", |
||||
title="Range encoding: an algorithm for removing redundancy from a digitised |
||||
message", |
||||
booktitle="Video \& Data Recording Conference", |
||||
year=1979, |
||||
address="Southampton", |
||||
month=Jul |
||||
} |
||||
@ARTICLE{MNW98, |
||||
author="Alistair Moffat and Radford Neal and Ian H. Witten", |
||||
title="Arithmetic Coding Revisited", |
||||
journal="{ACM} Transactions on Information Systems", |
||||
year=1998, |
||||
volume=16, |
||||
number=3, |
||||
pages="256--294", |
||||
month=Jul, |
||||
URL="http://www.stanford.edu/class/ee398/handouts/papers/Moffat98ArithmCoding.pdf" |
||||
}*/ |
||||
|
||||
static int ec_write_byte(ec_enc *_this,unsigned _value){ |
||||
if(_this->offs+_this->end_offs>=_this->storage)return -1; |
||||
_this->buf[_this->offs++]=(unsigned char)_value; |
||||
return 0; |
||||
} |
||||
|
||||
static int ec_write_byte_at_end(ec_enc *_this,unsigned _value){ |
||||
if(_this->offs+_this->end_offs>=_this->storage)return -1; |
||||
_this->buf[_this->storage-++(_this->end_offs)]=(unsigned char)_value; |
||||
return 0; |
||||
} |
||||
|
||||
/*Outputs a symbol, with a carry bit.
|
||||
If there is a potential to propagate a carry over several symbols, they are |
||||
buffered until it can be determined whether or not an actual carry will |
||||
occur. |
||||
If the counter for the buffered symbols overflows, then the stream becomes |
||||
undecodable. |
||||
This gives a theoretical limit of a few billion symbols in a single packet on |
||||
32-bit systems. |
||||
The alternative is to truncate the range in order to force a carry, but |
||||
requires similar carry tracking in the decoder, needlessly slowing it down.*/ |
||||
static void ec_enc_carry_out(ec_enc *_this,int _c){ |
||||
if(_c!=EC_SYM_MAX){ |
||||
/*No further carry propagation possible, flush buffer.*/ |
||||
int carry; |
||||
carry=_c>>EC_SYM_BITS; |
||||
/*Don't output a byte on the first write.
|
||||
This compare should be taken care of by branch-prediction thereafter.*/ |
||||
if(_this->rem>=0)_this->error|=ec_write_byte(_this,_this->rem+carry); |
||||
if(_this->ext>0){ |
||||
unsigned sym; |
||||
sym=(EC_SYM_MAX+carry)&EC_SYM_MAX; |
||||
do _this->error|=ec_write_byte(_this,sym); |
||||
while(--(_this->ext)>0); |
||||
} |
||||
_this->rem=_c&EC_SYM_MAX; |
||||
} |
||||
else _this->ext++; |
||||
} |
||||
|
||||
static OPUS_INLINE void ec_enc_normalize(ec_enc *_this){ |
||||
/*If the range is too small, output some bits and rescale it.*/ |
||||
while(_this->rng<=EC_CODE_BOT){ |
||||
ec_enc_carry_out(_this,(int)(_this->val>>EC_CODE_SHIFT)); |
||||
/*Move the next-to-high-order symbol into the high-order position.*/ |
||||
_this->val=(_this->val<<EC_SYM_BITS)&(EC_CODE_TOP-1); |
||||
_this->rng<<=EC_SYM_BITS; |
||||
_this->nbits_total+=EC_SYM_BITS; |
||||
} |
||||
} |
||||
|
||||
void ec_enc_init(ec_enc *_this,unsigned char *_buf,opus_uint32 _size){ |
||||
_this->buf=_buf; |
||||
_this->end_offs=0; |
||||
_this->end_window=0; |
||||
_this->nend_bits=0; |
||||
/*This is the offset from which ec_tell() will subtract partial bits.*/ |
||||
_this->nbits_total=EC_CODE_BITS+1; |
||||
_this->offs=0; |
||||
_this->rng=EC_CODE_TOP; |
||||
_this->rem=-1; |
||||
_this->val=0; |
||||
_this->ext=0; |
||||
_this->storage=_size; |
||||
_this->error=0; |
||||
} |
||||
|
||||
void ec_encode(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _ft){ |
||||
opus_uint32 r; |
||||
r=celt_udiv(_this->rng,_ft); |
||||
if(_fl>0){ |
||||
_this->val+=_this->rng-IMUL32(r,(_ft-_fl)); |
||||
_this->rng=IMUL32(r,(_fh-_fl)); |
||||
} |
||||
else _this->rng-=IMUL32(r,(_ft-_fh)); |
||||
ec_enc_normalize(_this); |
||||
} |
||||
|
||||
void ec_encode_bin(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _bits){ |
||||
opus_uint32 r; |
||||
r=_this->rng>>_bits; |
||||
if(_fl>0){ |
||||
_this->val+=_this->rng-IMUL32(r,((1U<<_bits)-_fl)); |
||||
_this->rng=IMUL32(r,(_fh-_fl)); |
||||
} |
||||
else _this->rng-=IMUL32(r,((1U<<_bits)-_fh)); |
||||
ec_enc_normalize(_this); |
||||
} |
||||
|
||||
/*The probability of having a "one" is 1/(1<<_logp).*/ |
||||
void ec_enc_bit_logp(ec_enc *_this,int _val,unsigned _logp){ |
||||
opus_uint32 r; |
||||
opus_uint32 s; |
||||
opus_uint32 l; |
||||
r=_this->rng; |
||||
l=_this->val; |
||||
s=r>>_logp; |
||||
r-=s; |
||||
if(_val)_this->val=l+r; |
||||
_this->rng=_val?s:r; |
||||
ec_enc_normalize(_this); |
||||
} |
||||
|
||||
void ec_enc_icdf(ec_enc *_this,int _s,const unsigned char *_icdf,unsigned _ftb){ |
||||
opus_uint32 r; |
||||
r=_this->rng>>_ftb; |
||||
if(_s>0){ |
||||
_this->val+=_this->rng-IMUL32(r,_icdf[_s-1]); |
||||
_this->rng=IMUL32(r,_icdf[_s-1]-_icdf[_s]); |
||||
} |
||||
else _this->rng-=IMUL32(r,_icdf[_s]); |
||||
ec_enc_normalize(_this); |
||||
} |
||||
|
||||
void ec_enc_icdf16(ec_enc *_this,int _s,const opus_uint16 *_icdf,unsigned _ftb){ |
||||
opus_uint32 r; |
||||
r=_this->rng>>_ftb; |
||||
if(_s>0){ |
||||
_this->val+=_this->rng-IMUL32(r,_icdf[_s-1]); |
||||
_this->rng=IMUL32(r,_icdf[_s-1]-_icdf[_s]); |
||||
} |
||||
else _this->rng-=IMUL32(r,_icdf[_s]); |
||||
ec_enc_normalize(_this); |
||||
} |
||||
|
||||
void ec_enc_uint(ec_enc *_this,opus_uint32 _fl,opus_uint32 _ft){ |
||||
unsigned ft; |
||||
unsigned fl; |
||||
int ftb; |
||||
/*In order to optimize EC_ILOG(), it is undefined for the value 0.*/ |
||||
celt_assert(_ft>1); |
||||
_ft--; |
||||
ftb=EC_ILOG(_ft); |
||||
if(ftb>EC_UINT_BITS){ |
||||
ftb-=EC_UINT_BITS; |
||||
ft=(_ft>>ftb)+1; |
||||
fl=(unsigned)(_fl>>ftb); |
||||
ec_encode(_this,fl,fl+1,ft); |
||||
ec_enc_bits(_this,_fl&(((opus_uint32)1<<ftb)-1U),ftb); |
||||
} |
||||
else ec_encode(_this,_fl,_fl+1,_ft+1); |
||||
} |
||||
|
||||
void ec_enc_bits(ec_enc *_this,opus_uint32 _fl,unsigned _bits){ |
||||
ec_window window; |
||||
int used; |
||||
window=_this->end_window; |
||||
used=_this->nend_bits; |
||||
celt_assert(_bits>0); |
||||
if(used+_bits>EC_WINDOW_SIZE){ |
||||
do{ |
||||
_this->error|=ec_write_byte_at_end(_this,(unsigned)window&EC_SYM_MAX); |
||||
window>>=EC_SYM_BITS; |
||||
used-=EC_SYM_BITS; |
||||
} |
||||
while(used>=EC_SYM_BITS); |
||||
} |
||||
window|=(ec_window)_fl<<used; |
||||
used+=_bits; |
||||
_this->end_window=window; |
||||
_this->nend_bits=used; |
||||
_this->nbits_total+=_bits; |
||||
} |
||||
|
||||
void ec_enc_patch_initial_bits(ec_enc *_this,unsigned _val,unsigned _nbits){ |
||||
int shift; |
||||
unsigned mask; |
||||
celt_assert(_nbits<=EC_SYM_BITS); |
||||
shift=EC_SYM_BITS-_nbits; |
||||
mask=((1<<_nbits)-1)<<shift; |
||||
if(_this->offs>0){ |
||||
/*The first byte has been finalized.*/ |
||||
_this->buf[0]=(unsigned char)((_this->buf[0]&~mask)|_val<<shift); |
||||
} |
||||
else if(_this->rem>=0){ |
||||
/*The first byte is still awaiting carry propagation.*/ |
||||
_this->rem=(_this->rem&~mask)|_val<<shift; |
||||
} |
||||
else if(_this->rng<=(EC_CODE_TOP>>_nbits)){ |
||||
/*The renormalization loop has never been run.*/ |
||||
_this->val=(_this->val&~((opus_uint32)mask<<EC_CODE_SHIFT))| |
||||
(opus_uint32)_val<<(EC_CODE_SHIFT+shift); |
||||
} |
||||
/*The encoder hasn't even encoded _nbits of data yet.*/ |
||||
else _this->error=-1; |
||||
} |
||||
|
||||
void ec_enc_shrink(ec_enc *_this,opus_uint32 _size){ |
||||
celt_assert(_this->offs+_this->end_offs<=_size); |
||||
OPUS_MOVE(_this->buf+_size-_this->end_offs, |
||||
_this->buf+_this->storage-_this->end_offs,_this->end_offs); |
||||
_this->storage=_size; |
||||
} |
||||
|
||||
void ec_enc_done(ec_enc *_this){ |
||||
ec_window window; |
||||
int used; |
||||
opus_uint32 msk; |
||||
opus_uint32 end; |
||||
int l; |
||||
/*We output the minimum number of bits that ensures that the symbols encoded
|
||||
thus far will be decoded correctly regardless of the bits that follow.*/ |
||||
l=EC_CODE_BITS-EC_ILOG(_this->rng); |
||||
msk=(EC_CODE_TOP-1)>>l; |
||||
end=(_this->val+msk)&~msk; |
||||
if((end|msk)>=_this->val+_this->rng){ |
||||
l++; |
||||
msk>>=1; |
||||
end=(_this->val+msk)&~msk; |
||||
} |
||||
while(l>0){ |
||||
ec_enc_carry_out(_this,(int)(end>>EC_CODE_SHIFT)); |
||||
end=(end<<EC_SYM_BITS)&(EC_CODE_TOP-1); |
||||
l-=EC_SYM_BITS; |
||||
} |
||||
/*If we have a buffered byte flush it into the output buffer.*/ |
||||
if(_this->rem>=0||_this->ext>0)ec_enc_carry_out(_this,0); |
||||
/*If we have buffered extra bits, flush them as well.*/ |
||||
window=_this->end_window; |
||||
used=_this->nend_bits; |
||||
while(used>=EC_SYM_BITS){ |
||||
_this->error|=ec_write_byte_at_end(_this,(unsigned)window&EC_SYM_MAX); |
||||
window>>=EC_SYM_BITS; |
||||
used-=EC_SYM_BITS; |
||||
} |
||||
/*Clear any excess space and add any remaining extra bits to the last byte.*/ |
||||
if(!_this->error){ |
||||
if (_this->buf) OPUS_CLEAR(_this->buf+_this->offs, |
||||
_this->storage-_this->offs-_this->end_offs); |
||||
if(used>0){ |
||||
/*If there's no range coder data at all, give up.*/ |
||||
if(_this->end_offs>=_this->storage)_this->error=-1; |
||||
else{ |
||||
l=-l; |
||||
/*If we've busted, don't add too many extra bits to the last byte; it
|
||||
would corrupt the range coder data, and that's more important.*/ |
||||
if(_this->offs+_this->end_offs>=_this->storage&&l<used){ |
||||
window&=(1<<l)-1; |
||||
_this->error=-1; |
||||
} |
||||
_this->buf[_this->storage-_this->end_offs-1]|=(unsigned char)window; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,119 @@
|
||||
/* Copyright (c) 2001-2011 Timothy B. Terriberry
|
||||
Copyright (c) 2008-2009 Xiph.Org Foundation */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#if !defined(_entenc_H) |
||||
# define _entenc_H (1) |
||||
# include <stddef.h> |
||||
# include "entcode.h" |
||||
|
||||
/*Initializes the encoder.
|
||||
_buf: The buffer to store output bytes in. |
||||
_size: The size of the buffer, in chars.*/ |
||||
void ec_enc_init(ec_enc *_this,unsigned char *_buf,opus_uint32 _size); |
||||
/*Encodes a symbol given its frequency information.
|
||||
The frequency information must be discernible by the decoder, assuming it |
||||
has read only the previous symbols from the stream. |
||||
It is allowable to change the frequency information, or even the entire |
||||
source alphabet, so long as the decoder can tell from the context of the |
||||
previously encoded information that it is supposed to do so as well. |
||||
_fl: The cumulative frequency of all symbols that come before the one to be |
||||
encoded. |
||||
_fh: The cumulative frequency of all symbols up to and including the one to |
||||
be encoded. |
||||
Together with _fl, this defines the range [_fl,_fh) in which the |
||||
decoded value will fall. |
||||
_ft: The sum of the frequencies of all the symbols*/ |
||||
void ec_encode(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _ft); |
||||
|
||||
/*Equivalent to ec_encode() with _ft==1<<_bits.*/ |
||||
void ec_encode_bin(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _bits); |
||||
|
||||
/* Encode a bit that has a 1/(1<<_logp) probability of being a one */ |
||||
void ec_enc_bit_logp(ec_enc *_this,int _val,unsigned _logp); |
||||
|
||||
/*Encodes a symbol given an "inverse" CDF table.
|
||||
_s: The index of the symbol to encode. |
||||
_icdf: The "inverse" CDF, such that symbol _s falls in the range |
||||
[_s>0?ft-_icdf[_s-1]:0,ft-_icdf[_s]), where ft=1<<_ftb. |
||||
The values must be monotonically non-increasing, and the last value |
||||
must be 0. |
||||
_ftb: The number of bits of precision in the cumulative distribution.*/ |
||||
void ec_enc_icdf(ec_enc *_this,int _s,const unsigned char *_icdf,unsigned _ftb); |
||||
|
||||
/*Encodes a symbol given an "inverse" CDF table.
|
||||
_s: The index of the symbol to encode. |
||||
_icdf: The "inverse" CDF, such that symbol _s falls in the range |
||||
[_s>0?ft-_icdf[_s-1]:0,ft-_icdf[_s]), where ft=1<<_ftb. |
||||
The values must be monotonically non-increasing, and the last value |
||||
must be 0. |
||||
_ftb: The number of bits of precision in the cumulative distribution.*/ |
||||
void ec_enc_icdf16(ec_enc *_this,int _s,const opus_uint16 *_icdf,unsigned _ftb); |
||||
|
||||
/*Encodes a raw unsigned integer in the stream.
|
||||
_fl: The integer to encode. |
||||
_ft: The number of integers that can be encoded (one more than the max). |
||||
This must be at least 2, and no more than 2**32-1.*/ |
||||
void ec_enc_uint(ec_enc *_this,opus_uint32 _fl,opus_uint32 _ft); |
||||
|
||||
/*Encodes a sequence of raw bits in the stream.
|
||||
_fl: The bits to encode. |
||||
_ftb: The number of bits to encode. |
||||
This must be between 1 and 25, inclusive.*/ |
||||
void ec_enc_bits(ec_enc *_this,opus_uint32 _fl,unsigned _ftb); |
||||
|
||||
/*Overwrites a few bits at the very start of an existing stream, after they
|
||||
have already been encoded. |
||||
This makes it possible to have a few flags up front, where it is easy for |
||||
decoders to access them without parsing the whole stream, even if their |
||||
values are not determined until late in the encoding process, without having |
||||
to buffer all the intermediate symbols in the encoder. |
||||
In order for this to work, at least _nbits bits must have already been |
||||
encoded using probabilities that are an exact power of two. |
||||
The encoder can verify the number of encoded bits is sufficient, but cannot |
||||
check this latter condition. |
||||
_val: The bits to encode (in the least _nbits significant bits). |
||||
They will be decoded in order from most-significant to least. |
||||
_nbits: The number of bits to overwrite. |
||||
This must be no more than 8.*/ |
||||
void ec_enc_patch_initial_bits(ec_enc *_this,unsigned _val,unsigned _nbits); |
||||
|
||||
/*Compacts the data to fit in the target size.
|
||||
This moves up the raw bits at the end of the current buffer so they are at |
||||
the end of the new buffer size. |
||||
The caller must ensure that the amount of data that's already been written |
||||
will fit in the new size. |
||||
_size: The number of bytes in the new buffer. |
||||
This must be large enough to contain the bits already written, and |
||||
must be no larger than the existing size.*/ |
||||
void ec_enc_shrink(ec_enc *_this,opus_uint32 _size); |
||||
|
||||
/*Indicates that there are no more symbols to encode.
|
||||
All remaining output bytes are flushed to the output buffer. |
||||
ec_enc_init() must be called before the encoder can be used again.*/ |
||||
void ec_enc_done(ec_enc *_this); |
||||
|
||||
#endif |
||||
@ -0,0 +1,79 @@
|
||||
/* Copyright (C) 2003 Jean-Marc Valin */ |
||||
/**
|
||||
@file fixed_c5x.h |
||||
@brief Fixed-point operations for the TI C5x DSP family |
||||
*/ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef FIXED_C5X_H |
||||
#define FIXED_C5X_H |
||||
|
||||
#include "dsplib.h" |
||||
|
||||
#undef IMUL32 |
||||
static OPUS_INLINE long IMUL32(long i, long j) |
||||
{ |
||||
long ac0, ac1; |
||||
ac0 = _lmpy(i>>16,j); |
||||
ac1 = ac0 + _lmpy(i,j>>16); |
||||
return _lmpyu(i,j) + (ac1<<16); |
||||
} |
||||
|
||||
#undef MAX16 |
||||
#define MAX16(a,b) _max(a,b) |
||||
|
||||
#undef MIN16 |
||||
#define MIN16(a,b) _min(a,b) |
||||
|
||||
#undef MAX32 |
||||
#define MAX32(a,b) _lmax(a,b) |
||||
|
||||
#undef MIN32 |
||||
#define MIN32(a,b) _lmin(a,b) |
||||
|
||||
#undef VSHR32 |
||||
#define VSHR32(a, shift) _lshl(a,-(shift)) |
||||
|
||||
#undef MULT16_16_Q15 |
||||
#define MULT16_16_Q15(a,b) (_smpy(a,b)) |
||||
|
||||
#undef MULT16_16SU |
||||
#define MULT16_16SU(a,b) _lmpysu(a,b) |
||||
|
||||
#undef MULT_16_16 |
||||
#define MULT_16_16(a,b) _lmpy(a,b) |
||||
|
||||
/* FIXME: This is technically incorrect and is bound to cause problems. Is there any cleaner solution? */ |
||||
#undef MULT16_32_Q15 |
||||
#define MULT16_32_Q15(a,b) ADD32(SHL(MULT16_16((a),SHR((b),16)),1), SHR(MULT16_16SU((a),(b)),15)) |
||||
|
||||
#define celt_ilog2(x) (30 - _lnorm(x)) |
||||
#define OVERRIDE_CELT_ILOG2 |
||||
|
||||
#define celt_maxabs16(x, len) MAX32(EXTEND32(maxval((DATA *)x, len)),-EXTEND32(minval((DATA *)x, len))) |
||||
#define OVERRIDE_CELT_MAXABS16 |
||||
|
||||
#endif /* FIXED_C5X_H */ |
||||
@ -0,0 +1,70 @@
|
||||
/* Copyright (C) 2008 CSIRO */ |
||||
/**
|
||||
@file fixed_c6x.h |
||||
@brief Fixed-point operations for the TI C6x DSP family |
||||
*/ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef FIXED_C6X_H |
||||
#define FIXED_C6X_H |
||||
|
||||
#undef MULT16_16SU |
||||
#define MULT16_16SU(a,b) _mpysu(a,b) |
||||
|
||||
#undef MULT_16_16 |
||||
#define MULT_16_16(a,b) _mpy(a,b) |
||||
|
||||
#define celt_ilog2(x) (30 - _norm(x)) |
||||
#define OVERRIDE_CELT_ILOG2 |
||||
|
||||
#undef MULT16_32_Q15 |
||||
#define MULT16_32_Q15(a,b) (_mpylill(a, b) >> 15) |
||||
|
||||
#if 0 |
||||
#include "dsplib.h" |
||||
|
||||
#undef MAX16 |
||||
#define MAX16(a,b) _max(a,b) |
||||
|
||||
#undef MIN16 |
||||
#define MIN16(a,b) _min(a,b) |
||||
|
||||
#undef MAX32 |
||||
#define MAX32(a,b) _lmax(a,b) |
||||
|
||||
#undef MIN32 |
||||
#define MIN32(a,b) _lmin(a,b) |
||||
|
||||
#undef VSHR32 |
||||
#define VSHR32(a, shift) _lshl(a,-(shift)) |
||||
|
||||
#undef MULT16_16_Q15 |
||||
#define MULT16_16_Q15(a,b) (_smpy(a,b)) |
||||
|
||||
#define celt_maxabs16(x, len) MAX32(EXTEND32(maxval((DATA *)x, len)),-EXTEND32(minval((DATA *)x, len))) |
||||
#define OVERRIDE_CELT_MAXABS16 |
||||
|
||||
#endif /* FIXED_C6X_H */ |
||||
@ -0,0 +1,848 @@
|
||||
/* Copyright (C) 2003-2008 Jean-Marc Valin
|
||||
Copyright (C) 2007-2012 Xiph.Org Foundation */ |
||||
/**
|
||||
@file fixed_debug.h |
||||
@brief Fixed-point operations with debugging |
||||
*/ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef FIXED_DEBUG_H |
||||
#define FIXED_DEBUG_H |
||||
|
||||
#include <stdio.h> |
||||
#include "opus_defines.h" |
||||
|
||||
#ifdef CELT_C |
||||
OPUS_EXPORT opus_int64 celt_mips=0; |
||||
#else |
||||
extern opus_int64 celt_mips; |
||||
#endif |
||||
|
||||
#define MULT16_16U(a,b) ((opus_uint32)(a)*(opus_uint32)(b)) |
||||
#define MULT16_16SU(a,b) ((opus_val32)(opus_val16)(a)*(opus_val32)(opus_uint16)(b)) |
||||
#define MULT32_32_Q31(a,b) ADD32(ADD32(SHL32(MULT16_16(SHR32((a),16),SHR((b),16)),1), SHR32(MULT16_16SU(SHR32((a),16),((b)&0x0000ffff)),15)), SHR32(MULT16_16SU(SHR32((b),16),((a)&0x0000ffff)),15)) |
||||
#define MULT32_32_P31(a,b) ADD32(SHL32(MULT16_16(SHR((a),16),SHR((b),16)),1), SHR32(128+(opus_int32)(MULT16_16U(((a)&0x0000ffff),((b)&0x0000ffff))>>(16+7)) + SHR32(MULT16_16SU(SHR((a),16),((b)&0x0000ffff)),7) + SHR32(MULT16_16SU(SHR((b),16),((a)&0x0000ffff)),7), 8) ) |
||||
#define MULT32_32_P31_ovflw(a,b) ADD32_ovflw(SHL32(MULT16_16(SHR((a),16),SHR((b),16)),1), SHR32(128+(opus_int32)(MULT16_16U(((a)&0x0000ffff),((b)&0x0000ffff))>>(16+7)) + SHR32(MULT16_16SU(SHR((a),16),((b)&0x0000ffff)),7) + SHR32(MULT16_16SU(SHR((b),16),((a)&0x0000ffff)),7), 8) ) |
||||
#define MULT32_32_Q32(a,b) ADD32(ADD32(MULT16_16(SHR((a),16),SHR((b),16)), SHR(MULT16_16SU(SHR((a),16),((b)&0x0000ffff)),16)), SHR(MULT16_16SU(SHR((b),16),((a)&0x0000ffff)),16)) |
||||
|
||||
/** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */ |
||||
#define MULT16_32_Q16(a,b) ADD32(MULT16_16((a),SHR32((b),16)), SHR32(MULT16_16SU((a),((b)&0x0000ffff)),16)) |
||||
|
||||
#define MULT16_32_P16(a,b) MULT16_32_PX(a,b,16) |
||||
|
||||
#define QCONST16(x,bits) ((opus_val16)(.5+(x)*(((opus_val32)1)<<(bits)))) |
||||
#define QCONST32(x,bits) ((opus_val32)(.5+(x)*(((opus_val64)1)<<(bits)))) |
||||
#define GCONST2(x,bits) ((celt_glog)(.5+(x)*(((celt_glog)1)<<(bits)))) |
||||
#define GCONST(x) GCONST2((x),DB_SHIFT) |
||||
|
||||
#define VERIFY_SHORT(x) ((x)<=32767&&(x)>=-32768) |
||||
#define VERIFY_INT(x) ((x)<=2147483647LL&&(x)>=-2147483648LL) |
||||
#define VERIFY_UINT(x) ((x)<=(2147483647LLU<<1)) |
||||
|
||||
#define SHR(a,b) SHR32(a,b) |
||||
#define PSHR(a,b) PSHR32(a,b) |
||||
|
||||
/** Add two 32-bit values, ignore any overflows */ |
||||
#define ADD32_ovflw(a,b) (celt_mips+=2,(opus_val32)((opus_uint32)(a)+(opus_uint32)(b))) |
||||
/** Subtract two 32-bit values, ignore any overflows */ |
||||
#define SUB32_ovflw(a,b) (celt_mips+=2,(opus_val32)((opus_uint32)(a)-(opus_uint32)(b))) |
||||
/* Avoid MSVC warning C4146: unary minus operator applied to unsigned type */ |
||||
/** Negate 32-bit value, ignore any overflows */ |
||||
#define NEG32_ovflw(a) (celt_mips+=2,(opus_val32)(0-(opus_uint32)(a))) |
||||
/** 32-bit shift left, ignoring overflows */ |
||||
#define SHL32_ovflw(a,shift) ((opus_int32)((opus_uint32)(a)<<(shift))) |
||||
/** 32-bit arithmetic shift right with rounding-to-nearest, ignoring overflows */ |
||||
#define PSHR32_ovflw(a,shift) (SHR32(ADD32_ovflw(a, (EXTEND32(1)<<(shift)>>1)),shift)) |
||||
|
||||
static OPUS_INLINE short NEG16(int x) |
||||
{ |
||||
int res; |
||||
if (!VERIFY_SHORT(x)) |
||||
{ |
||||
fprintf (stderr, "NEG16: input is not short: %d\n", (int)x); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = -x; |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "NEG16: output is not short: %d\n", (int)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips++; |
||||
return res; |
||||
} |
||||
static OPUS_INLINE int NEG32(opus_int64 x) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_INT(x)) |
||||
{ |
||||
fprintf (stderr, "NEG16: input is not int: %d\n", (int)x); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = -x; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "NEG16: output is not int: %d\n", (int)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=2; |
||||
return res; |
||||
} |
||||
|
||||
#define EXTRACT16(x) EXTRACT16_(x, __FILE__, __LINE__) |
||||
static OPUS_INLINE short EXTRACT16_(int x, char *file, int line) |
||||
{ |
||||
int res; |
||||
if (!VERIFY_SHORT(x)) |
||||
{ |
||||
fprintf (stderr, "EXTRACT16: input is not short: %d in %s: line %d\n", x, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = x; |
||||
celt_mips++; |
||||
return res; |
||||
} |
||||
|
||||
#define EXTEND32(x) EXTEND32_(x, __FILE__, __LINE__) |
||||
static OPUS_INLINE int EXTEND32_(int x, char *file, int line) |
||||
{ |
||||
int res; |
||||
if (!VERIFY_SHORT(x)) |
||||
{ |
||||
fprintf (stderr, "EXTEND32: input is not short: %d in %s: line %d\n", x, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = x; |
||||
celt_mips++; |
||||
return res; |
||||
} |
||||
|
||||
#define SHR16(a, shift) SHR16_(a, shift, __FILE__, __LINE__) |
||||
static OPUS_INLINE short SHR16_(int a, int shift, char *file, int line) |
||||
{ |
||||
int res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(shift)) |
||||
{ |
||||
fprintf (stderr, "SHR16: inputs are not short: %d >> %d in %s: line %d\n", a, shift, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = a>>shift; |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "SHR16: output is not short: %d in %s: line %d\n", res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips++; |
||||
return res; |
||||
} |
||||
#define SHL16(a, shift) SHL16_(a, shift, __FILE__, __LINE__) |
||||
static OPUS_INLINE short SHL16_(int a, int shift, char *file, int line) |
||||
{ |
||||
opus_int32 res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(shift)) |
||||
{ |
||||
fprintf (stderr, "SHL16: inputs are not short: %d %d in %s: line %d\n", a, shift, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = (opus_int32)((opus_uint32)a<<shift); |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "SHL16: output is not short: %d in %s: line %d\n", res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips++; |
||||
return res; |
||||
} |
||||
|
||||
static OPUS_INLINE int SHR32(opus_int64 a, int shift) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_INT(a) || !VERIFY_SHORT(shift)) |
||||
{ |
||||
fprintf (stderr, "SHR32: inputs are not int: %d %d\n", (int)a, shift); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = a>>shift; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "SHR32: output is not int: %d\n", (int)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=2; |
||||
return res; |
||||
} |
||||
#define SHL32(a, shift) SHL32_(a, shift, __FILE__, __LINE__) |
||||
static OPUS_INLINE int SHL32_(opus_int64 a, int shift, char *file, int line) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_INT(a) || !VERIFY_SHORT(shift)) |
||||
{ |
||||
fprintf (stderr, "SHL32: inputs are not int: %lld %d in %s: line %d\n", (long long)a, shift, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = (opus_int64)((opus_uint64)a<<shift); |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "SHL32: output is not int: %lld<<%d = %lld in %s: line %d\n", (long long)a, shift, (long long)res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=2; |
||||
return res; |
||||
} |
||||
|
||||
#define PSHR32(a,shift) (celt_mips--,SHR32(ADD32((a),(((opus_val32)(1)<<((shift))>>1))),shift)) |
||||
#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) |
||||
|
||||
#define SHR64(a,shift) (celt_mips++,(a) >> (shift)) |
||||
|
||||
#define ROUND16(x,a) (celt_mips--,EXTRACT16(PSHR32((x),(a)))) |
||||
#define SROUND16(x,a) (celt_mips--,EXTRACT16(SATURATE(PSHR32(x,a), 32767))); |
||||
|
||||
#define HALF16(x) (SHR16(x,1)) |
||||
#define HALF32(x) (SHR32(x,1)) |
||||
|
||||
#define ADD16(a, b) ADD16_(a, b, __FILE__, __LINE__) |
||||
static OPUS_INLINE short ADD16_(int a, int b, char *file, int line) |
||||
{ |
||||
int res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) |
||||
{ |
||||
fprintf (stderr, "ADD16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = a+b; |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "ADD16: output is not short: %d+%d=%d in %s: line %d\n", a,b,res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips++; |
||||
return res; |
||||
} |
||||
|
||||
#define SUB16(a, b) SUB16_(a, b, __FILE__, __LINE__) |
||||
static OPUS_INLINE short SUB16_(int a, int b, char *file, int line) |
||||
{ |
||||
int res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) |
||||
{ |
||||
fprintf (stderr, "SUB16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = a-b; |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "SUB16: output is not short: %d in %s: line %d\n", res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips++; |
||||
return res; |
||||
} |
||||
|
||||
#define ADD32(a, b) ADD32_(a, b, __FILE__, __LINE__) |
||||
static OPUS_INLINE int ADD32_(opus_int64 a, opus_int64 b, char *file, int line) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_INT(a) || !VERIFY_INT(b)) |
||||
{ |
||||
fprintf (stderr, "ADD32: inputs are not int: %d %d in %s: line %d\n", (int)a, (int)b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = a+b; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "ADD32: output is not int: %d in %s: line %d\n", (int)res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=2; |
||||
return res; |
||||
} |
||||
|
||||
#define SUB32(a, b) SUB32_(a, b, __FILE__, __LINE__) |
||||
static OPUS_INLINE int SUB32_(opus_int64 a, opus_int64 b, char *file, int line) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_INT(a) || !VERIFY_INT(b)) |
||||
{ |
||||
fprintf (stderr, "SUB32: inputs are not int: %d %d in %s: line %d\n", (int)a, (int)b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = a-b; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "SUB32: output is not int: %d in %s: line %d\n", (int)res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=2; |
||||
return res; |
||||
} |
||||
|
||||
#undef UADD32 |
||||
#define UADD32(a, b) UADD32_(a, b, __FILE__, __LINE__) |
||||
static OPUS_INLINE unsigned int UADD32_(opus_uint64 a, opus_uint64 b, char *file, int line) |
||||
{ |
||||
opus_uint64 res; |
||||
if (!VERIFY_UINT(a) || !VERIFY_UINT(b)) |
||||
{ |
||||
fprintf (stderr, "UADD32: inputs are not uint32: %llu %llu in %s: line %d\n", (unsigned long long)a, (unsigned long long)b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = a+b; |
||||
if (!VERIFY_UINT(res)) |
||||
{ |
||||
fprintf (stderr, "UADD32: output is not uint32: %llu in %s: line %d\n", (unsigned long long)res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=2; |
||||
return res; |
||||
} |
||||
|
||||
#undef USUB32 |
||||
#define USUB32(a, b) USUB32_(a, b, __FILE__, __LINE__) |
||||
static OPUS_INLINE unsigned int USUB32_(opus_uint64 a, opus_uint64 b, char *file, int line) |
||||
{ |
||||
opus_uint64 res; |
||||
if (!VERIFY_UINT(a) || !VERIFY_UINT(b)) |
||||
{ |
||||
fprintf (stderr, "USUB32: inputs are not uint32: %llu %llu in %s: line %d\n", (unsigned long long)a, (unsigned long long)b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
if (a<b) |
||||
{ |
||||
fprintf (stderr, "USUB32: inputs underflow: %llu < %llu in %s: line %d\n", (unsigned long long)a, (unsigned long long)b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = a-b; |
||||
if (!VERIFY_UINT(res)) |
||||
{ |
||||
fprintf (stderr, "USUB32: output is not uint32: %llu - %llu = %llu in %s: line %d\n", (unsigned long long)a, (unsigned long long)b, (unsigned long long)res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=2; |
||||
return res; |
||||
} |
||||
|
||||
/* result fits in 16 bits */ |
||||
static OPUS_INLINE short MULT16_16_16(int a, int b) |
||||
{ |
||||
int res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_16: inputs are not short: %d %d\n", a, b); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = a*b; |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_16: output is not short: %d\n", res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips++; |
||||
return res; |
||||
} |
||||
|
||||
/* result fits in 32 bits */ |
||||
static OPUS_INLINE int MULT32_32_32(opus_int64 a, opus_int64 b) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_INT(a) || !VERIFY_INT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT32_32_32: inputs are not int: %lld %lld\n", (long long)a, (long long)b); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = a*b; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT32_32_32: output is not int: %lld\n", (long long)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=5; |
||||
return res; |
||||
} |
||||
|
||||
static OPUS_INLINE int MULT32_32_Q16(opus_int64 a, opus_int64 b) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_INT(a) || !VERIFY_INT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT32_32_Q16: inputs are not int: %lld %lld\n", (long long)a, (long long)b); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = ((opus_int64)(a)*(opus_int64)(b)) >> 16; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT32_32_Q16: output is not int: %lld*%lld=%lld\n", (long long)a, (long long)b, (long long)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=5; |
||||
return res; |
||||
} |
||||
|
||||
#define MULT16_16(a, b) MULT16_16_(a, b, __FILE__, __LINE__) |
||||
static OPUS_INLINE int MULT16_16_(int a, int b, char *file, int line) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = ((opus_int64)a)*b; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16: output is not int: %d in %s: line %d\n", (int)res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips++; |
||||
return res; |
||||
} |
||||
|
||||
#define MAC16_16(c,a,b) (celt_mips-=2,ADD32((c),MULT16_16((a),(b)))) |
||||
|
||||
#define MULT16_32_QX(a, b, Q) MULT16_32_QX_(a, b, Q, __FILE__, __LINE__) |
||||
static OPUS_INLINE int MULT16_32_QX_(int a, opus_int64 b, int Q, char *file, int line) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_INT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT16_32_Q%d: inputs are not short+int: %d %d in %s: line %d\n", Q, (int)a, (int)b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
if (ABS32(b)>=((opus_int64)(1)<<(16+Q))) |
||||
{ |
||||
fprintf (stderr, "MULT16_32_Q%d: second operand too large: %d %d in %s: line %d\n", Q, (int)a, (int)b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = (((opus_int64)a)*(opus_int64)b) >> Q; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_32_Q%d: output is not int: %d*%d=%d in %s: line %d\n", Q, (int)a, (int)b,(int)res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
if (Q==15) |
||||
celt_mips+=3; |
||||
else |
||||
celt_mips+=4; |
||||
return res; |
||||
} |
||||
|
||||
#define MULT16_32_PX(a, b, Q) MULT16_32_PX_(a, b, Q, __FILE__, __LINE__) |
||||
static OPUS_INLINE int MULT16_32_PX_(int a, opus_int64 b, int Q, char *file, int line) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_INT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT16_32_P%d: inputs are not short+int: %d %d in %s: line %d\n\n", Q, (int)a, (int)b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
if (ABS32(b)>=((opus_int64)(1)<<(16+Q))) |
||||
{ |
||||
fprintf (stderr, "MULT16_32_Q%d: second operand too large: %d %d in %s: line %d\n\n", Q, (int)a, (int)b,file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = ((((opus_int64)a)*(opus_int64)b) + (((opus_val32)(1)<<Q)>>1))>> Q; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_32_P%d: output is not int: %d*%d=%d in %s: line %d\n\n", Q, (int)a, (int)b,(int)res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
if (Q==15) |
||||
celt_mips+=4; |
||||
else |
||||
celt_mips+=5; |
||||
return res; |
||||
} |
||||
|
||||
#define MULT16_32_Q15(a,b) MULT16_32_QX(a,b,15) |
||||
#define MAC16_32_Q15(c,a,b) (celt_mips-=2,ADD32((c),MULT16_32_Q15((a),(b)))) |
||||
#define MAC16_32_Q16(c,a,b) (celt_mips-=2,ADD32((c),MULT16_32_Q16((a),(b)))) |
||||
|
||||
static OPUS_INLINE int SATURATE(int a, int b) |
||||
{ |
||||
if (a>b) |
||||
a=b; |
||||
if (a<-b) |
||||
a = -b; |
||||
celt_mips+=3; |
||||
return a; |
||||
} |
||||
|
||||
static OPUS_INLINE opus_int16 SATURATE16(opus_int32 a) |
||||
{ |
||||
celt_mips+=3; |
||||
if (a>32767) |
||||
return 32767; |
||||
else if (a<-32768) |
||||
return -32768; |
||||
else return a; |
||||
} |
||||
|
||||
static OPUS_INLINE int MULT16_16_Q11_32(int a, int b) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_Q11: inputs are not short: %d %d\n", a, b); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = ((opus_int64)a)*b; |
||||
res >>= 11; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_Q11: output is not short: %d*%d=%d\n", (int)a, (int)b, (int)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=3; |
||||
return res; |
||||
} |
||||
static OPUS_INLINE short MULT16_16_Q13(int a, int b) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_Q13: inputs are not short: %d %d\n", a, b); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = ((opus_int64)a)*b; |
||||
res >>= 13; |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_Q13: output is not short: %d*%d=%d\n", a, b, (int)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=3; |
||||
return res; |
||||
} |
||||
static OPUS_INLINE short MULT16_16_Q14(int a, int b) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_Q14: inputs are not short: %d %d\n", a, b); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = ((opus_int64)a)*b; |
||||
res >>= 14; |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_Q14: output is not short: %d\n", (int)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=3; |
||||
return res; |
||||
} |
||||
|
||||
#define MULT16_16_Q15(a, b) MULT16_16_Q15_(a, b, __FILE__, __LINE__) |
||||
static OPUS_INLINE short MULT16_16_Q15_(int a, int b, char *file, int line) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_Q15: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = ((opus_int64)a)*b; |
||||
res >>= 15; |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_Q15: output is not short: %d in %s: line %d\n", (int)res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=1; |
||||
return res; |
||||
} |
||||
|
||||
static OPUS_INLINE short MULT16_16_P13(int a, int b) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_P13: inputs are not short: %d %d\n", a, b); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = ((opus_int64)a)*b; |
||||
res += 4096; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_P13: overflow: %d*%d=%d\n", a, b, (int)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res >>= 13; |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_P13: output is not short: %d*%d=%d\n", a, b, (int)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=4; |
||||
return res; |
||||
} |
||||
static OPUS_INLINE short MULT16_16_P14(int a, int b) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_P14: inputs are not short: %d %d\n", a, b); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = ((opus_int64)a)*b; |
||||
res += 8192; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_P14: overflow: %d*%d=%d\n", a, b, (int)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res >>= 14; |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_P14: output is not short: %d*%d=%d\n", a, b, (int)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=4; |
||||
return res; |
||||
} |
||||
static OPUS_INLINE short MULT16_16_P15(int a, int b) |
||||
{ |
||||
opus_int64 res; |
||||
if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_P15: inputs are not short: %d %d\n", a, b); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = ((opus_int64)a)*b; |
||||
res += 16384; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_P15: overflow: %d*%d=%d\n", a, b, (int)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res >>= 15; |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "MULT16_16_P15: output is not short: %d*%d=%d\n", a, b, (int)res); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=2; |
||||
return res; |
||||
} |
||||
|
||||
#define DIV32_16(a, b) DIV32_16_(a, b, __FILE__, __LINE__) |
||||
|
||||
static OPUS_INLINE int DIV32_16_(opus_int64 a, opus_int64 b, char *file, int line) |
||||
{ |
||||
opus_int64 res; |
||||
if (b==0) |
||||
{ |
||||
fprintf(stderr, "DIV32_16: divide by zero: %d/%d in %s: line %d\n", (int)a, (int)b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
return 0; |
||||
} |
||||
if (!VERIFY_INT(a) || !VERIFY_SHORT(b)) |
||||
{ |
||||
fprintf (stderr, "DIV32_16: inputs are not int/short: %d %d in %s: line %d\n", (int)a, (int)b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = a/b; |
||||
if (!VERIFY_SHORT(res)) |
||||
{ |
||||
fprintf (stderr, "DIV32_16: output is not short: %d / %d = %d in %s: line %d\n", (int)a,(int)b,(int)res, file, line); |
||||
if (res>32767) |
||||
res = 32767; |
||||
if (res<-32768) |
||||
res = -32768; |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=35; |
||||
return res; |
||||
} |
||||
|
||||
#define DIV32(a, b) DIV32_(a, b, __FILE__, __LINE__) |
||||
static OPUS_INLINE int DIV32_(opus_int64 a, opus_int64 b, char *file, int line) |
||||
{ |
||||
opus_int64 res; |
||||
if (b==0) |
||||
{ |
||||
fprintf(stderr, "DIV32: divide by zero: %d/%d in %s: line %d\n", (int)a, (int)b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
return 0; |
||||
} |
||||
|
||||
if (!VERIFY_INT(a) || !VERIFY_INT(b)) |
||||
{ |
||||
fprintf (stderr, "DIV32: inputs are not int/short: %d %d in %s: line %d\n", (int)a, (int)b, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
res = a/b; |
||||
if (!VERIFY_INT(res)) |
||||
{ |
||||
fprintf (stderr, "DIV32: output is not int: %d in %s: line %d\n", (int)res, file, line); |
||||
#ifdef FIXED_DEBUG_ASSERT |
||||
celt_assert(0); |
||||
#endif |
||||
} |
||||
celt_mips+=70; |
||||
return res; |
||||
} |
||||
|
||||
static OPUS_INLINE opus_val16 SIG2WORD16_generic(celt_sig x) |
||||
{ |
||||
x = PSHR32(x, SIG_SHIFT); |
||||
x = MAX32(x, -32768); |
||||
x = MIN32(x, 32767); |
||||
return EXTRACT16(x); |
||||
} |
||||
#define SIG2WORD16(x) (SIG2WORD16_generic(x)) |
||||
|
||||
|
||||
#undef PRINT_MIPS |
||||
#define PRINT_MIPS(file) do {fprintf (file, "total complexity = %llu MIPS\n", (unsigned long long)celt_mips);} while (0); |
||||
|
||||
#endif |
||||
@ -0,0 +1,218 @@
|
||||
/* Copyright (C) 2007-2009 Xiph.Org Foundation
|
||||
Copyright (C) 2003-2008 Jean-Marc Valin |
||||
Copyright (C) 2007-2008 CSIRO */ |
||||
/**
|
||||
@file fixed_generic.h |
||||
@brief Generic fixed-point operations |
||||
*/ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef FIXED_GENERIC_H |
||||
#define FIXED_GENERIC_H |
||||
|
||||
/** Multiply a 16-bit signed value by a 16-bit unsigned value. The result is a 32-bit signed value */ |
||||
#define MULT16_16SU(a,b) ((opus_val32)(opus_val16)(a)*(opus_val32)(opus_uint16)(b)) |
||||
|
||||
/** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */ |
||||
#if OPUS_FAST_INT64 |
||||
#define MULT16_32_Q16(a,b) ((opus_val32)SHR((opus_int64)((opus_val16)(a))*(b),16)) |
||||
#else |
||||
#define MULT16_32_Q16(a,b) ADD32(MULT16_16((a),SHR((b),16)), SHR(MULT16_16SU((a),((b)&0x0000ffff)),16)) |
||||
#endif |
||||
|
||||
/** 16x32 multiplication, followed by a 16-bit shift right (round-to-nearest). Results fits in 32 bits */ |
||||
#if OPUS_FAST_INT64 |
||||
#define MULT16_32_P16(a,b) ((opus_val32)PSHR((opus_int64)((opus_val16)(a))*(b),16)) |
||||
#else |
||||
#define MULT16_32_P16(a,b) ADD32(MULT16_16((a),SHR((b),16)), PSHR(MULT16_16SU((a),((b)&0x0000ffff)),16)) |
||||
#endif |
||||
|
||||
/** 16x32 multiplication, followed by a 15-bit shift right. Results fits in 32 bits */ |
||||
#if OPUS_FAST_INT64 |
||||
#define MULT16_32_Q15(a,b) ((opus_val32)SHR((opus_int64)((opus_val16)(a))*(b),15)) |
||||
#else |
||||
#define MULT16_32_Q15(a,b) ADD32(SHL(MULT16_16((a),SHR((b),16)),1), SHR(MULT16_16SU((a),((b)&0x0000ffff)),15)) |
||||
#endif |
||||
|
||||
/** 32x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */ |
||||
#if OPUS_FAST_INT64 |
||||
#define MULT32_32_Q16(a,b) ((opus_val32)SHR((opus_int64)(a)*(opus_int64)(b),16)) |
||||
#else |
||||
#define MULT32_32_Q16(a,b) (ADD32(ADD32(ADD32((opus_val32)(SHR32(((opus_uint32)((a)&0x0000ffff)*(opus_uint32)((b)&0x0000ffff)),16)), MULT16_16SU(SHR32(a,16),((b)&0x0000ffff))), MULT16_16SU(SHR32(b,16),((a)&0x0000ffff))), SHL32(MULT16_16(SHR32(a,16),SHR32(b,16)),16))) |
||||
#endif |
||||
|
||||
/** 32x32 multiplication, followed by a 31-bit shift right. Results fits in 32 bits */ |
||||
#if OPUS_FAST_INT64 |
||||
#define MULT32_32_Q31(a,b) ((opus_val32)SHR((opus_int64)(a)*(opus_int64)(b),31)) |
||||
#else |
||||
#define MULT32_32_Q31(a,b) ADD32(ADD32(SHL(MULT16_16(SHR((a),16),SHR((b),16)),1), SHR(MULT16_16SU(SHR((a),16),((b)&0x0000ffff)),15)), SHR(MULT16_16SU(SHR((b),16),((a)&0x0000ffff)),15)) |
||||
#endif |
||||
|
||||
/** 32x32 multiplication, followed by a 31-bit shift right (with rounding). Results fits in 32 bits */ |
||||
#if OPUS_FAST_INT64 |
||||
#define MULT32_32_P31(a,b) ((opus_val32)SHR(1073741824+(opus_int64)(a)*(opus_int64)(b),31)) |
||||
#define MULT32_32_P31_ovflw(a,b) MULT32_32_P31(a,b) |
||||
#else |
||||
#define MULT16_16U(a,b) ((opus_uint32)(a)*(opus_uint32)(b)) |
||||
#define MULT32_32_P31(a,b) ADD32(SHL(MULT16_16(SHR((a),16),SHR((b),16)),1), SHR32(128+(opus_int32)SHR(MULT16_16U(((a)&0x0000ffff),((b)&0x0000ffff)),16+7) + SHR32(MULT16_16SU(SHR((a),16),((b)&0x0000ffff)),7) + SHR32(MULT16_16SU(SHR((b),16),((a)&0x0000ffff)),7), 8) ) |
||||
#define MULT32_32_P31_ovflw(a,b) ADD32_ovflw(SHL(MULT16_16(SHR((a),16),SHR((b),16)),1), SHR32(128+(opus_int32)SHR(MULT16_16U(((a)&0x0000ffff),((b)&0x0000ffff)),16+7) + SHR32(MULT16_16SU(SHR((a),16),((b)&0x0000ffff)),7) + SHR32(MULT16_16SU(SHR((b),16),((a)&0x0000ffff)),7), 8) ) |
||||
#endif |
||||
|
||||
/** 32x32 multiplication, followed by a 32-bit shift right. Results fits in 32 bits */ |
||||
#if OPUS_FAST_INT64 |
||||
#define MULT32_32_Q32(a,b) ((opus_val32)SHR((opus_int64)(a)*(opus_int64)(b),32)) |
||||
#else |
||||
#define MULT32_32_Q32(a,b) ADD32(ADD32(MULT16_16(SHR((a),16),SHR((b),16)), SHR(MULT16_16SU(SHR((a),16),((b)&0x0000ffff)),16)), SHR(MULT16_16SU(SHR((b),16),((a)&0x0000ffff)),16)) |
||||
#endif |
||||
|
||||
/** Compile-time conversion of float constant to 16-bit value */ |
||||
#define QCONST16(x,bits) ((opus_val16)(.5+(x)*(((opus_val32)1)<<(bits)))) |
||||
|
||||
/** Compile-time conversion of float constant to 32-bit value */ |
||||
#define QCONST32(x,bits) ((opus_val32)(.5+(x)*(((opus_int64)1)<<(bits)))) |
||||
|
||||
/** Compile-time conversion of float constant to log gain value */ |
||||
#define GCONST2(x,bits) ((celt_glog)(.5+(x)*(((celt_glog)1)<<(bits)))) |
||||
|
||||
/** Compile-time conversion of float constant to DB_SHIFT log gain value */ |
||||
#define GCONST(x) GCONST2((x),DB_SHIFT) |
||||
|
||||
/** Negate a 16-bit value */ |
||||
#define NEG16(x) (-(x)) |
||||
/** Negate a 32-bit value */ |
||||
#define NEG32(x) (-(x)) |
||||
|
||||
/** Change a 32-bit value into a 16-bit value. The value is assumed to fit in 16-bit, otherwise the result is undefined */ |
||||
#define EXTRACT16(x) ((opus_val16)(x)) |
||||
/** Change a 16-bit value into a 32-bit value */ |
||||
#define EXTEND32(x) ((opus_val32)(x)) |
||||
|
||||
/** Arithmetic shift-right of a 16-bit value */ |
||||
#define SHR16(a,shift) ((a) >> (shift)) |
||||
/** Arithmetic shift-left of a 16-bit value */ |
||||
#define SHL16(a,shift) ((opus_int16)((opus_uint16)(a)<<(shift))) |
||||
/** Arithmetic shift-right of a 32-bit value */ |
||||
#define SHR32(a,shift) ((a) >> (shift)) |
||||
/** Arithmetic shift-left of a 32-bit value */ |
||||
#define SHL32(a,shift) ((opus_int32)((opus_uint32)(a)<<(shift))) |
||||
|
||||
/** 32-bit arithmetic shift right with rounding-to-nearest instead of rounding down */ |
||||
#define PSHR32(a,shift) (SHR32((a)+((EXTEND32(1)<<((shift))>>1)),shift)) |
||||
/** 32-bit arithmetic shift right where the argument can be negative */ |
||||
#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) |
||||
|
||||
/** Arithmetic shift-right of a 64-bit value */ |
||||
#define SHR64(a,shift) ((a) >> (shift)) |
||||
|
||||
/** "RAW" macros, should not be used outside of this header file */ |
||||
#define SHR(a,shift) ((a) >> (shift)) |
||||
#define SHL(a,shift) SHL32(a,shift) |
||||
#define PSHR(a,shift) (SHR((a)+((EXTEND32(1)<<((shift))>>1)),shift)) |
||||
#define SATURATE(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) |
||||
|
||||
#define SATURATE16(x) (EXTRACT16((x)>32767 ? 32767 : (x)<-32768 ? -32768 : (x))) |
||||
|
||||
/** Shift by a and round-to-nearest 32-bit value. Result is a 16-bit value */ |
||||
#define ROUND16(x,a) (EXTRACT16(PSHR32((x),(a)))) |
||||
/** Shift by a and round-to-nearest 32-bit value. Result is a saturated 16-bit value */ |
||||
#define SROUND16(x,a) EXTRACT16(SATURATE(PSHR32(x,a), 32767)); |
||||
|
||||
/** Divide by two */ |
||||
#define HALF16(x) (SHR16(x,1)) |
||||
#define HALF32(x) (SHR32(x,1)) |
||||
|
||||
/** Add two 16-bit values */ |
||||
#define ADD16(a,b) ((opus_val16)((opus_val16)(a)+(opus_val16)(b))) |
||||
/** Subtract two 16-bit values */ |
||||
#define SUB16(a,b) ((opus_val16)(a)-(opus_val16)(b)) |
||||
/** Add two 32-bit values */ |
||||
#define ADD32(a,b) ((opus_val32)(a)+(opus_val32)(b)) |
||||
/** Subtract two 32-bit values */ |
||||
#define SUB32(a,b) ((opus_val32)(a)-(opus_val32)(b)) |
||||
|
||||
/** Add two 32-bit values, ignore any overflows */ |
||||
#define ADD32_ovflw(a,b) ((opus_val32)((opus_uint32)(a)+(opus_uint32)(b))) |
||||
/** Subtract two 32-bit values, ignore any overflows */ |
||||
#define SUB32_ovflw(a,b) ((opus_val32)((opus_uint32)(a)-(opus_uint32)(b))) |
||||
/* Avoid MSVC warning C4146: unary minus operator applied to unsigned type */ |
||||
/** Negate 32-bit value, ignore any overflows */ |
||||
#define NEG32_ovflw(a) ((opus_val32)(0-(opus_uint32)(a))) |
||||
/** 32-bit shift left, ignoring overflows */ |
||||
#define SHL32_ovflw(a,shift) SHL32(a,shift) |
||||
/** 32-bit arithmetic shift right with rounding-to-nearest, ignoring overflows */ |
||||
#define PSHR32_ovflw(a,shift) (SHR32(ADD32_ovflw(a, (EXTEND32(1)<<(shift)>>1)),shift)) |
||||
|
||||
/** 16x16 multiplication where the result fits in 16 bits */ |
||||
#define MULT16_16_16(a,b) ((((opus_val16)(a))*((opus_val16)(b)))) |
||||
|
||||
/** 32x32 multiplication where the result fits in 32 bits */ |
||||
#define MULT32_32_32(a,b) ((((opus_val32)(a))*((opus_val32)(b)))) |
||||
|
||||
/* (opus_val32)(opus_val16) gives TI compiler a hint that it's 16x16->32 multiply */ |
||||
/** 16x16 multiplication where the result fits in 32 bits */ |
||||
#define MULT16_16(a,b) (((opus_val32)(opus_val16)(a))*((opus_val32)(opus_val16)(b))) |
||||
|
||||
/** 16x16 multiply-add where the result fits in 32 bits */ |
||||
#define MAC16_16(c,a,b) (ADD32((c),MULT16_16((a),(b)))) |
||||
/** 16x32 multiply, followed by a 15-bit shift right and 32-bit add.
|
||||
b must fit in 31 bits. |
||||
Result fits in 32 bits. */ |
||||
#define MAC16_32_Q15(c,a,b) ADD32((c),ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))) |
||||
|
||||
/** 16x32 multiplication, followed by a 16-bit shift right and 32-bit add.
|
||||
Results fits in 32 bits */ |
||||
#define MAC16_32_Q16(c,a,b) ADD32((c),ADD32(MULT16_16((a),SHR((b),16)), SHR(MULT16_16SU((a),((b)&0x0000ffff)),16))) |
||||
|
||||
#define MULT16_16_Q11_32(a,b) (SHR(MULT16_16((a),(b)),11)) |
||||
#define MULT16_16_Q11(a,b) (SHR(MULT16_16((a),(b)),11)) |
||||
#define MULT16_16_Q13(a,b) (SHR(MULT16_16((a),(b)),13)) |
||||
#define MULT16_16_Q14(a,b) (SHR(MULT16_16((a),(b)),14)) |
||||
#define MULT16_16_Q15(a,b) (SHR(MULT16_16((a),(b)),15)) |
||||
|
||||
#define MULT16_16_P13(a,b) (SHR(ADD32(4096,MULT16_16((a),(b))),13)) |
||||
#define MULT16_16_P14(a,b) (SHR(ADD32(8192,MULT16_16((a),(b))),14)) |
||||
#define MULT16_16_P15(a,b) (SHR(ADD32(16384,MULT16_16((a),(b))),15)) |
||||
|
||||
/** Divide a 32-bit value by a 16-bit value. Result fits in 16 bits */ |
||||
#define DIV32_16(a,b) ((opus_val16)(((opus_val32)(a))/((opus_val16)(b)))) |
||||
|
||||
/** Divide a 32-bit value by a 32-bit value. Result fits in 32 bits */ |
||||
#define DIV32(a,b) (((opus_val32)(a))/((opus_val32)(b))) |
||||
|
||||
#if defined(__mips) |
||||
#include "mips/fixed_generic_mipsr1.h" |
||||
#endif |
||||
|
||||
static OPUS_INLINE opus_val16 SIG2WORD16_generic(celt_sig x) |
||||
{ |
||||
x = PSHR32(x, SIG_SHIFT); |
||||
x = MAX32(x, -32768); |
||||
x = MIN32(x, 32767); |
||||
return EXTRACT16(x); |
||||
} |
||||
#define SIG2WORD16(x) (SIG2WORD16_generic(x)) |
||||
|
||||
#endif |
||||
@ -0,0 +1,176 @@
|
||||
/* Copyright (C) 2001 Erik de Castro Lopo <erikd AT mega-nerd DOT com> */ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
/* Version 1.1 */ |
||||
|
||||
#ifndef FLOAT_CAST_H |
||||
#define FLOAT_CAST_H |
||||
|
||||
|
||||
#include "arch.h" |
||||
|
||||
/*============================================================================
|
||||
** On Intel Pentium processors (especially PIII and probably P4), converting |
||||
** from float to int is very slow. To meet the C specs, the code produced by |
||||
** most C compilers targeting Pentium needs to change the FPU rounding mode |
||||
** before the float to int conversion is performed. |
||||
** |
||||
** Changing the FPU rounding mode causes the FPU pipeline to be flushed. It |
||||
** is this flushing of the pipeline which is so slow. |
||||
** |
||||
** Fortunately the ISO C99 specifications define the functions lrint, lrintf, |
||||
** llrint and llrintf which fix this problem as a side effect. |
||||
** |
||||
** On Unix-like systems, the configure process should have detected the |
||||
** presence of these functions. If they weren't found we have to replace them |
||||
** here with a standard C cast. |
||||
*/ |
||||
|
||||
/*
|
||||
** The C99 prototypes for lrint and lrintf are as follows: |
||||
** |
||||
** long int lrintf (float x) ; |
||||
** long int lrint (double x) ; |
||||
*/ |
||||
|
||||
/* The presence of the required functions are detected during the configure
|
||||
** process and the values HAVE_LRINT and HAVE_LRINTF are set accordingly in |
||||
** the config.h file. |
||||
*/ |
||||
|
||||
/* With GCC, when SSE is available, the fastest conversion is cvtss2si. */ |
||||
#if defined(__GNUC__) && defined(__SSE__) |
||||
|
||||
#include <xmmintrin.h> |
||||
static OPUS_INLINE opus_int32 float2int(float x) {return _mm_cvt_ss2si(_mm_set_ss(x));} |
||||
|
||||
#elif (defined(_MSC_VER) && _MSC_VER >= 1400) && (defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 1)) |
||||
|
||||
#include <xmmintrin.h> |
||||
static OPUS_INLINE opus_int32 float2int(float value) |
||||
{ |
||||
/* _mm_load_ss will generate same code as _mm_set_ss
|
||||
** in _MSC_VER >= 1914 /02 so keep __mm_load__ss |
||||
** for backward compatibility. |
||||
*/ |
||||
return _mm_cvtss_si32(_mm_load_ss(&value)); |
||||
} |
||||
|
||||
#elif (defined(_MSC_VER) && _MSC_VER >= 1400) && defined (_M_IX86) |
||||
|
||||
#include <math.h> |
||||
|
||||
/* Win32 doesn't seem to have these functions.
|
||||
** Therefore implement OPUS_INLINE versions of these functions here. |
||||
*/ |
||||
|
||||
static OPUS_INLINE opus_int32 |
||||
float2int (float flt) |
||||
{ int intgr; |
||||
|
||||
_asm |
||||
{ fld flt |
||||
fistp intgr |
||||
} ; |
||||
|
||||
return intgr ; |
||||
} |
||||
#elif defined(__aarch64__) |
||||
|
||||
#include <arm_neon.h> |
||||
static OPUS_INLINE opus_int32 float2int(float flt) |
||||
{ |
||||
return vcvtns_s32_f32(flt); |
||||
} |
||||
|
||||
#elif defined(HAVE_LRINTF) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L |
||||
|
||||
/* These defines enable functionality introduced with the 1999 ISO C
|
||||
** standard. They must be defined before the inclusion of math.h to |
||||
** engage them. If optimisation is enabled, these functions will be |
||||
** inlined. With optimisation switched off, you have to link in the |
||||
** maths library using -lm. |
||||
*/ |
||||
|
||||
#define _ISOC9X_SOURCE 1 |
||||
#define _ISOC99_SOURCE 1 |
||||
|
||||
#define __USE_ISOC9X 1 |
||||
#define __USE_ISOC99 1 |
||||
|
||||
#include <math.h> |
||||
#define float2int(x) lrintf(x) |
||||
|
||||
#elif defined(HAVE_LRINT) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L |
||||
|
||||
#define _ISOC9X_SOURCE 1 |
||||
#define _ISOC99_SOURCE 1 |
||||
|
||||
#define __USE_ISOC9X 1 |
||||
#define __USE_ISOC99 1 |
||||
|
||||
#include <math.h> |
||||
#define float2int(x) lrint(x) |
||||
|
||||
#else |
||||
|
||||
#if (defined(__GNUC__) && defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) |
||||
/* supported by gcc in C99 mode, but not by all other compilers */ |
||||
#warning "Don't have the functions lrint() and lrintf ()." |
||||
#warning "Replacing these functions with a standard C cast." |
||||
#endif /* __STDC_VERSION__ >= 199901L */ |
||||
#include <math.h> |
||||
#define float2int(flt) ((int)(floor(.5+flt))) |
||||
#endif |
||||
|
||||
#ifndef DISABLE_FLOAT_API |
||||
static OPUS_INLINE opus_int16 FLOAT2INT16(float x) |
||||
{ |
||||
x = x*CELT_SIG_SCALE; |
||||
x = MAX32(x, -32768); |
||||
x = MIN32(x, 32767); |
||||
return (opus_int16)float2int(x); |
||||
} |
||||
|
||||
static OPUS_INLINE opus_int32 FLOAT2INT24(float x) |
||||
{ |
||||
x = x*(CELT_SIG_SCALE*256.f); |
||||
x = MAX32(x, -16777216); |
||||
x = MIN32(x, 16777216); |
||||
return float2int(x); |
||||
} |
||||
#ifdef FIXED_POINT |
||||
static OPUS_INLINE opus_int32 FLOAT2SIG(float x) |
||||
{ |
||||
x = x*((opus_int32)32768<<SIG_SHIFT); |
||||
x = MAX32(x, -(65536<<SIG_SHIFT)); |
||||
x = MIN32(x, 65536<<SIG_SHIFT); |
||||
return float2int(x); |
||||
} |
||||
#endif |
||||
#endif /* DISABLE_FLOAT_API */ |
||||
|
||||
#endif /* FLOAT_CAST_H */ |
||||
@ -0,0 +1,650 @@
|
||||
/*Copyright (c) 2003-2004, Mark Borgerding
|
||||
Lots of modifications by Jean-Marc Valin |
||||
Copyright (c) 2005-2007, Xiph.Org Foundation |
||||
Copyright (c) 2008, Xiph.Org Foundation, CSIRO |
||||
|
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
* Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above copyright notice, |
||||
this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
POSSIBILITY OF SUCH DAMAGE.*/ |
||||
|
||||
/* This code is originally from Mark Borgerding's KISS-FFT but has been
|
||||
heavily modified to better suit Opus */ |
||||
|
||||
#ifndef SKIP_CONFIG_H |
||||
# ifdef HAVE_CONFIG_H |
||||
# include "config.h" |
||||
# endif |
||||
#endif |
||||
|
||||
#include "_kiss_fft_guts.h" |
||||
#include "arch.h" |
||||
#include "os_support.h" |
||||
#include "mathops.h" |
||||
#include "stack_alloc.h" |
||||
|
||||
#ifndef M_PI |
||||
#define M_PI 3.141592653 |
||||
#endif |
||||
|
||||
/* The guts header contains all the multiplication and addition macros that are defined for
|
||||
complex numbers. It also declares the kf_ internal functions. |
||||
*/ |
||||
|
||||
static void kf_bfly2( |
||||
kiss_fft_cpx * Fout, |
||||
int m, |
||||
int N |
||||
) |
||||
{ |
||||
kiss_fft_cpx * Fout2; |
||||
int i; |
||||
(void)m; |
||||
#ifdef CUSTOM_MODES |
||||
if (m==1) |
||||
{ |
||||
celt_assert(m==1); |
||||
for (i=0;i<N;i++) |
||||
{ |
||||
kiss_fft_cpx t; |
||||
Fout2 = Fout + 1; |
||||
t = *Fout2; |
||||
C_SUB( *Fout2 , *Fout , t ); |
||||
C_ADDTO( *Fout , t ); |
||||
Fout += 2; |
||||
} |
||||
} else |
||||
#endif |
||||
{ |
||||
celt_coef tw; |
||||
tw = QCONST32(0.7071067812f, COEF_SHIFT-1); |
||||
/* We know that m==4 here because the radix-2 is just after a radix-4 */ |
||||
celt_assert(m==4); |
||||
for (i=0;i<N;i++) |
||||
{ |
||||
kiss_fft_cpx t; |
||||
Fout2 = Fout + 4; |
||||
t = Fout2[0]; |
||||
C_SUB( Fout2[0] , Fout[0] , t ); |
||||
C_ADDTO( Fout[0] , t ); |
||||
|
||||
t.r = S_MUL(ADD32_ovflw(Fout2[1].r, Fout2[1].i), tw); |
||||
t.i = S_MUL(SUB32_ovflw(Fout2[1].i, Fout2[1].r), tw); |
||||
C_SUB( Fout2[1] , Fout[1] , t ); |
||||
C_ADDTO( Fout[1] , t ); |
||||
|
||||
t.r = Fout2[2].i; |
||||
t.i = NEG32_ovflw(Fout2[2].r); |
||||
C_SUB( Fout2[2] , Fout[2] , t ); |
||||
C_ADDTO( Fout[2] , t ); |
||||
|
||||
t.r = S_MUL(SUB32_ovflw(Fout2[3].i, Fout2[3].r), tw); |
||||
t.i = S_MUL(NEG32_ovflw(ADD32_ovflw(Fout2[3].i, Fout2[3].r)), tw); |
||||
C_SUB( Fout2[3] , Fout[3] , t ); |
||||
C_ADDTO( Fout[3] , t ); |
||||
Fout += 8; |
||||
} |
||||
} |
||||
} |
||||
|
||||
static void kf_bfly4( |
||||
kiss_fft_cpx * Fout, |
||||
const size_t fstride, |
||||
const kiss_fft_state *st, |
||||
int m, |
||||
int N, |
||||
int mm |
||||
) |
||||
{ |
||||
int i; |
||||
|
||||
if (m==1) |
||||
{ |
||||
/* Degenerate case where all the twiddles are 1. */ |
||||
for (i=0;i<N;i++) |
||||
{ |
||||
kiss_fft_cpx scratch0, scratch1; |
||||
|
||||
C_SUB( scratch0 , *Fout, Fout[2] ); |
||||
C_ADDTO(*Fout, Fout[2]); |
||||
C_ADD( scratch1 , Fout[1] , Fout[3] ); |
||||
C_SUB( Fout[2], *Fout, scratch1 ); |
||||
C_ADDTO( *Fout , scratch1 ); |
||||
C_SUB( scratch1 , Fout[1] , Fout[3] ); |
||||
|
||||
Fout[1].r = ADD32_ovflw(scratch0.r, scratch1.i); |
||||
Fout[1].i = SUB32_ovflw(scratch0.i, scratch1.r); |
||||
Fout[3].r = SUB32_ovflw(scratch0.r, scratch1.i); |
||||
Fout[3].i = ADD32_ovflw(scratch0.i, scratch1.r); |
||||
Fout+=4; |
||||
} |
||||
} else { |
||||
int j; |
||||
kiss_fft_cpx scratch[6]; |
||||
const kiss_twiddle_cpx *tw1,*tw2,*tw3; |
||||
const int m2=2*m; |
||||
const int m3=3*m; |
||||
kiss_fft_cpx * Fout_beg = Fout; |
||||
for (i=0;i<N;i++) |
||||
{ |
||||
Fout = Fout_beg + i*mm; |
||||
tw3 = tw2 = tw1 = st->twiddles; |
||||
/* m is guaranteed to be a multiple of 4. */ |
||||
for (j=0;j<m;j++) |
||||
{ |
||||
C_MUL(scratch[0],Fout[m] , *tw1 ); |
||||
C_MUL(scratch[1],Fout[m2] , *tw2 ); |
||||
C_MUL(scratch[2],Fout[m3] , *tw3 ); |
||||
|
||||
C_SUB( scratch[5] , *Fout, scratch[1] ); |
||||
C_ADDTO(*Fout, scratch[1]); |
||||
C_ADD( scratch[3] , scratch[0] , scratch[2] ); |
||||
C_SUB( scratch[4] , scratch[0] , scratch[2] ); |
||||
C_SUB( Fout[m2], *Fout, scratch[3] ); |
||||
tw1 += fstride; |
||||
tw2 += fstride*2; |
||||
tw3 += fstride*3; |
||||
C_ADDTO( *Fout , scratch[3] ); |
||||
|
||||
Fout[m].r = ADD32_ovflw(scratch[5].r, scratch[4].i); |
||||
Fout[m].i = SUB32_ovflw(scratch[5].i, scratch[4].r); |
||||
Fout[m3].r = SUB32_ovflw(scratch[5].r, scratch[4].i); |
||||
Fout[m3].i = ADD32_ovflw(scratch[5].i, scratch[4].r); |
||||
++Fout; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
#ifndef RADIX_TWO_ONLY |
||||
|
||||
static void kf_bfly3( |
||||
kiss_fft_cpx * Fout, |
||||
const size_t fstride, |
||||
const kiss_fft_state *st, |
||||
int m, |
||||
int N, |
||||
int mm |
||||
) |
||||
{ |
||||
int i; |
||||
size_t k; |
||||
const size_t m2 = 2*m; |
||||
const kiss_twiddle_cpx *tw1,*tw2; |
||||
kiss_fft_cpx scratch[5]; |
||||
kiss_twiddle_cpx epi3; |
||||
|
||||
kiss_fft_cpx * Fout_beg = Fout; |
||||
#ifdef FIXED_POINT |
||||
/*epi3.r = -16384;*/ /* Unused */ |
||||
epi3.i = -QCONST32(0.86602540f, COEF_SHIFT-1); |
||||
#else |
||||
epi3 = st->twiddles[fstride*m]; |
||||
#endif |
||||
for (i=0;i<N;i++) |
||||
{ |
||||
Fout = Fout_beg + i*mm; |
||||
tw1=tw2=st->twiddles; |
||||
/* For non-custom modes, m is guaranteed to be a multiple of 4. */ |
||||
k=m; |
||||
do { |
||||
|
||||
C_MUL(scratch[1],Fout[m] , *tw1); |
||||
C_MUL(scratch[2],Fout[m2] , *tw2); |
||||
|
||||
C_ADD(scratch[3],scratch[1],scratch[2]); |
||||
C_SUB(scratch[0],scratch[1],scratch[2]); |
||||
tw1 += fstride; |
||||
tw2 += fstride*2; |
||||
|
||||
Fout[m].r = SUB32_ovflw(Fout->r, HALF_OF(scratch[3].r)); |
||||
Fout[m].i = SUB32_ovflw(Fout->i, HALF_OF(scratch[3].i)); |
||||
|
||||
C_MULBYSCALAR( scratch[0] , epi3.i ); |
||||
|
||||
C_ADDTO(*Fout,scratch[3]); |
||||
|
||||
Fout[m2].r = ADD32_ovflw(Fout[m].r, scratch[0].i); |
||||
Fout[m2].i = SUB32_ovflw(Fout[m].i, scratch[0].r); |
||||
|
||||
Fout[m].r = SUB32_ovflw(Fout[m].r, scratch[0].i); |
||||
Fout[m].i = ADD32_ovflw(Fout[m].i, scratch[0].r); |
||||
|
||||
++Fout; |
||||
} while(--k); |
||||
} |
||||
} |
||||
|
||||
|
||||
#ifndef OVERRIDE_kf_bfly5 |
||||
static void kf_bfly5( |
||||
kiss_fft_cpx * Fout, |
||||
const size_t fstride, |
||||
const kiss_fft_state *st, |
||||
int m, |
||||
int N, |
||||
int mm |
||||
) |
||||
{ |
||||
kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; |
||||
int i, u; |
||||
kiss_fft_cpx scratch[13]; |
||||
const kiss_twiddle_cpx *tw; |
||||
kiss_twiddle_cpx ya,yb; |
||||
kiss_fft_cpx * Fout_beg = Fout; |
||||
|
||||
#ifdef FIXED_POINT |
||||
ya.r = QCONST32(0.30901699f, COEF_SHIFT-1); |
||||
ya.i = -QCONST32(0.95105652f, COEF_SHIFT-1); |
||||
yb.r = -QCONST32(0.80901699f, COEF_SHIFT-1); |
||||
yb.i = -QCONST32(0.58778525f, COEF_SHIFT-1); |
||||
#else |
||||
ya = st->twiddles[fstride*m]; |
||||
yb = st->twiddles[fstride*2*m]; |
||||
#endif |
||||
tw=st->twiddles; |
||||
|
||||
for (i=0;i<N;i++) |
||||
{ |
||||
Fout = Fout_beg + i*mm; |
||||
Fout0=Fout; |
||||
Fout1=Fout0+m; |
||||
Fout2=Fout0+2*m; |
||||
Fout3=Fout0+3*m; |
||||
Fout4=Fout0+4*m; |
||||
|
||||
/* For non-custom modes, m is guaranteed to be a multiple of 4. */ |
||||
for ( u=0; u<m; ++u ) { |
||||
scratch[0] = *Fout0; |
||||
|
||||
C_MUL(scratch[1] ,*Fout1, tw[u*fstride]); |
||||
C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]); |
||||
C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]); |
||||
C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]); |
||||
|
||||
C_ADD( scratch[7],scratch[1],scratch[4]); |
||||
C_SUB( scratch[10],scratch[1],scratch[4]); |
||||
C_ADD( scratch[8],scratch[2],scratch[3]); |
||||
C_SUB( scratch[9],scratch[2],scratch[3]); |
||||
|
||||
Fout0->r = ADD32_ovflw(Fout0->r, ADD32_ovflw(scratch[7].r, scratch[8].r)); |
||||
Fout0->i = ADD32_ovflw(Fout0->i, ADD32_ovflw(scratch[7].i, scratch[8].i)); |
||||
|
||||
scratch[5].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,ya.r), S_MUL(scratch[8].r,yb.r))); |
||||
scratch[5].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,ya.r), S_MUL(scratch[8].i,yb.r))); |
||||
|
||||
scratch[6].r = ADD32_ovflw(S_MUL(scratch[10].i,ya.i), S_MUL(scratch[9].i,yb.i)); |
||||
scratch[6].i = NEG32_ovflw(ADD32_ovflw(S_MUL(scratch[10].r,ya.i), S_MUL(scratch[9].r,yb.i))); |
||||
|
||||
C_SUB(*Fout1,scratch[5],scratch[6]); |
||||
C_ADD(*Fout4,scratch[5],scratch[6]); |
||||
|
||||
scratch[11].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,yb.r), S_MUL(scratch[8].r,ya.r))); |
||||
scratch[11].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,yb.r), S_MUL(scratch[8].i,ya.r))); |
||||
scratch[12].r = SUB32_ovflw(S_MUL(scratch[9].i,ya.i), S_MUL(scratch[10].i,yb.i)); |
||||
scratch[12].i = SUB32_ovflw(S_MUL(scratch[10].r,yb.i), S_MUL(scratch[9].r,ya.i)); |
||||
|
||||
C_ADD(*Fout2,scratch[11],scratch[12]); |
||||
C_SUB(*Fout3,scratch[11],scratch[12]); |
||||
|
||||
++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; |
||||
} |
||||
} |
||||
} |
||||
#endif /* OVERRIDE_kf_bfly5 */ |
||||
|
||||
|
||||
#endif |
||||
|
||||
|
||||
#ifdef CUSTOM_MODES |
||||
|
||||
static |
||||
void compute_bitrev_table( |
||||
int Fout, |
||||
opus_int16 *f, |
||||
const size_t fstride, |
||||
int in_stride, |
||||
opus_int16 * factors, |
||||
const kiss_fft_state *st |
||||
) |
||||
{ |
||||
const int p=*factors++; /* the radix */ |
||||
const int m=*factors++; /* stage's fft length/p */ |
||||
|
||||
/*printf ("fft %d %d %d %d %d %d\n", p*m, m, p, s2, fstride*in_stride, N);*/ |
||||
if (m==1) |
||||
{ |
||||
int j; |
||||
for (j=0;j<p;j++) |
||||
{ |
||||
*f = Fout+j; |
||||
f += fstride*in_stride; |
||||
} |
||||
} else { |
||||
int j; |
||||
for (j=0;j<p;j++) |
||||
{ |
||||
compute_bitrev_table( Fout , f, fstride*p, in_stride, factors,st); |
||||
f += fstride*in_stride; |
||||
Fout += m; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* facbuf is populated by p1,m1,p2,m2, ...
|
||||
where |
||||
p[i] * m[i] = m[i-1] |
||||
m0 = n */ |
||||
static |
||||
int kf_factor(int n,opus_int16 * facbuf) |
||||
{ |
||||
int p=4; |
||||
int i; |
||||
int stages=0; |
||||
int nbak = n; |
||||
|
||||
/*factor out powers of 4, powers of 2, then any remaining primes */ |
||||
do { |
||||
while (n % p) { |
||||
switch (p) { |
||||
case 4: p = 2; break; |
||||
case 2: p = 3; break; |
||||
default: p += 2; break; |
||||
} |
||||
if (p>32000 || (opus_int32)p*(opus_int32)p > n) |
||||
p = n; /* no more factors, skip to end */ |
||||
} |
||||
n /= p; |
||||
#ifdef RADIX_TWO_ONLY |
||||
if (p!=2 && p != 4) |
||||
#else |
||||
if (p>5) |
||||
#endif |
||||
{ |
||||
return 0; |
||||
} |
||||
facbuf[2*stages] = p; |
||||
if (p==2 && stages > 1) |
||||
{ |
||||
facbuf[2*stages] = 4; |
||||
facbuf[2] = 2; |
||||
} |
||||
stages++; |
||||
} while (n > 1); |
||||
n = nbak; |
||||
/* Reverse the order to get the radix 4 at the end, so we can use the
|
||||
fast degenerate case. It turns out that reversing the order also |
||||
improves the noise behaviour. */ |
||||
for (i=0;i<stages/2;i++) |
||||
{ |
||||
int tmp; |
||||
tmp = facbuf[2*i]; |
||||
facbuf[2*i] = facbuf[2*(stages-i-1)]; |
||||
facbuf[2*(stages-i-1)] = tmp; |
||||
} |
||||
for (i=0;i<stages;i++) |
||||
{ |
||||
n /= facbuf[2*i]; |
||||
facbuf[2*i+1] = n; |
||||
} |
||||
return 1; |
||||
} |
||||
|
||||
static void compute_twiddles(kiss_twiddle_cpx *twiddles, int nfft) |
||||
{ |
||||
int i; |
||||
#ifdef FIXED_POINT |
||||
for (i=0;i<nfft;++i) { |
||||
opus_val32 phase = -i; |
||||
#ifdef ENABLE_QEXT |
||||
twiddles[i].r = (int)MIN32(2147483647, floor(.5+2147483648*cos((2*M_PI/nfft)*phase))); |
||||
twiddles[i].i = (int)MIN32(2147483647, floor(.5+2147483648*sin((2*M_PI/nfft)*phase))); |
||||
#else |
||||
kf_cexp2(twiddles+i, DIV32(SHL32(phase,17),nfft)); |
||||
#endif |
||||
} |
||||
#else |
||||
for (i=0;i<nfft;++i) { |
||||
const double pi=3.14159265358979323846264338327; |
||||
double phase = ( -2*pi /nfft ) * i; |
||||
kf_cexp(twiddles+i, phase ); |
||||
} |
||||
#endif |
||||
} |
||||
|
||||
int opus_fft_alloc_arch_c(kiss_fft_state *st) { |
||||
(void)st; |
||||
return 0; |
||||
} |
||||
|
||||
/*
|
||||
* |
||||
* Allocates all necessary storage space for the fft and ifft. |
||||
* The return value is a contiguous block of memory. As such, |
||||
* It can be freed with free(). |
||||
* */ |
||||
kiss_fft_state *opus_fft_alloc_twiddles(int nfft,void * mem,size_t * lenmem, |
||||
const kiss_fft_state *base, int arch) |
||||
{ |
||||
kiss_fft_state *st=NULL; |
||||
size_t memneeded = sizeof(struct kiss_fft_state); /* twiddle factors*/ |
||||
|
||||
if ( lenmem==NULL ) { |
||||
st = ( kiss_fft_state*)KISS_FFT_MALLOC( memneeded ); |
||||
}else{ |
||||
if (mem != NULL && *lenmem >= memneeded) |
||||
st = (kiss_fft_state*)mem; |
||||
*lenmem = memneeded; |
||||
} |
||||
if (st) { |
||||
opus_int16 *bitrev; |
||||
kiss_twiddle_cpx *twiddles; |
||||
|
||||
st->nfft=nfft; |
||||
#ifdef FIXED_POINT |
||||
st->scale_shift = celt_ilog2(st->nfft); |
||||
# ifdef ENABLE_QEXT |
||||
if (st->nfft == 1<<st->scale_shift) |
||||
st->scale = QCONST32(1.0f, 30); |
||||
else |
||||
st->scale = (((opus_int64)1073741824<<st->scale_shift)+st->nfft/2)/st->nfft; |
||||
# else |
||||
if (st->nfft == 1<<st->scale_shift) |
||||
st->scale = Q15ONE; |
||||
else |
||||
st->scale = (1073741824+st->nfft/2)/st->nfft>>(15-st->scale_shift); |
||||
# endif |
||||
#else |
||||
st->scale = 1.f/nfft; |
||||
#endif |
||||
if (base != NULL) |
||||
{ |
||||
st->twiddles = base->twiddles; |
||||
st->shift = 0; |
||||
while (st->shift < 32 && nfft<<st->shift != base->nfft) |
||||
st->shift++; |
||||
if (st->shift>=32) |
||||
goto fail; |
||||
} else { |
||||
st->twiddles = twiddles = (kiss_twiddle_cpx*)KISS_FFT_MALLOC(sizeof(kiss_twiddle_cpx)*nfft); |
||||
compute_twiddles(twiddles, nfft); |
||||
st->shift = -1; |
||||
} |
||||
if (!kf_factor(nfft,st->factors)) |
||||
{ |
||||
goto fail; |
||||
} |
||||
|
||||
/* bitrev */ |
||||
st->bitrev = bitrev = (opus_int16*)KISS_FFT_MALLOC(sizeof(opus_int16)*nfft); |
||||
if (st->bitrev==NULL) |
||||
goto fail; |
||||
compute_bitrev_table(0, bitrev, 1,1, st->factors,st); |
||||
|
||||
/* Initialize architecture specific fft parameters */ |
||||
if (opus_fft_alloc_arch(st, arch)) |
||||
goto fail; |
||||
} |
||||
return st; |
||||
fail: |
||||
opus_fft_free(st, arch); |
||||
return NULL; |
||||
} |
||||
|
||||
kiss_fft_state *opus_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch) |
||||
{ |
||||
return opus_fft_alloc_twiddles(nfft, mem, lenmem, NULL, arch); |
||||
} |
||||
|
||||
void opus_fft_free_arch_c(kiss_fft_state *st) { |
||||
(void)st; |
||||
} |
||||
|
||||
void opus_fft_free(const kiss_fft_state *cfg, int arch) |
||||
{ |
||||
if (cfg) |
||||
{ |
||||
opus_fft_free_arch((kiss_fft_state *)cfg, arch); |
||||
opus_free((opus_int16*)cfg->bitrev); |
||||
if (cfg->shift < 0) |
||||
opus_free((kiss_twiddle_cpx*)cfg->twiddles); |
||||
opus_free((kiss_fft_state*)cfg); |
||||
} |
||||
} |
||||
|
||||
#endif /* CUSTOM_MODES */ |
||||
|
||||
#ifdef FIXED_POINT |
||||
#ifndef OVERRIDE_fft_downshift |
||||
static void fft_downshift(kiss_fft_cpx *x, int N, int *total, int step) { |
||||
int shift; |
||||
shift = IMIN(step, *total); |
||||
*total -= shift; |
||||
if (shift == 1) { |
||||
int i; |
||||
for (i=0;i<N;i++) { |
||||
x[i].r = SHR32(x[i].r, 1); |
||||
x[i].i = SHR32(x[i].i, 1); |
||||
} |
||||
} else if (shift>0) { |
||||
int i; |
||||
for (i=0;i<N;i++) { |
||||
x[i].r = PSHR32(x[i].r, shift); |
||||
x[i].i = PSHR32(x[i].i, shift); |
||||
} |
||||
} |
||||
} |
||||
#endif /* OVERRIDE_fft_downshift */ |
||||
#else |
||||
#define fft_downshift(x, N, total, step) |
||||
#endif |
||||
|
||||
void opus_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout ARG_FIXED(int downshift)) |
||||
{ |
||||
int m2, m; |
||||
int p; |
||||
int L; |
||||
int fstride[MAXFACTORS]; |
||||
int i; |
||||
int shift; |
||||
|
||||
/* st->shift can be -1 */ |
||||
shift = st->shift>0 ? st->shift : 0; |
||||
|
||||
fstride[0] = 1; |
||||
L=0; |
||||
do { |
||||
p = st->factors[2*L]; |
||||
m = st->factors[2*L+1]; |
||||
fstride[L+1] = fstride[L]*p; |
||||
L++; |
||||
} while(m!=1); |
||||
m = st->factors[2*L-1]; |
||||
for (i=L-1;i>=0;i--) |
||||
{ |
||||
if (i!=0) |
||||
m2 = st->factors[2*i-1]; |
||||
else |
||||
m2 = 1; |
||||
switch (st->factors[2*i]) |
||||
{ |
||||
case 2: |
||||
fft_downshift(fout, st->nfft, &downshift, 1); |
||||
kf_bfly2(fout, m, fstride[i]); |
||||
break; |
||||
case 4: |
||||
fft_downshift(fout, st->nfft, &downshift, 2); |
||||
kf_bfly4(fout,fstride[i]<<shift,st,m, fstride[i], m2); |
||||
break; |
||||
#ifndef RADIX_TWO_ONLY |
||||
case 3: |
||||
fft_downshift(fout, st->nfft, &downshift, 2); |
||||
kf_bfly3(fout,fstride[i]<<shift,st,m, fstride[i], m2); |
||||
break; |
||||
case 5: |
||||
fft_downshift(fout, st->nfft, &downshift, 3); |
||||
kf_bfly5(fout,fstride[i]<<shift,st,m, fstride[i], m2); |
||||
break; |
||||
#endif |
||||
} |
||||
m = m2; |
||||
} |
||||
fft_downshift(fout, st->nfft, &downshift, downshift); |
||||
} |
||||
|
||||
void opus_fft_c(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) |
||||
{ |
||||
int i; |
||||
celt_coef scale; |
||||
#ifdef FIXED_POINT |
||||
/* Allows us to scale with MULT16_32_Q16(), which is faster than
|
||||
MULT16_32_Q15() on ARM. */ |
||||
int scale_shift = st->scale_shift-1; |
||||
#endif |
||||
scale = st->scale; |
||||
|
||||
celt_assert2 (fin != fout, "In-place FFT not supported"); |
||||
/* Bit-reverse the input */ |
||||
for (i=0;i<st->nfft;i++) |
||||
{ |
||||
kiss_fft_cpx x = fin[i]; |
||||
fout[st->bitrev[i]].r = S_MUL2(x.r, scale); |
||||
fout[st->bitrev[i]].i = S_MUL2(x.i, scale); |
||||
} |
||||
opus_fft_impl(st, fout ARG_FIXED(scale_shift)); |
||||
} |
||||
|
||||
|
||||
void opus_ifft_c(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) |
||||
{ |
||||
int i; |
||||
celt_assert2 (fin != fout, "In-place FFT not supported"); |
||||
/* Bit-reverse the input */ |
||||
for (i=0;i<st->nfft;i++) |
||||
fout[st->bitrev[i]] = fin[i]; |
||||
for (i=0;i<st->nfft;i++) |
||||
fout[i].i = -fout[i].i; |
||||
opus_fft_impl(st, fout ARG_FIXED(0)); |
||||
for (i=0;i<st->nfft;i++) |
||||
fout[i].i = -fout[i].i; |
||||
} |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue