Author SHA1 Message Date
evgeny 6c9be8b134 Record full-suite failure deleting an unowned member-files temp 2026-10-24 19:26:27 +02:00
evgeny cfa0419576 Record repeated slow-duplex failures with native and real clocks 2026-10-24 19:09:34 +02:00
evgeny f8a2ecaabd Add isolated slow-duplex measurements and record BBR diagnosis 2026-10-24 19:07:18 +02:00
evgeny a198cfb9e3 Bind inline dummynet filters to UDP link lifetime and report failures 2026-10-24 17:33:59 +02:00
evgeny 83bf76dd41 Release borrowed send-hook owners before ETCP link teardown 2026-10-24 17:33:59 +02:00
evgeny 36c096c4e9 Make UDP dummynet configuration and resource failures recoverable 2026-10-24 17:33:59 +02:00
evgeny 29d54ed20d Reproduce dummynet configuration datagram and timer failures 2026-10-24 17:33:59 +02:00
evgeny e59ef240e8 test(member-files): let transfer own initial route request 2026-10-24 17:33:47 +02:00
evgeny 27dd9e7b60 test(member-files): preserve unowned download temp on refusal 2026-10-24 17:33:47 +02:00
evgeny 8185b83eba Record final RTT timer, callback and network validation 2026-10-24 17:26:59 +02:00
evgeny 76afc56be8 Make RTT callback regression independent of host uptime 2026-10-24 17:26:59 +02:00
evgeny 5420a07ffc Refresh RTO before RTT callbacks can close the link 2026-10-24 17:26:59 +02:00
evgeny f00d67ca3c Trace refreshed RTO deadlines without per-packet debug noise 2026-10-24 17:26:59 +02:00
evgeny 90dff0b2f2 Refresh pending retransmit timer when timestamp RTT changes 2026-10-24 17:26:59 +02:00
evgeny 83734c920d Expose existing RTO timer refresh and regress timestamp shrink 2026-10-24 17:26:59 +02:00
evgeny e1dcee3c86 Record verified RTT filter and RTO recovery fixes 2026-10-24 17:26:59 +02:00
evgeny d5114f36b4 Clarify timestamp RTT role in transport contracts 2026-10-24 17:26:59 +02:00
evgeny 68a225baec Exercise RTT changes through real ETCP and document RTO inputs 2026-10-24 17:26:59 +02:00
evgeny ef404da44c Recover DATA RTT sampling after path delay increases 2026-10-24 17:26:59 +02:00
evgeny dbda8a24d7 Verify RTT hold rounds and document filter timing 2026-10-24 17:26:59 +02:00
evgeny bb462bad69 Preserve BBR RTT filter age and full-width probe deadlines 2026-10-24 17:26:59 +02:00
evgeny deee734e6c Record duplex validation and remaining BBR follow-ups 2026-10-24 17:26:59 +02:00
evgeny 8ff4a0bf46 Document ETCP receiver-clock bandwidth sampling 2026-10-24 17:26:59 +02:00
evgeny d2703b2145 Use receiver delivery clock for ETCP bandwidth samples 2026-10-24 17:26:59 +02:00
evgeny 9b72a0dadf Close media contract audit and record architecture review 2026-10-24 17:26:12 +02:00
evgeny 5e3701bb2f Clarify encrypted attachment MIME and UI contracts 2026-10-24 17:26:12 +02:00
evgeny 4bab1f7ea4 Align media delivery architecture docs with implementation 2026-10-24 17:26:12 +02:00
evgeny 9d9c82fb03 Document validated MIME fix and regression results 2026-10-24 17:26:12 +02:00
evgeny 99c1ee3394 test(media): cover encrypted MIME validation and side effect free rejection 2026-10-24 17:26:11 +02:00
evgeny d88e39fb4e chat: reject unsupported encrypted attachment types before side effects 2026-10-24 17:26:11 +02:00
evgeny f63d640c95 media: share bounded content type validation in encrypted containers 2026-10-24 17:26:11 +02:00
evgeny 86b7f9cf44 docs(media): record accepted but unreadable MIME containers 2026-10-24 17:26:11 +02:00
evgeny f331d177ca docs(media): clarify preparation ownership and active protocol commands 2026-10-24 17:26:11 +02:00
evgeny 28bbd62d74 docs(media): specify attachment metadata and voice file contracts 2026-10-24 17:26:11 +02:00
evgeny c1717d4047 docs(media): record validated SQLite announcement fix 2026-10-24 17:26:11 +02:00
evgeny 4bfdb1dcc2 test(media): restore console after SQL diagnostic assertions 2026-10-24 17:26:11 +02:00
evgeny 6e3be1aaf3 test(media): verify exact announcement identities after SQL recovery 2026-10-24 17:26:11 +02:00
evgeny eea2b22c4e test(media): cover malformed local announcements and SQLite failures 2026-10-24 17:26:11 +02:00
evgeny 81fc4291db media: validate SQLite records before local block announcements 2026-10-24 17:26:11 +02:00
evgeny 1a212489fd docs(media): correct announcement function reference 2026-10-24 17:26:11 +02:00
evgeny 0d616ce1e7 docs(media): record unchecked SQLite announcements and fix plan 2026-10-24 17:26:11 +02:00
evgeny 7ff8e1f15a docs(media): specify crypto and file transfer ownership contracts 2026-10-24 17:26:11 +02:00
evgeny 40e2fe2e27 docs(media): record validated voice duration fix 2026-10-24 17:26:11 +02:00
evgeny 359a9f3dfc test(media): cover voice duration bounds and early rejection 2026-10-24 17:26:11 +02:00
evgeny 68a7695d81 media: reject voice duration by complete frames before encoding 2026-10-24 17:26:11 +02:00
evgeny 6f3d634c77 media: share attachment duration limit 2026-10-24 17:26:11 +02:00
evgeny 4177ab3536 docs(media): record voice duration boundary defect and fix plan 2026-10-24 17:26:11 +02:00
evgeny 80558f6686 Merge remote-tracking branch 'origin/master' 2026-10-24 17:15:42 +02:00
evgeny da5cf6fc99 Prepare signed existing members before end-to-end CHAT joining 2026-10-24 17:00:53 +02:00
evgeny e6e866a41a Seed complete members in isolated database sync fixtures 2026-10-24 17:00:53 +02:00
evgeny e1675f6910 Preserve chat stress failure logs and group admission diagnostics 2026-10-24 16:56:52 +02:00
evgeny bb13824ad8 Complete NAT wire lifecycle scheduling and diagnostic contracts 2026-10-24 16:53:10 +02:00
evgeny 857a4cee2f Exercise NAT startup rollback and UDP timeout allocation failure 2026-10-24 16:52:10 +02:00
evgeny a6444725da Reject UDP probes when their timeout cannot be scheduled 2026-10-24 16:51:34 +02:00
evgeny 4a00da31da Document complete membership queries separately from row presence 2026-10-24 16:50:41 +02:00
evgeny af50fde9d9 Verify CHAT admission excludes placeholders and deleted members 2026-10-24 16:50:41 +02:00
evgeny f1b62f9112 Require active complete membership for CHAT group admission 2026-10-24 16:46:01 +02:00
evgeny 0b4997c5d5 Release removed NAT helper sockets and reject partial startup 2026-10-24 16:40:51 +02:00
evgeny b8dd2b8cc5 Verify media cancellation without reading a closed CM handle 2026-10-24 16:31:49 +02:00
evgeny 6cfd88afcf Reject zero CM request ids before creating peer state 2026-10-24 16:25:27 +02:00
evgeny 8e7fb1f31b Reconcile CM protocol scheduling cache and cleanup contracts 2026-10-24 16:24:30 +02:00
evgeny 00a85a1468 Keep local CM demand after stale control and reject zero requests 2026-10-24 16:22:15 +02:00
evgeny 182fb97970 Remove obsolete CM disconnect and clarify service ownership 2026-10-24 16:22:15 +02:00
evgeny e6a812042f Check CM bootstrap ownership and mandatory monitor timers 2026-10-24 16:17:20 +02:00
evgeny 2f3cf0933c Keep READY intermediaries eligible and test CM allocation failures 2026-10-24 16:14:48 +02:00
evgeny 80451c43be Make CM cleanup allocation-free and reject incomplete startup 2026-10-24 16:09:39 +02:00
evgeny 007f7c8ae8 Preserve group attachment before transport readiness 2026-10-24 16:01:29 +02:00
evgeny f541ab428b Describe BGP commit boundaries and diagnose initialization failures 2026-10-24 15:57:40 +02:00
evgeny 7ebfddfc4b Clarify group processing callbacks and failed route installation 2026-10-24 15:56:36 +02:00
evgeny 56d513ed85 Inject topology pool database registry and radio timer failures 2026-10-24 15:52:39 +02:00
evgeny d1562bdd0e Check that late BGP allocation failure preserves accepted state 2026-10-24 15:51:43 +02:00
evgeny 145dd1e5ab Prepare BGP paths before committing received node state 2026-10-24 15:50:17 +02:00
evgeny ab5c077600 Unwind topology initialization on pool and SQLite failures 2026-10-24 15:49:42 +02:00
evgeny 6b413d67a6 Exercise group creation rollback radio retry and BGP subnets 2026-10-24 15:48:58 +02:00
evgeny 4363a424d0 Reject incomplete groups and refresh BGP subnet ownership 2026-10-24 15:47:28 +02:00
evgeny 086eefed55 Keep both test transports owned while checking invite timeout 2026-10-24 15:41:16 +02:00
evgeny 5e8ff95c99 Verify invite deadlines and transport loss with shared owners 2026-10-24 15:38:42 +02:00
evgeny 21d8b3844e Apply refreshed peer priorities without replacing group requests 2026-10-24 15:36:12 +02:00
evgeny a7ffaf497e Include concrete transport state in invite regression tests 2026-10-24 15:30:37 +02:00
evgeny 9ce0abe5a9 Cover invite response bounds isolation and failure propagation 2026-10-24 15:30:04 +02:00
evgeny 483e89cd79 Validate channel descriptions and isolate invite request ownership 2026-10-24 15:28:44 +02:00
evgeny 4d9e624f8e Reconcile router protocol and architecture with implementation 2026-10-24 15:15:35 +02:00
evgeny bafda5ae3a Merge origin/master with encrypted attachment ownership fixes
Validation: ./build.sh --no-gui -j4 and make check -j4 passed; 186 tests, no failures.
2026-10-24 15:11:50 +02:00
evgeny 09e620e12a Specify router local delivery, callbacks, flow control and reset contracts 2026-10-24 15:10:06 +02:00
evgeny 3bd9b964fa Remove obsolete router state and clarify receive diagnostics 2026-10-24 15:07:41 +02:00
evgeny a258b77240 Cancel pending transit queues when destroying the router 2026-10-24 15:06:48 +02:00
evgeny 936bacbe4b Clarify encrypted output cleanup contract on filesystem errors 2026-10-24 15:00:05 +02:00
evgeny 1227027c2f Record validated encrypted attachment ownership fix and remaining audit scope 2026-10-24 14:59:12 +02:00
evgeny d8089f8d12 Release router unit fixture registry ownership completely 2026-10-24 14:56:23 +02:00
evgeny 4862a52684 Document encrypted attachment preparation publication and recovery ownership 2026-10-24 14:55:24 +02:00
evgeny e40d8de1b2 Keep full router jitter precision in monitor snapshots 2026-10-24 14:54:48 +02:00
evgeny dbf6bb759c Preserve the unsigned range of router RTT jitter 2026-10-24 14:54:48 +02:00
evgeny be0bd224fa Use canonical media base throughout attachment recovery 2026-10-24 14:51:32 +02:00
evgeny 6bf6d99b9c Test service cancellation after encrypted attachment rename 2026-10-24 14:50:32 +02:00
evgeny 7caebc5a11 Verify attachment recovery preserves active temp owners across restarts 2026-10-24 14:48:25 +02:00
evgeny 6c13755a5c Reject invalid local router payloads and report delivery failures 2026-10-24 14:48:04 +02:00
evgeny c6f28571b7 Keep local callback context private and fully detach services 2026-10-24 14:46:33 +02:00
evgeny 76ba7aae6a Cover encrypted file aliases and I/O failures through publication 2026-10-24 14:45:32 +02:00
evgeny 066b895055 Remove closing router channels before notifying services 2026-10-24 14:44:22 +02:00
evgeny a297d2d649 Record validated ACK batching and remaining solo duplex limits 2026-10-24 14:43:03 +02:00
evgeny f09d76e3fd Recover owned encrypted attachment temporaries in both storage trees 2026-10-24 14:43:03 +02:00
evgeny 7737391ded Document absolute ACK scheduling and cross-path precedence 2026-10-24 14:43:03 +02:00
evgeny dc08256025 Clarify due ACK precedence and bounded burst behavior 2026-10-24 14:43:03 +02:00
evgeny 72d7405a97 Serve due path ACKs before DATA on another link 2026-10-24 14:43:03 +02:00
evgeny 73096e8e3e Verify ACK deadlines through link down and removal 2026-10-24 14:43:03 +02:00
evgeny 51c667edd8 Distinguish early ACK packets from legitimate batched entries 2026-10-24 14:43:03 +02:00
evgeny 2edf09fde1 Cover ACK batching deadlines, timer races, and failure cleanup 2026-10-24 14:43:03 +02:00
evgeny 6ab2526966 Align ACK contracts and diagnostics with deadline state 2026-10-24 14:43:03 +02:00
evgeny c1b0772071 Honor absolute ACK deadlines across wakes and piggyback 2026-10-24 14:43:03 +02:00
evgeny 159237a70c Define absolute ACK deadlines and urgent fallback contracts 2026-10-24 14:43:03 +02:00
evgeny c8051619c1 Reproduce premature ACKs before their absolute deadline 2026-10-24 14:43:03 +02:00
evgeny 2b74d2bf4f Record final queue diagnostics validation and observations 2026-10-24 14:43:03 +02:00
evgeny 35bd19b6c0 Observe ACK queue peaks before immediate send 2026-10-24 14:43:03 +02:00
evgeny 7e166c650c Document multipath diagnostics and the ACK deadline defect 2026-10-24 14:43:03 +02:00
evgeny 055ffa27b2 Add offline multipath charts and count timestamped ACKs 2026-10-24 14:43:03 +02:00
evgeny cd3cf38f06 Expose callback and stream progress in multipath snapshots 2026-10-24 14:43:03 +02:00
evgeny 12b0ab437e Capture buffered multipath queue and pacing diagnostics 2026-10-24 14:43:03 +02:00
evgeny a9bbb470c2 Record full validation of retry scheduling changes 2026-10-24 14:43:03 +02:00
evgeny 6894c66f3a Record passing fault matrix and remaining duplex limits 2026-10-24 14:43:03 +02:00
evgeny a2f9b76d10 Record retry scheduler architecture and regression coverage 2026-10-24 14:43:03 +02:00
evgeny 3d40951e66 Clarify input resumption with queued DATA 2026-10-24 14:43:03 +02:00
evgeny 4350b54769 Publish attachment output once and preserve ownership through cancellation 2026-10-24 14:41:39 +02:00
evgeny 92d52d25a8 Prepare encrypted attachments in exclusively owned durable temporaries 2026-10-24 14:40:53 +02:00
evgeny 9e0949434e Preserve video temp mode and test durable file commit boundaries 2026-10-24 14:39:46 +02:00
evgeny aa7a9b74f4 Select ordinary temporary file mode for copies and media blocks 2026-10-24 14:38:54 +02:00
evgeny 12556344b0 Add durable owned temporary file lifecycle and shared publication helpers 2026-10-24 14:38:34 +02:00
evgeny 2e3ab00ce6 Design owned temporary output and durable publication for encrypted attachments 2026-10-24 14:34:00 +02:00
evgeny f92c78a2e0 Merge remote-tracking branch 'origin/master' 2026-10-24 14:33:18 +02:00
evgeny 7c7c316c3f Document atomic local publication and bootstrap cleanup 2026-10-24 14:25:18 +02:00
evgeny 94aaf3c8e3 Align topology model documentation with current runtime contracts 2026-10-24 14:23:50 +02:00
evgeny c282d03f25 Describe NODEINFO byte layout, identity and registry semantics 2026-10-24 14:20:29 +02:00
evgeny f7db49fc97 Cover local topology replacement allocation failures 2026-10-24 14:18:42 +02:00
evgeny 8cc97d2ffe Record reproducible encrypted file alias data loss during media audit 2026-10-24 14:17:35 +02:00
evgeny f9994e11b5 Clarify local update ownership, results and failure contracts 2026-10-24 14:17:13 +02:00
evgeny 1e0281d4d8 Prepare local topology replacements before retiring old state 2026-10-24 14:16:39 +02:00
evgeny 42a58881ca Document SQLite snapshots, projections and membership contracts 2026-10-24 14:13:26 +02:00
evgeny 19c3cc0434 Reject interrupted membership channel queries 2026-10-24 14:10:28 +02:00
evgeny 81e2a9aa4a Return peer lists atomically after successful SQL iteration 2026-10-24 14:09:36 +02:00
evgeny 575a3788e3 Roll back failed channel deletion and node classification 2026-10-24 14:07:28 +02:00
evgeny 461417beac Record completed proxy fixes and architecture validation results 2026-10-24 14:03:56 +02:00
evgeny 26b5da6835 Release partial SQL bootstrap records on allocation failures 2026-10-24 14:03:48 +02:00
evgeny 99608de811 Reject failed topology writes and malformed stored signatures 2026-10-24 14:01:37 +02:00
evgeny 5c4207c145 Keep resolved routing audit details out of the task backlog 2026-10-24 14:00:22 +02:00
evgeny a42dc38874 Check real raw ICMP explicitly alongside deterministic loopback 2026-10-24 14:00:17 +02:00
evgeny 83194b19c7 Use supported TCP proxy exit option in TUN integration fixture 2026-10-24 13:59:29 +02:00
evgeny 14c5ddf864 Propagate failures when storing verified node identities 2026-10-24 13:57:05 +02:00
evgeny f10a6f70c5 Describe checked datagram startup and bounded TCP HUP handling 2026-10-24 13:54:40 +02:00
evgeny e2ee5f2397 Document atomic proxy client creation and instance shutdown order 2026-10-24 13:52:22 +02:00
evgeny 324b289968 Bound topology dump writes and return the actual length 2026-10-24 13:45:08 +02:00
evgeny 5914e26579 Update datagram and TCP exit documentation for explicit ownership 2026-10-24 13:42:20 +02:00
evgeny 2894e06014 Document HUP polling contract and isolate ICMP fault injection 2026-10-24 13:41:16 +02:00
evgeny d0edacb7b9 Test HUP registration failure and teardown with pending TCP writes 2026-10-24 13:39:50 +02:00
evgeny 1ce285c09b Preserve datagram bindings and verify optional ICMP behavior 2026-10-24 13:38:45 +02:00
evgeny 8dcd2fac3b Record topology dump truncation defect found during audit 2026-10-24 13:37:22 +02:00
evgeny bb2ef05157 Roll back required TUN mappings and verify instance startup failures 2026-10-24 13:36:22 +02:00
evgeny c4eddfe50e Cover proxy startup rollback and datagram socket failures 2026-10-24 13:34:51 +02:00
evgeny f0629ea679 Exercise orderly and abrupt TLS shutdown in proxy integration 2026-10-24 13:29:45 +02:00
evgeny 4da9d85545 Honor TCP read backpressure across HUP and restore polling 2026-10-24 13:28:44 +02:00
evgeny d5444fb9bc Check UDP socket operations and roll back incomplete flows 2026-10-24 13:25:43 +02:00
evgeny 50da7fe457 Make TCP proxy construction atomic and retain instance datagrams 2026-10-24 13:24:30 +02:00
evgeny 99be040488 Separate ICMP exit startup from shared context ownership 2026-10-24 13:23:58 +02:00
evgeny 902ffa05b4 Record NODEINFO parsing fix and sanitizer validation 2026-10-24 13:21:42 +02:00
evgeny 051b13e161 docs(proxy): align planned teardown with instance ownership 2026-10-24 13:18:52 +02:00
evgeny 04ace670ed docs(proxy): design error handling and lifecycle fixes 2026-10-24 13:17:33 +02:00
evgeny 99dd48480f Align REALITY round-trip fixture with NODEINFO ownership 2026-10-24 13:09:16 +02:00
evgeny acd9dcd1a8 Cover empty NODEINFO outputs and malformed BGP preservation 2026-10-24 13:03:42 +02:00
evgeny 790978458c Make NODEINFO section parsing independent of requested outputs 2026-10-24 13:01:30 +02:00
evgeny f9a3830416 Add NODEINFO wire parsing and allocation regressions 2026-10-24 12:59:00 +02:00
evgeny 5f51149622 Record routing audit progress and NODEINFO parsing defect 2026-10-24 12:42:47 +02:00
evgeny 0688ffc26d docs(proxy): qualify integration audit results 2026-10-24 12:39:54 +02:00
evgeny ec1fc52da1 docs(proxy): record teardown findings and refine counters 2026-10-24 12:39:09 +02:00
evgeny 5e80a9cc6c docs(proxy): consolidate current protocol and audit coverage 2026-10-24 12:38:27 +02:00
evgeny f003aaa92d docs(proxy): reconcile TCP client server and listener behavior 2026-10-24 12:36:16 +02:00
evgeny bf03348c64 Correct connectivity probe and IPv4 routing documentation 2026-10-24 12:19:22 +02:00
evgeny 0445c38c31 Clarify address profiles and route statistics contracts 2026-10-24 12:18:30 +02:00
evgeny 30a69712fc Align route table and observation documentation 2026-10-24 12:17:44 +02:00
evgeny 27cf6ae365 docs(proxy): document per-instance datagram contexts 2026-10-24 12:16:47 +02:00
evgeny 1ad8ff73ad Document routing helper ownership and wire contracts 2026-10-24 12:16:31 +02:00
evgeny 0bc0d6cc62 Enforce disjoint IPv4 route insertion atomically 2026-10-24 12:15:29 +02:00
evgeny 23cec465bf docs(proxy): specify TCP lifecycle and UDP ownership 2026-10-24 12:14:42 +02:00
evgeny a4abe30b18 docs(proxy): clarify flow control and HTTP parser contracts 2026-10-24 12:13:05 +02:00
evgeny 34d1dcb7c8 Document send scanning, retry rounds, and fast stall recovery 2026-10-24 12:01:20 +02:00
evgeny 93bf846fc4 Preserve retry priority during bounded burst measurements 2026-10-24 11:46:42 +02:00
evgeny c87cc6e4a2 Match the input waiter callback signature 2026-10-24 11:41:39 +02:00
evgeny 1fbd1e466b Verify retry history, stall recovery, and admission callbacks 2026-10-24 11:39:31 +02:00
evgeny b4d5da1df2 Wait for both configured RTOs in queue bypass regression 2026-10-24 11:35:25 +02:00
evgeny ff74ac8295 Scan past blocked DATA while prioritizing sendable retries 2026-10-24 11:31:46 +02:00
evgeny dcd6cd93c4 Select unused retry paths before starting another round 2026-10-24 11:29:13 +02:00
evgeny d439ee7ca7 Track stalled DATA paths and accepted retry rounds safely 2026-10-24 11:28:29 +02:00
evgeny 39ee5b392b Define retry path history and fast stall state contracts 2026-10-24 11:27:48 +02:00
evgeny acde6bda9d Test send queue bypass, retry priority, and bounded admission 2026-10-24 11:22:28 +02:00
evgeny 8167e0e5f5 Record recovery stage checks and repeated stalled-path cycles 2026-10-24 10:43:25 +02:00
evgeny 5a32d08ffd Buffer retry choice diagnostics until measurement completes 2026-10-24 10:32:44 +02:00
evgeny b98b87ea12 Explain retry choices between stalled multipath channels 2026-10-24 10:26:14 +02:00
evgeny 05d3fa30d9 Record path history for the fragment blocking delivery 2026-10-24 10:19:36 +02:00
evgeny 516ed04897 Identify the fragment behind maximum multipath message latency 2026-10-24 10:18:11 +02:00
evgeny 1d295c7d3a Measure multipath recovery stages separately from RTO waits 2026-10-24 10:05:13 +02:00
evgeny 1c6a7e7bf9 Remove speculative early retries from the ETCP plan 2026-10-24 09:56:54 +02:00
evgeny f1f4976222 Record retry recovery status and full regression results 2026-10-24 09:48:19 +02:00
evgeny 14515ca446 Document retry wait policy and fault recovery measurements 2026-10-24 09:46:38 +02:00
evgeny 8dd03c2b5c Wait for another UP path before retrying ETCP DATA 2026-10-24 09:38:20 +02:00
evgeny 412b9bc713 Test retry waiting for another available ETCP path 2026-10-24 09:34:21 +02:00
evgeny ff2860ffbc Record pacer validation and remaining multipath failures 2026-10-24 09:09:42 +02:00
evgeny c7a530092d Check pacer reset callback against its actual contract 2026-10-24 02:54:41 +02:00
evgeny deac10e3f6 Wake ETCP pacer at its first permitted transmit time 2026-10-24 02:49:25 +02:00
evgeny 42c4195f2e Test pacer eligibility boundaries and readiness lifecycle 2026-10-24 02:25:23 +02:00
evgeny ce3d942efe Record multipath baseline and verify pacer wakeup defect 2026-10-24 02:17:38 +02:00
evgeny a22a27b7a9 Record validated signature lifecycle fix and exact contracts 2026-10-24 01:15:49 +02:00
evgeny 7d57b484d8 Focus signature test logs on lifecycle and failure stages 2026-10-24 01:10:12 +02:00
evgeny 44acb5aecf Document streaming signature state ownership and retries 2026-10-24 01:03:24 +02:00
evgeny 5d11f90cd4 Make streaming signature initialization and cleanup safe 2026-10-24 01:02:10 +02:00
evgeny 4824f3523d Test streaming signature ownership and OpenSSL failures 2026-10-24 01:00:36 +02:00
evgeny 5a7823e6ef Record streaming signature OOM defect and completed temp fix 2026-10-24 00:46:40 +02:00
evgeny 5792db996c Clarify crypto key derivation and actual module dependencies 2026-10-24 00:44:29 +02:00
evgeny 16a33ac9f7 Correct remaining crypto examples and streaming signature buffer contract 2026-10-24 00:37:18 +02:00
evgeny 3afd31e561 Align crypto documentation with random CCM nonces and public-key node IDs 2026-10-24 00:35:13 +02:00
evgeny 2715faffff Document receiver-bounded BBR samples and diagnostic input contracts 2026-10-24 00:33:19 +02:00
evgeny 9459b43211 Remove stale pacing and handshake descriptions from ETCP protocol specification 2026-10-24 00:32:42 +02:00
evgeny 964f6cb045 Correct ETCP diagnostic filters and state dump documentation 2026-10-24 00:30:33 +02:00
evgeny bed99abbe9 Clarify STCP client ownership, ping lifetime and handshake byte order 2026-10-24 00:28:58 +02:00
evgeny d6eecd6f1c Document STCP listener arguments, accepted ownership and current handshake phases 2026-10-24 00:27:56 +02:00
evgeny ecdd767934 Align connection documentation with current UDP sessions and keepalive wire format 2026-10-24 00:27:14 +02:00
evgeny 1cf2cc10dd Keep file I/O return-value overview consistent with orphan cleanup API 2026-10-24 00:21:25 +02:00
evgeny 39f9d6a0e9 Keep active block and worker temporaries during media cache cleanup 2026-10-24 00:16:57 +02:00
evgeny e84c66a3d3 Reproduce orphan cleanup during a real encrypted attachment transfer 2026-10-24 00:15:16 +02:00
evgeny e195894048 Protect owned temporary files from concurrent orphan removal 2026-10-24 00:13:18 +02:00
evgeny ee437b9473 Align ETCP header declarations, timestamp units and UDP contracts with code 2026-10-24 00:06:45 +02:00
evgeny 299c613760 Correct ETCP API callback and datagram ownership documentation 2026-10-24 00:06:00 +02:00
evgeny 74b63cc315 Verify ETCP registry identity and failure ownership invariants 2026-10-24 00:01:02 +02:00
evgeny 032f8e83b2 Publish incoming UDP peers before callbacks and preserve accepted ownership 2026-10-23 23:57:51 +02:00
evgeny c7d7789a58 Reuse ETCP registry entries and reject duplicate peer indexing 2026-10-23 23:57:32 +02:00
evgeny fb995db489 Cover UDP accept cancellation with surviving paths and NCD owners 2026-10-23 23:50:39 +02:00
evgeny 942736d604 Check accepted and rejected MTU boundaries in both UDP handshake directions 2026-10-23 23:42:49 +02:00
evgeny b8021efed8 Validate UDP handshake MTU and nonzero path IDs before state updates 2026-10-23 23:41:58 +02:00
evgeny 1260884485 Exercise invalid UDP INIT path identifiers and MTU limits 2026-10-23 23:40:37 +02:00
evgeny 52d9077bb5 Describe retained link scopes and callback-safe RX timeouts 2026-10-23 23:37:44 +02:00
evgeny 519a2ee242 Cover UDP callback deletion, connection close and path replacement 2026-10-23 23:35:55 +02:00
evgeny 6d6ec7e725 Keep RX timeout and link-status callbacks inside retained link scopes 2026-10-23 23:31:16 +02:00
evgeny d0a168f89e Retain ETCP links across callbacks and release closed paths on scope exit 2026-10-23 23:30:43 +02:00
evgeny 5ccd47dd17 Bind incoming UDP INIT identity to the authenticated key 2026-10-23 23:17:25 +02:00
evgeny 2f6ff9303e Reject authenticated INIT requests claiming another node identity 2026-10-23 23:17:01 +02:00
evgeny d9aede4138 Drain pending UDP traffic before snapshotting INIT validation state 2026-10-23 23:13:03 +02:00
evgeny 0860b1e06d Validate the complete UDP INIT request body before reading fields 2026-10-23 23:09:27 +02:00
evgeny 10927b1d08 Allow early wire rejection in INIT request boundary tests 2026-10-23 23:06:23 +02:00
evgeny 3a8cc8a326 Exercise encrypted UDP INIT request body boundaries 2026-10-23 23:03:48 +02:00
evgeny bf7464a4d9 Document connection ownership after a cancelled TCP accept 2026-10-23 22:53:58 +02:00
evgeny e51badebcb Preserve active owners and paths when an incoming TCP link is cancelled 2026-10-23 22:53:36 +02:00
evgeny d5d103148f Test ownership handoff while cancelling an incoming TCP path 2026-10-23 22:52:40 +02:00
evgeny d5721563fc Clarify NEW cancellation and subscription mutation semantics 2026-10-23 22:43:09 +02:00
evgeny 274554f7a5 Cover TCP creation cancellation and self-unsubscription on both roles 2026-10-23 22:42:20 +02:00
evgeny 8fe1011da2 Document constructor cancellation and link memory ownership 2026-10-23 22:41:42 +02:00
evgeny 21a4dd7a70 Exercise callback unsubscription and nested dispatch on all ETCP chains 2026-10-23 22:40:47 +02:00
evgeny 33912355d6 Keep removed ETCP subscriptions until the outer dispatch returns 2026-10-23 22:39:56 +02:00
evgeny 5178f7abc6 Define instance callback dispatch lifetime for safe unsubscription 2026-10-23 22:39:06 +02:00
evgeny d0ee0341c5 Verify cancelled link constructors and guard UDP handshake callbacks 2026-10-23 22:36:39 +02:00
evgeny a2861b0570 Keep cancelled links alive until their constructor returns 2026-10-23 22:35:14 +02:00
evgeny a51c080407 Return NULL when callbacks cancel ETCP connection creation 2026-10-23 22:34:46 +02:00
evgeny 2ea4fb94ab Test nested deletion of the current and neighboring ETCP paths 2026-10-23 22:03:58 +02:00
evgeny 986193055e Suppress stale connection UP after its callback removes the last path 2026-10-23 22:03:29 +02:00
evgeny a43d478495 Cover close from connection INIT and UP callbacks on both roles 2026-10-23 22:02:44 +02:00
evgeny bf5a128a05 Correct STCP close contracts and sanitizer issues in its test 2026-10-23 22:01:48 +02:00
evgeny 477d8f995b Document STCP adapter ownership, callbacks and reconnect lifecycle 2026-10-23 21:59:12 +02:00
evgeny 03d69cb772 Verify TCP retry timing, recovery and cancellation end to end 2026-10-23 21:58:27 +02:00
evgeny f5e4c78b8c Exercise repeated close during the server link teardown event 2026-10-23 21:57:10 +02:00
evgeny 425e1a527b Provide the peer snapshot for the incoming test owner 2026-10-23 21:56:35 +02:00
evgeny f1e3c2c08a Hold the incoming connection before exercising DOWN callbacks 2026-10-23 21:55:54 +02:00
evgeny bd9f1bb0c9 Flush cancelled timers in teardown tests and clarify adapter close 2026-10-23 21:54:49 +02:00
evgeny 1fb52d3f5b Report active resources when callback teardown is unbalanced 2026-10-23 21:54:15 +02:00
evgeny 4614577d19 Stop readiness publication after callback close and rescan teardown links 2026-10-23 21:49:11 +02:00
evgeny f37221107d Guard TCP link transitions across callbacks and unify reconnect backoff 2026-10-23 21:48:11 +02:00
evgeny 42e7b9b1b6 Own STCP adapter queues and report connection failures before READY 2026-10-23 21:46:12 +02:00
evgeny 7753a0f11e Include the raw STCP server API in adapter regression tests 2026-10-23 21:43:17 +02:00
evgeny 4bfec7257f Cover TCP callback teardown on both roles and guard link close reentry 2026-10-23 21:40:43 +02:00
evgeny 2cad81050d Exercise early STCP adapter failure and cancellation callbacks 2026-10-23 21:39:32 +02:00
evgeny 18f10b23b3 Record compact attempt validation and aggregation check 2026-10-23 19:34:46 +02:00
evgeny a3eeebbc52 Clarify attempt reservation ownership on epoch exhaustion 2026-10-23 19:33:11 +02:00
evgeny 85c92e35a8 Update multipath contract for compact attempt pairs 2026-10-23 19:30:22 +02:00
evgeny be95e93a36 Reject old encrypted ACKs when compact attempt pairs restart in a new epoch 2026-10-23 19:27:06 +02:00
evgeny 7c09bfca7f Stop packet construction when input callbacks restart the stream 2026-10-23 19:26:00 +02:00
evgeny 77a737fd71 Document compact attempt wire layout and epoch reuse protection 2026-10-23 19:25:20 +02:00
evgeny 9a9517deab Exercise ACK splitting beyond compact entry capacity 2026-10-23 19:24:52 +02:00
evgeny b73d6e91e2 Verify compact attempt isolation, wire boundaries and exhaustion recovery 2026-10-23 19:23:30 +02:00
evgeny 0987d9dd04 Adapt encrypted session and window fixtures to compact attempt fields 2026-10-23 19:20:49 +02:00
evgeny ccbd214c97 Encode 16-bit attempt numbers and guard pair reuse by session epochs 2026-10-23 19:19:54 +02:00
evgeny 6414cb15ef Define compact per-fragment ETCP attempt numbers 2026-10-23 19:19:07 +02:00
evgeny 8c3fcfd1c8 Log fragment bytes and session epochs on normalizer desync 2026-10-23 18:15:17 +02:00
evgeny 1b7d314d85 Record bandwidth-only correction validation and remaining speed limits 2026-10-23 16:48:47 +02:00
evgeny ea5a0b7d5a Describe receiver-clock bandwidth bounds and ACK wire format 2026-10-23 16:44:07 +02:00
evgeny 6fc964031c Check ACK receiver point diagnostics at buffer boundaries 2026-10-23 16:40:52 +02:00
evgeny 4bf47ca357 Exercise receiver bandwidth bounds and ACK progress framing 2026-10-23 16:39:12 +02:00
evgeny ea4a92125b Carry receiver progress only in ACKs with attempt confirmations 2026-10-23 16:23:20 +02:00
evgeny 5586528555 Bound ETCP bandwidth samples by receiver delivery progress 2026-10-23 16:22:13 +02:00
evgeny e8d9d683dc Record duplex plan trials and measured ACK bandwidth costs 2026-10-23 16:10:46 +02:00
evgeny cc01457957 Record verified INIT recovery and BGP regression results 2026-10-23 14:24:31 +02:00
evgeny 5b8aba9c8b Drive callback deletion through the public reinit transition 2026-10-23 14:19:53 +02:00
evgeny f5d67d5240 Test link deletion from INIT and keepalive callbacks 2026-10-23 14:19:24 +02:00
evgeny 35ef504423 Document INIT liveness and response validation contracts 2026-10-23 14:16:28 +02:00
evgeny a87d3116af Validate INIT response body before updating peer state 2026-10-23 14:14:53 +02:00
evgeny c5f743a09d Cover INIT liveness with an independent healthy link 2026-10-23 14:01:57 +02:00
evgeny b167a27e85 Document RX ownership and limit readiness wakeups to transitions 2026-10-23 13:48:09 +02:00
evgeny f00a5bd306 Apply INIT reachability before reply and publish guarded RX transitions 2026-10-23 13:45:23 +02:00
evgeny 608caf01c1 Initialize the liveness fixture crypto and drain cancelled timers 2026-10-23 13:41:59 +02:00
evgeny e801fce2c6 Add encrypted UDP INIT liveness and one-way recovery regression 2026-10-23 13:38:36 +02:00
evgeny e89948f5ed Include the lowest reverse solo rate in measurement summary 2026-10-23 13:20:44 +02:00
evgeny 3c7b59ef02 Report three-run multipath rates and confirmed INIT liveness failure 2026-10-23 13:20:15 +02:00
evgeny 54fb4185f1 Identify one-way INIT liveness defect behind intermittent BGP failure 2026-10-23 13:14:51 +02:00
evgeny ac264c254c Expose transport topology in BGP route exchange failures 2026-10-23 13:13:47 +02:00
evgeny 5403647aee Record full regression results after retransmission timeout change 2026-10-23 13:05:22 +02:00
evgeny 3a007fbb29 Record jitter control and the cost of a shorter retransmission margin 2026-10-23 13:02:18 +02:00
evgeny 92e9281623 Record timeout matrix improvements and outstanding latency thresholds 2026-10-23 13:01:37 +02:00
evgeny e0f28bf851 Document reduced RTT multiplier and jitter margin for RTO 2026-10-23 12:52:59 +02:00
evgeny 9dbef48eb3 Use RTT jitter and fixed margin for ETCP retransmission timeout 2026-10-23 12:52:35 +02:00
evgeny e84e17c396 Record successful proxy burst and load validation after OOM changes 2026-10-23 12:46:10 +02:00
evgeny 9cc921f615 Record full regression failures and diagnose active media temp deletion 2026-10-23 12:45:38 +02:00
evgeny 0d5e9332e3 Clarify connection references versus active callback lifetime 2026-10-23 12:43:26 +02:00
evgeny b1e94eff34 Describe OOM session interruption and normalizer ownership precisely 2026-10-23 12:42:05 +02:00
evgeny bbe43b9842 Record completed fail-fast OOM recovery and remaining multipath work 2026-10-23 12:41:14 +02:00
evgeny 3bba05d354 Give encrypted DATA fixture a valid keepalive receive timeout 2026-10-23 12:36:20 +02:00
evgeny d43716933f Test encrypted two-sided recovery after normalizer OOM 2026-10-23 12:33:58 +02:00
evgeny 3c18e594d1 Keep the session deadline unchanged across handshake retries 2026-10-23 12:32:57 +02:00
evgeny 23d426fd0c Document fail-fast OOM and session interruption contracts 2026-10-23 12:32:56 +02:00
evgeny f221fa5a89 Exercise normalizer waiter allocation failure on a busy ETCP queue 2026-10-23 12:30:38 +02:00
evgeny 6e33675503 Verify OOM session abort, recovery and reentrant callback teardown 2026-10-23 12:29:29 +02:00
evgeny 76398d204a Add deterministic normalizer and restart fault injection hooks 2026-10-23 12:28:13 +02:00
evgeny d2b195473d Test queue discard without allocation or stale callbacks 2026-10-23 12:27:38 +02:00
evgeny caf37e9541 Make stream restart terminate explicitly if session scheduling fails 2026-10-23 12:24:48 +02:00
evgeny 316180c276 Abort normalizer sessions on OOM and guard reentrant delivery callbacks 2026-10-23 12:24:48 +02:00
evgeny 5f085b2086 Discard failed ETCP streams without allocating or replaying fragments 2026-10-23 12:22:22 +02:00
evgeny 466a56b499 Add allocation-free queue discard for terminal stream resets 2026-10-23 12:20:34 +02:00
evgeny 0989eff73d Record complete proxy integration checks and normalizer ownership fix outline 2026-10-23 11:32:10 +02:00
evgeny 187aa02b49 Record passing full regression and eight sanitizer checks 2026-10-23 11:29:53 +02:00
evgeny 0aaf6dfea0 Check metrics aggregate ACK queues and RTO state across paths 2026-10-23 11:26:45 +02:00
evgeny 9c40b489cf Record implemented multipath foundation and remaining reliability blockers 2026-10-23 11:25:13 +02:00
evgeny ce798a193e Remove stale fixture pointer and verify full transmission IDs in packet dumps 2026-10-23 11:22:37 +02:00
evgeny ddba151aa5 Adapt control metrics fixture to per-link ACK ownership 2026-10-23 11:17:20 +02:00
evgeny 2fddf839af Show transmission identity and frontier queries in ETCP packet diagnostics 2026-10-23 11:13:26 +02:00
evgeny 7bb43c42d6 Document per-path attempt ACKs, loss timers, pacing and memory bounds 2026-10-23 11:12:40 +02:00
evgeny b442d131c9 Exercise bounded attempt and ACK storage after logical delivery 2026-10-23 11:10:19 +02:00
evgeny 783646a967 Apply attempt budgets before selecting DATA paths 2026-10-23 11:09:28 +02:00
evgeny b74ff2633d Bound path ACK queues and physical attempt memory 2026-10-23 11:09:28 +02:00
evgeny b97169de19 Verify DATA pacing independently of piggyback ACK size 2026-10-23 11:04:11 +02:00
evgeny 72f48d1f14 Keep DATA pacing units consistent and test startup delay exit 2026-10-23 10:59:30 +02:00
evgeny 227104e224 Leave BBR startup after a full round of excessive queue delay 2026-10-23 10:58:38 +02:00
evgeny d57a729b92 Preserve bounded pacing credit across millisecond timer rounding 2026-10-23 10:45:39 +02:00
evgeny 3ca9926c48 Release synthetic path queues in the session-timeout fixture 2026-10-23 10:42:43 +02:00
evgeny 4c9a32d865 Observe peer-window recovery after the deferred queue resume 2026-10-23 10:40:48 +02:00
evgeny 5e436975c6 Exercise retry rotation, late attempt ACKs and receiver admission faults 2026-10-23 10:38:23 +02:00
evgeny a4a9ae7b0a Return receive-window query replies on the requesting path 2026-10-23 10:35:38 +02:00
evgeny 34a31f63be Update encrypted session fixtures and multipath observation for attempt IDs 2026-10-23 10:29:20 +02:00
evgeny 75485bf7bf Aggregate pending path ACKs in chat transport status 2026-10-23 10:28:17 +02:00
evgeny 432017cc22 Verify logical delivery and physical window counters independently 2026-10-23 10:27:38 +02:00
evgeny 147648ffd0 Expose per-path attempts and ACK queues in transport diagnostics 2026-10-23 10:25:27 +02:00
evgeny 1b3c4552fb Account for transmission IDs in MTU and packet diagnostics 2026-10-23 10:24:29 +02:00
evgeny cc510fc74c Separate stream delivery from path ACKs and acknowledge retained DATA 2026-10-23 10:23:14 +02:00
evgeny abe3d4bc89 Rotate retransmissions per fragment and commit only accepted sends 2026-10-23 10:14:46 +02:00
evgeny 7bc2ac5a46 Implement per-path attempt accounting and isolated ACK delivery samples 2026-10-23 10:13:22 +02:00
evgeny b68e0ac3db Define independent logical fragments and per-path transmission attempts 2026-10-23 10:09:44 +02:00
evgeny 3c44c3d7bc Keep unknown BBR bandwidth from imposing a one-byte pacing stall 2026-10-23 02:32:36 +02:00
evgeny e21cd26511 Apply BBR pacing through one owner and keep burst estimates diagnostic 2026-10-23 02:28:45 +02:00
evgeny 7e413db1e1 Keep pacing in bytes per second and reprice outstanding shaper debt 2026-10-23 02:28:11 +02:00
evgeny b39a861a57 Initialize STARTUP pacing from the actual initial DATA window 2026-10-23 02:24:10 +02:00
evgeny b349d7b7b2 Document agreed retransmission bounds and verified timer behavior 2026-10-23 02:21:43 +02:00
evgeny c3ac337ef0 Clamp ETCP retransmission timeout to 10 ms through 1 second 2026-10-23 02:20:10 +02:00
evgeny 4aae1f6151 Specify retry path rotation and close ACK ownership gaps 2026-10-23 02:08:04 +02:00
evgeny 259dfa35d9 Record RTT feedback experiments and refine multipath recovery plan 2026-10-23 01:56:01 +02:00
evgeny 7e0a0905e5 Document BBR queue time policy and its physical delay limits 2026-10-23 01:03:27 +02:00
evgeny 34e3d9bba0 Verify queue budget saturation and the BBR fast path contract 2026-10-23 01:02:15 +02:00
evgeny 599d8ad421 Bound BBR probe windows by the steady-state queue time budget 2026-10-23 01:01:25 +02:00
evgeny 4035794631 Define and test the BBR steady-state queue time budget 2026-10-23 01:00:36 +02:00
evgeny 5f406c2a41 Document BBR queue budget conflict with the fixed latency target 2026-10-23 00:58:29 +02:00
evgeny 94d6dd05c8 Expose BBR bandwidth and ACK budget alongside physical queue peaks 2026-10-23 00:54:27 +02:00
evgeny 67ae4ecf11 Record full regression results and reproducible NAT profile failures 2026-10-23 00:51:44 +02:00
evgeny d0f122f4d8 Record verified RTT probe fixes and remaining multipath failures 2026-10-23 00:49:02 +02:00
evgeny 08b6b2e2a1 Document DATA MSS and shared BBR transport window contracts 2026-10-23 00:45:09 +02:00
evgeny 5298fe9f6a Keep BBR loss bounds above the DATA window minimum 2026-10-23 00:43:54 +02:00
evgeny aefa8ff680 Check BBR loss bounds against the minimum DATA window 2026-10-23 00:43:31 +02:00
evgeny 0f23076f48 Apply BBR DATA windows consistently during startup and RTT probes 2026-10-23 00:42:45 +02:00
evgeny 92a535e009 Cover BBR probe drain and transport DATA window consistency 2026-10-23 00:42:02 +02:00
evgeny 519d6e4eaf Record verified BBR fixes and remaining multipath contract conflicts 2026-10-23 00:25:55 +02:00
evgeny 716db3a820 Saturate BBR byte windows instead of wrapping at large BDP 2026-10-23 00:24:35 +02:00
evgeny 3d6f855d34 Keep BBR probing and aggregation counters dimensionally consistent in bytes 2026-10-23 00:17:14 +02:00
evgeny 229e992695 Cover probing byte budgets and ACK aggregation beyond 64KiB 2026-10-23 00:16:40 +02:00
evgeny a79c724110 Grow BBR probe inflight bounds in MSS-sized byte increments 2026-10-23 00:15:46 +02:00
evgeny 6b1298aa62 Reproduce byte-sized instead of MSS-sized BBR probing growth 2026-10-23 00:15:28 +02:00
evgeny 0364abb00e Log sampled BBR model state without flooding transport diagnostics 2026-10-23 00:12:28 +02:00
evgeny f8d532b04d Describe ACK handling and bounded DATA pacing in the ETCP protocol 2026-10-23 00:10:01 +02:00
evgeny 4dba742149 Document BBR byte units and flight-based delivery rate samples 2026-10-23 00:10:00 +02:00
evgeny 88eb0f2108 Require multipath goodput without inflating channel queues 2026-10-23 00:08:15 +02:00
evgeny ade0e93f7f Measure actual channel queueing delay independently of propagation RTT 2026-10-23 00:08:15 +02:00
evgeny addff8547e Keep the ACK compression fixture within its known retransmission deadline 2026-10-23 00:06:30 +02:00
evgeny 1c18bdc9ab Measure BBR delivery over packet flights instead of inter-ACK gaps 2026-10-23 00:06:30 +02:00
evgeny f3008b7aba Snapshot delivery and send intervals for per-link BBR rate measurement 2026-10-23 00:06:30 +02:00
evgeny c2f4f048d7 Reproduce inflated delivery-rate samples when ACKs arrive together 2026-10-23 00:05:48 +02:00
evgeny 3c38072b9b Send ACK independently of the local outgoing DATA congestion pacer 2026-10-23 00:03:29 +02:00
evgeny 4cfd0ec89f Identify DATA in local datagram metadata without changing the wire header 2026-10-23 00:03:29 +02:00
evgeny 08c42e62ef Reproduce ACK starvation behind the reverse-direction DATA pacer 2026-10-23 00:02:57 +02:00
evgeny dd153264e1 Verify byte-rate scaling at multiple MTUs and multi-gigabit rates 2026-10-23 00:00:40 +02:00
evgeny 6f87ea1426 Keep BBR pacing and BDP in byte units without a second MSS factor 2026-10-22 23:59:57 +02:00
evgeny 6fec5d6d98 Reproduce MSS-dependent BBR pacing and BDP with a dimensional regression 2026-10-22 23:59:20 +02:00
evgeny 9b9a9f2e00 Record final multipath fault results and sanitizer validation 2026-10-22 23:41:45 +02:00
evgeny 27a7b7bf25 Declare the multipath phony target without overlapping Automake conditions 2026-10-22 23:40:12 +02:00
evgeny c9ba018a0d Bound fault steady goodput by newly received unique DATA 2026-10-22 23:37:24 +02:00
evgeny df5f6263bd Measure short HOLD across resume and arm faults before propagation completes 2026-10-22 23:37:02 +02:00
evgeny 0b5a892a88 Document multipath acceptance criteria and reproduced protocol limitations 2026-10-22 23:36:21 +02:00
evgeny 076016f34a Expose multipath queue accounting and add a dedicated long-running check target 2026-10-22 23:34:20 +02:00
evgeny 8417b1955c Document emulator queue bounds and account for allocation failures 2026-10-22 23:33:14 +02:00
evgeny 67322582f6 Report delivery failures per scenario without hiding subsequent channel cases 2026-10-22 23:32:34 +02:00
evgeny 439361cb52 Exclude previously buffered data from multipath utilization results 2026-10-22 23:31:02 +02:00
evgeny 6e350760d1 Check active channel stalls, delivery deadlines and delayed packet recovery 2026-10-22 23:29:29 +02:00
evgeny 672f04b833 Exercise real ETCP multipath against independent bandwidth and RTT profiles 2026-10-22 23:23:05 +02:00
evgeny f5cab5a14e Calibrate dummynet bandwidth and verify stalled channel lifecycle 2026-10-22 23:19:01 +02:00
evgeny f5f6ff50af Make dummynet bandwidth timing cumulative and add bounded DROP/HOLD modes 2026-10-22 23:18:11 +02:00
evgeny 62f4850968 Record successful full ETCP regression validation 2026-10-22 23:04:00 +02:00
evgeny 082d9d3960 Record STCP SIGPIPE regression and sanitizer validation 2026-10-22 23:02:42 +02:00
evgeny 05a5b6c2a7 Exercise STCP writes with default SIGPIPE disposition 2026-10-22 22:59:45 +02:00
evgeny fdcf92be04 Prevent SIGPIPE from terminating embedded STCP transports 2026-10-22 22:59:44 +02:00
evgeny 11aa760d47 Document ETCP window ownership and completed connection fixes 2026-10-22 22:57:06 +02:00
evgeny bc27352710 Remove stale references to the legacy connect installer 2026-10-22 22:53:55 +02:00
evgeny 0a00bd1c90 Clarify readiness contracts and stop on corrupt accounting 2026-10-22 22:53:30 +02:00
evgeny e2ee0d81dd Wait for both ETCP streams before starting Merkle tests 2026-10-22 22:52:14 +02:00
evgeny 6afc57569a Test zero-delay sender wakeups and failure injection 2026-10-22 22:50:38 +02:00
evgeny ccb3821e2f Schedule ordinary ETCP sends without timer latency 2026-10-22 22:50:04 +02:00
evgeny 29f9a97552 Use valid global options in REALITY config fixtures 2026-10-22 22:48:53 +02:00
evgeny f37ac2c637 Verify deferred teardown when the TX timer cannot start 2026-10-22 22:47:50 +02:00
evgeny 0ef0553598 Coalesce useful TX wakeups and defer failure teardown safely 2026-10-22 22:45:58 +02:00
evgeny f1c88ad375 Document congestion configuration and protect key diagnostics 2026-10-22 22:40:47 +02:00
evgeny 17c92e1745 Diagnose adopted transports and isolate traffic test commands 2026-10-22 22:39:43 +02:00
evgeny 34e346463d Cover initial UP, pacing and bidirectional tight windows 2026-10-22 22:38:20 +02:00
evgeny ef11d3b677 Preserve initial readiness while sender waits for a session 2026-10-22 22:36:53 +02:00
evgeny 2689931826 Inject allocation failures through the actual pool symbol 2026-10-22 22:34:07 +02:00
evgeny 133ea8d120 Wake sender after preparing each new fragment 2026-10-22 22:33:43 +02:00
evgeny 75ff7c4ce3 Exercise DATA windows, ACKs, retries and sender wakeups 2026-10-22 22:33:11 +02:00
evgeny 86a0f041c7 Validate congestion limits and guard window arithmetic 2026-10-22 22:31:12 +02:00
evgeny c1f07ec6ae Enforce per-link DATA windows without blocking ACKs 2026-10-22 22:29:31 +02:00
evgeny 50d5486a9f Define per-link accounting and deferred sender contracts 2026-10-22 22:26:51 +02:00
evgeny 036cf43918 Build and document direct connections through NCD 2026-10-22 22:25:52 +02:00
evgeny fc5b3911f3 Detach ETCP core from obsolete connect contexts 2026-10-22 22:25:39 +02:00
evgeny 008f499367 Remove obsolete parallel ETCP connection installer 2026-10-22 22:25:39 +02:00
evgeny 8169322d56 Move direct transport tests to NCD ownership 2026-10-22 22:24:45 +02:00
evgeny d84ab69465 Verify link selection mismatch with valid congestion limits 2026-10-22 21:32:34 +02:00
evgeny 91bb95a981 Record ETCP and STCP header audit findings 2026-10-22 21:30:28 +02:00
evgeny 03f4cd7939 Record validated media supernode replication implementation 2026-10-22 21:21:43 +02:00
evgeny 71d3b017e6 Clarify local media catalog seeding and channel cleanup contracts 2026-10-22 21:20:21 +02:00
evgeny 8230eb4de7 Test final-page update races and replication read failures 2026-10-22 21:16:59 +02:00
evgeny bc149497f3 Document media replication protocol and resource ownership 2026-10-22 21:15:55 +02:00
evgeny 0bcdb9930a Make media send queue policy explicit and document announcement errors 2026-10-22 21:14:42 +02:00
evgeny f2283eff6b Wait for actual retry requests independently of change hints 2026-10-22 21:12:58 +02:00
evgeny a011c57237 Cover replication retries, source reset and router backpressure 2026-10-22 21:10:44 +02:00
evgeny 9e2a1e5208 Handle automatic SQLite rollback and complete CHAT integration setup 2026-10-22 21:09:16 +02:00
evgeny d6d8ffc4a8 Require role subscription and initialize real chat test contexts 2026-10-22 21:07:18 +02:00
evgeny 14b01964b1 Initialize media test dependencies and await committed sync 2026-10-22 21:06:15 +02:00
evgeny 7fb9b43e9e Validate media role changes through signed CHAT membership 2026-10-22 21:02:57 +02:00
evgeny 63c0b9a655 Run media integration replication in its actual CHAT group 2026-10-22 21:01:24 +02:00
evgeny 00502bf4d4 Exercise real media replication pages, rollback and catalog lifecycle 2026-10-22 21:00:48 +02:00
evgeny d3bc77922b Initialize replication router queues before waiting for room 2026-10-22 20:55:25 +02:00
evgeny b4b11d6086 Implement atomic group-scoped media catalog replication 2026-10-22 20:54:37 +02:00
evgeny b47a82e3ac Define bounded receiver-driven media replication protocol 2026-10-22 20:45:14 +02:00
evgeny 04dc53672a Add regression for media availability group isolation 2026-10-22 20:43:51 +02:00
evgeny 964a938437 Record reproduced media replication failures and correct cursor audit 2026-10-22 20:11:06 +02:00
evgeny e8843d9499 Track remaining media supernode replication audit separately 2026-10-22 19:53:31 +02:00
evgeny ecba318dec Record validated media block ownership and relay fixes 2026-10-22 19:48:47 +02:00
evgeny 1aec478787 Drive relay generation wakeup through actual queue consumption 2026-10-22 19:46:30 +02:00
evgeny 8f97cf8070 Verify old relay readers survive a new download generation independently 2026-10-22 19:45:45 +02:00
evgeny bd0260040c Replace obsolete media relay description with current attempt and buffer contracts 2026-10-22 19:43:59 +02:00
evgeny 3b24e7e207 Describe media source lifetime, callback ownership and publication ordering 2026-10-22 19:40:38 +02:00
evgeny 993e22085a Scope libc fault-injection regression target to Linux 2026-10-22 19:39:34 +02:00
evgeny 00e0421e56 Update media wire-size assertions and cover fortified read fault injection 2026-10-22 19:38:56 +02:00
evgeny fabeb770a7 Verify media source admission, redirect bounds and transmit I/O failure cleanup 2026-10-22 19:37:28 +02:00
evgeny 47addfeffc Cover terminal media registration failures and relay admission lifetime 2026-10-22 19:35:45 +02:00
evgeny 02e1526115 Remove redundant embedded download entry and update service-stop request 2026-10-22 19:33:49 +02:00
evgeny c81cc8b9ca Document media attempt validation and reserve before tracking outgoing writes 2026-10-22 19:33:12 +02:00
evgeny 99389f4bd3 Bound relay readers independently and redirect only to matching block recipients 2026-10-22 19:32:15 +02:00
evgeny b09a020855 Exercise media block identity, I/O failure, reentrancy and relay backpressure 2026-10-22 19:30:59 +02:00
evgeny 090e7db598 Expose transport queue control in media regression fixtures 2026-10-22 19:27:37 +02:00
evgeny 5215163a37 Share authenticated media fixtures for adversarial delivery regressions 2026-10-22 19:25:24 +02:00
evgeny 1c1ab24a27 Test real truncated-block assembly and balance media admission ownership 2026-10-22 19:24:17 +02:00
evgeny 353752165c Align media integration inputs with routed block identity 2026-10-22 19:23:53 +02:00
evgeny b1a3d0c6cd Scope media retry responses to active block attempts 2026-10-22 19:17:36 +02:00
evgeny 16333b989d Replace permissive media download fixtures with verified block inputs 2026-10-22 19:16:23 +02:00
evgeny d2bf40e11a Use authenticated block metadata in media query fixtures 2026-10-22 19:15:17 +02:00
evgeny d0a401e166 Validate received blocks and publish complete verified media atomically 2026-10-22 19:14:12 +02:00
evgeny 4edf72ea14 Create independent block attempts and cancel channel downloads safely 2026-10-22 19:11:44 +02:00
evgeny 9b4974f02d Unify media source and relay transmission with owned buffers 2026-10-22 19:11:26 +02:00
evgeny f15d53947e Define media block attempts and shared byte-source ownership 2026-10-22 19:08:14 +02:00
evgeny 68129c0151 Retain media downloads across reentrant progress callbacks 2026-10-22 19:07:04 +02:00
evgeny a3a8b5b1be Add checked UTF-8 opening of existing block files for update 2026-10-22 19:03:16 +02:00
evgeny 55d2f437a0 Clarify ownership requirements for media completion callbacks 2026-10-22 18:57:48 +02:00
evgeny 108dfae82b Record reproduced media block receive and completion defects 2026-10-22 18:45:19 +02:00
evgeny 3a703d1093 Record final media QUERY full-suite validation 2026-10-22 18:31:13 +02:00
evgeny 433a091db5 Record verified media QUERY fixes and regression coverage 2026-10-22 18:31:13 +02:00
evgeny 21ebc832e1 Verify holder selection and log download resource transitions 2026-10-22 18:31:12 +02:00
evgeny a3f3b2fff2 Document media lookup lifetimes and remove obsolete shared state 2026-10-22 18:31:12 +02:00
evgeny 093e537fed Check live QUERY deadlines and deferred CM failover 2026-10-22 18:31:12 +02:00
evgeny 6a14be05f5 Verify second-block SQL failure and propagate local download errors 2026-10-22 18:31:12 +02:00
evgeny fd6155aa4a Exercise media QUERY bounds, paging, failures and peer reuse 2026-10-22 18:31:12 +02:00
evgeny f47e80571d Assert media QUERY results and preserve transport ownership in tests 2026-10-22 18:31:12 +02:00
evgeny e02c9f7727 Cancel delayed CM UP before deferring failover 2026-10-22 18:31:12 +02:00
evgeny 05ace58589 Implement bounded media lookup with independent deadlines 2026-10-22 18:31:12 +02:00
evgeny 92c514ce18 Describe bounded media lookup and peer slot state 2026-10-22 18:31:12 +02:00
evgeny 6267973d62 Define bounded media QUERY batches and complete responses 2026-10-22 18:31:12 +02:00
evgeny cecbc682a0 Move stream start and stop into the title bar 2026-10-22 18:13:43 +02:00
evgeny 07cd150b47 Merge shared Android media audio routing and microphone fixes 2026-10-22 18:04:49 +02:00
evgeny 6a1c289ab2 Keep Bluetooth SCO active for radio reception without capture 2026-10-22 17:59:11 +02:00
evgeny bb380441a3 Fix shared microphone crackle from timestamp jitter 2026-10-22 16:53:25 +02:00
evgeny 94d1e245a5 Record reproduced media QUERY bounds and response truncation defects 2026-10-22 16:47:28 +02:00
evgeny 02265644ce Record verified media index fixes and remaining QUERY audit 2026-10-22 15:48:15 +02:00
evgeny c9a3a1334b Restore shared media playback to speaker after headset loss 2026-10-22 15:24:13 +02:00
evgeny 0835c3d3cc Describe media preparation records and deferred channel cleanup 2026-10-22 15:15:52 +02:00
evgeny 34959a9657 Share atomic media commits between downloads and backfill 2026-10-22 15:14:25 +02:00
evgeny 90fab54a4a Use one atomic commit API for authored and downloaded files 2026-10-22 15:14:22 +02:00
evgeny 439011ce60 Use real media schema and validate duplicate download ownership 2026-10-22 15:13:14 +02:00
evgeny ec70908733 Check relative path limits and binding failures without truncation 2026-10-22 15:11:23 +02:00
evgeny 2b63c680b2 Verify shared Android audio ownership on a physical device 2026-10-22 15:07:26 +02:00
evgeny 44a3b33c3a Expose shared audio devices in stream viewing and document them 2026-10-22 15:07:26 +02:00
evgeny 5d10a16baa Use shared audio controls in radio and stream publication 2026-10-22 15:07:25 +02:00
evgeny 33ccba1f3f Add a common audio device selector for radio and streams 2026-10-22 15:07:25 +02:00
evgeny bebc579180 Share media playback devices and await cleanup before calls 2026-10-22 15:07:25 +02:00
evgeny a893f6b4f2 Route radio and stream capture through the shared controller 2026-10-22 15:07:25 +02:00
evgeny ecada765a2 Own Android media devices in one shared audio controller 2026-10-22 15:07:25 +02:00
evgeny b72e51accb Keep shared microphone PCM continuous across device changes 2026-10-22 15:07:25 +02:00
evgeny f123a1bdeb Document preparation ownership and log exact media index failures 2026-10-22 15:06:00 +02:00
evgeny 3bb8f76b7d Reuse the database media base when restarting chat 2026-10-22 15:03:41 +02:00
evgeny 6b54b63986 Define download start ownership and avoid access after completion 2026-10-22 15:00:41 +02:00
evgeny c08c24140c Test reentrant media events and cleanup after hash failure 2026-10-22 14:58:15 +02:00
evgeny d4efdba44e Retain failed download destinations for later cleanup 2026-10-22 14:56:07 +02:00
evgeny 41057ee9d5 Keep media task ownership through synchronous submission events 2026-10-22 14:54:18 +02:00
evgeny 87724433b1 Exercise media index failure recovery and pending channel deletion 2026-10-22 14:52:27 +02:00
evgeny 9117813c4d Preserve indexed and unfinished files during orphan cleanup 2026-10-22 14:49:11 +02:00
evgeny 15fafc42c9 Use validated paths for media submission and backfill 2026-10-22 14:46:49 +02:00
evgeny 16a5e628fd Report incomplete media cleanup separately from channel deletion 2026-10-22 14:46:15 +02:00
evgeny 32d80110cb Drain outgoing media tasks before deleting their channel 2026-10-22 14:45:29 +02:00
evgeny b3a223521c Track unfinished media destinations for channel cleanup 2026-10-22 14:44:24 +02:00
evgeny 8cba8ef3e6 Propagate media schema and downloaded registration failures 2026-10-22 14:42:44 +02:00
evgeny 113f0259ea Preserve retryable media cleanup and validate served paths 2026-10-22 14:41:45 +02:00
evgeny 889dfc411a Validate media paths and register complete files atomically 2026-10-22 14:40:30 +02:00
evgeny ad62eee10c Record media index plan constraints and late completion race 2026-10-22 14:35:56 +02:00
evgeny d68ab90f23 Record reproduced media index path and database failures 2026-10-22 14:26:49 +02:00
evgeny ad0505bd31 Record verified media primitive fixes and remaining location audit 2026-10-22 13:40:39 +02:00
evgeny 64519c41a7 Restrict fortified stdio test hook to glibc 2026-10-22 13:35:25 +02:00
evgeny 0d55c98add Inject fortified fread failures in sanitizer regressions 2026-10-22 13:34:54 +02:00
evgeny 5549a69b1c Cover failed file stream creation and document media cancellation effects 2026-10-22 13:31:13 +02:00
evgeny 1ceb0beb8d Check generated media identifiers in integration fixtures 2026-10-22 10:28:32 +02:00
evgeny 386ea85fea Log precise copy and media indexing failure operations 2026-10-22 10:27:42 +02:00
evgeny 607de2a074 Bound recursive walker stack and report exact copy failure phases 2026-10-22 10:26:56 +02:00
evgeny bf3834b95d Use existing Linux condition for media fault injection 2026-10-22 10:24:16 +02:00
evgeny 3a00caef50 Add fault injection regressions for media files and worker ownership 2026-10-22 10:23:56 +02:00
evgeny 5505afd1f3 Keep thumbnail path bounds and check UUID status in stress test 2026-10-22 10:21:41 +02:00
evgeny 0e418c7fa9 Propagate RNG errors and preserve 64-bit media sizes through callers 2026-10-22 10:20:02 +02:00
evgeny f399ded5ba Preserve files on copy errors and check media primitive failures 2026-10-22 10:18:23 +02:00
evgeny 85d96fc857 Share UTF-8 Windows path conversion with file workers 2026-10-22 10:18:22 +02:00
evgeny 3100985e4c Specify media worker ownership errors and primitive contracts 2026-10-22 10:16:57 +02:00
evgeny 6355bd1d73 Use shared file publication in video processing 2026-10-22 10:15:39 +02:00
evgeny 3ed4b73ebf Extract checked file identity and temporary publication primitives 2026-10-22 10:15:13 +02:00
evgeny 41cc04bdab Record media async primitive and contract audit findings 2026-10-22 10:07:20 +02:00
evgeny d8621c7f27 Record bootstrap regression coverage and successful validation 2026-10-22 10:02:57 +02:00
evgeny 3c49c2a4aa Initialize callback epoch for pending transport shutdown test 2026-10-22 09:59:13 +02:00
evgeny 633f1d3044 Schedule pending deadline resets from probe creation 2026-10-22 09:47:23 +02:00
evgeny 34602e3e7e Separate cold bootstrap from controlled pending probe reset 2026-10-22 09:45:48 +02:00
evgeny 330e346a7b Record bootstrap test race in TCP session initialization 2026-10-22 09:40:08 +02:00
evgeny 6d39800351 Record completed video fixes and validation coverage 2026-10-22 00:20:40 +02:00
evgeny df2f90d1f5 Allow MP4 millisecond edit rounding in audio tail regression 2026-10-22 00:17:55 +02:00
evgeny 1d6596dc61 Check resampler tail duration and actual 1080p to 720p conversion 2026-10-22 00:17:16 +02:00
evgeny 0653121fc4 Document video publication and cancellation in service lifecycle 2026-10-22 00:15:54 +02:00
evgeny 3f603534e9 Log exact video failure phases and check buffered file seeks 2026-10-22 00:11:02 +02:00
evgeny 631cc924cf Measure AAC signal onset above verified codec pre-echo level 2026-10-22 00:09:49 +02:00
evgeny 9f1825dbf8 Verify decoded video content, AAC signal timing and FFmpeg-free stubs 2026-10-22 00:08:22 +02:00
evgeny 9163025912 Test video I/O failures, audio timing, cancellation and concurrent ownership 2026-10-22 00:05:30 +02:00
evgeny 5929bb4028 Normalize unspecified PCM channel layouts before AAC setup 2026-10-22 00:03:12 +02:00
evgeny fe313cf8f8 Match FFmpeg AVIO and codec capability APIs across supported versions 2026-10-22 00:02:29 +02:00
evgeny cb9102f381 Prepare video through checked temporary output and complete codec drains 2026-10-22 00:01:47 +02:00
evgeny 9a8bc9b461 Select video regression branch from configured FFmpeg support 2026-10-21 23:59:06 +02:00
evgeny c4bf292085 Specify video publication, remux and completion contracts 2026-10-21 23:58:55 +02:00
evgeny 8d9704036f Add real video fixtures and file preservation regressions 2026-10-21 23:56:55 +02:00
evgeny fb01a9bb2e Record video audit failures and missing API contracts 2026-10-21 23:28:53 +02:00
evgeny 1d7e12c562 Avoid random HEX substring collisions in config updater assertions 2026-10-21 23:12:12 +02:00
evgeny 7c473dd500 Reject signed NAT forward IDs hidden by leading whitespace 2026-10-21 23:11:32 +02:00
evgeny 1ccb266971 Describe NAT transport backpressure, ownership and mapping holds 2026-10-21 23:08:31 +02:00
evgeny 7de54ab3e4 Document NAT hash, expiry, mapping holds and bounded transport ownership 2026-10-21 22:58:28 +02:00
evgeny 2c10272e15 Add repeatable NAT benchmarks for table size, payload size and full-pool misses 2026-10-21 22:55:24 +02:00
evgeny 55cc721c9b Test held NAT mappings under TUN congestion and reject invalid provider input 2026-10-21 22:54:24 +02:00
evgeny dea768c21a Hold outgoing NAT mappings through asynchronous TUN delivery 2026-10-21 22:52:19 +02:00
evgeny 824c663ebd Protect NAT mappings referenced by pending TUN transmissions 2026-10-21 22:51:25 +02:00
evgeny 80b481827c Verify bounded global NAT pressure and retained destination across mapping reuse 2026-10-21 22:46:09 +02:00
evgeny 8360e48622 Exercise NAT backpressure, mapping reuse, copy ownership and shutdown 2026-10-21 22:44:46 +02:00
evgeny 41a68b86bc Close NAT channels after cancelling producers and quiet transient TUN backpressure 2026-10-21 22:43:54 +02:00
evgeny cfc0051088 Test NAT boundary validation, hash collisions, expiry and allocation failures 2026-10-21 22:34:06 +02:00
evgeny cecc5d7d1f Validate provider replies before client TUN delivery 2026-10-21 22:32:29 +02:00
evgeny 5e2e6c15cf Preserve NAT index invariants across transport and reuse tests 2026-10-21 22:31:55 +02:00
evgeny d0c83cfff1 Add bounded NAT backpressure and forward TUN packets without copying 2026-10-21 22:30:01 +02:00
evgeny 9d6d6454b0 Reject malformed NAT configuration and use mapping lifecycle APIs in tests 2026-10-21 22:27:16 +02:00
evgeny 94fa3207f6 Index NAT mappings by hash and maintain free ports in constant time 2026-10-21 22:24:17 +02:00
evgeny 5a837f903c Validate NAT packet boundaries and distinguish skipped packets 2026-10-21 22:21:37 +02:00
evgeny 00b4b92749 Align NAT documentation with node ownership and router framing 2026-10-21 22:01:08 +02:00
evgeny 19a5870d5f Document NAT ownership, packet formats and API contracts 2026-10-21 22:00:06 +02:00
evgeny bfa23afae9 Restrict NAT delivery to its group and configured provider 2026-10-21 21:58:10 +02:00
evgeny 909005c1a4 Complete standalone NAT forward configuration fixture 2026-10-21 21:57:23 +02:00
evgeny ddc163a2b7 Cover NAT forward owners, full port range and invalid rules 2026-10-21 21:56:20 +02:00
evgeny ca471d777e Route static NAT forwards to explicit owners on all ports 2026-10-21 21:55:13 +02:00
evgeny 2c4f107e63 Require a target node for NAT port forward configuration 2026-10-21 21:54:41 +02:00
evgeny 3d0c4ff0a5 Isolate NAT mappings by source node identity 2026-10-21 21:53:17 +02:00
evgeny a9820bea42 Preserve IPv4 UDP checksum encoding through NAT 2026-10-21 21:51:49 +02:00
evgeny 52a73ea6a7 Correct incremental NAT checksum arithmetic 2026-10-21 21:50:59 +02:00
evgeny 6e9bacd843 Fix NAT flow lookup at the maximum port boundary 2026-10-21 21:48:28 +02:00
evgeny fbbd62acc4 Fix NAT packet ownership after router send failures 2026-10-21 21:43:00 +02:00
evgeny ef673eb3b7 Merge branch 'master' of http://git.syqiq.tech/jeka/utun2 2026-10-21 21:22:49 +02:00
evgeny 05b0927619 Fix TUN packet ownership, lifecycle and backend consistency
Unify TUN initialization and raw-IP writes, preserve socketpair directions, and bound reader delivery with queue backpressure. Make deferred packet ownership safe across close and uasync teardown, and apply per-interface MTU limits to lwIP MSS.

Validation: full check.sh passed 172 tests, 9 proxy integration checks, burst and load. TUN ASAN/UBSAN regressions passed 30 cases; TCP exchange passed with asymmetric and minimum MTUs. Sanitized NAT integration exposed an existing unaligned uint16_t read in eim_nat.c; that separate defect remains unchanged. Native Windows and FreeBSD validation remains outstanding.
2026-10-21 21:19:42 +02:00
evgeny e3f2feb480 Merge branch 'master' of http://git.syqiq.tech/jeka/utun2 2026-10-21 20:26:26 +02:00
evgeny a65b0c8786 Fix queue extraction in NTP regression cleanup 2026-10-21 20:18:49 +02:00
evgeny 6c84e680af Improve NTP failure diagnostics and source transition coverage 2026-10-21 20:18:17 +02:00
evgeny d6eed66926 Document NTP contracts and verify corrected wall time 2026-10-21 20:16:38 +02:00
evgeny 88bf65e07d Cover NTP correction, retries, faults and peer lifecycle 2026-10-21 20:15:17 +02:00
evgeny 13c1831baa Fix peer time cache ownership and callback teardown 2026-10-21 20:12:04 +02:00
evgeny 5609d57947 Fix NTP offset and bound asynchronous polling cycles 2026-10-21 20:11:04 +02:00
evgeny 488be0edd1 Record recurring attachment test timeout during control validation 2026-10-21 20:06:51 +02:00
evgeny e620567804 Finalize control server regression fixture cleanup 2026-10-21 20:04:30 +02:00
evgeny fdf2912427 Cover control server regressions and correct API documentation 2026-10-21 20:04:30 +02:00
evgeny 51922cd18e Fix control server buffer bounds and client resource ownership 2026-10-21 20:04:19 +02:00
evgeny 79946bb07c Keep firewall reload regression on the selective path 2026-10-21 20:00:28 +02:00
evgeny 41c32cf063 Set router packet length in firewall integration fixture 2026-10-21 19:59:34 +02:00
evgeny 4f627bddff Align firewall examples with symmetric destination filtering 2026-10-21 19:53:50 +02:00
evgeny 2a2a0ee267 Cover firewall parsing, bidirectional TUN filtering and reload failures 2026-10-21 19:52:51 +02:00
evgeny 5de0eb5115 Filter both directions of the main TUN and remove obsolete firewall guide 2026-10-21 19:49:32 +02:00
evgeny cf6c3c99e8 Reject invalid firewall configuration and preserve policy on reload failure 2026-10-21 19:48:54 +02:00
evgeny c3a7f77b54 Make firewall rule replacement atomic and validate IPv4 packets 2026-10-21 19:48:19 +02:00
evgeny 4f5651906c Report precise INI read failures and identity repair reasons 2026-10-21 19:09:03 +02:00
evgeny 2df21efc22 Bound edited INI growth and cover effective duplicate values 2026-10-21 19:06:59 +02:00
evgeny b0f5055c31 Describe shared INI parsing and distribute its internal header 2026-10-21 19:04:54 +02:00
evgeny 80eb331d6a Document config identity and publication guarantees 2026-10-21 19:04:53 +02:00
evgeny bb87a0845b Verify config updates preserve files and allocations under OOM 2026-10-21 19:03:39 +02:00
evgeny f7c8b95af6 Bound INI buffers to allocator limits and log allocation errors 2026-10-21 19:03:38 +02:00
evgeny 33130c1d74 Test identity preservation and failed config publication 2026-10-21 19:02:20 +02:00
evgeny f61c0c3e38 Preserve config identity and publish verified updates safely 2026-10-21 19:00:06 +02:00
evgeny 2410c39b71 Share INI parsing and validate complete configuration values 2026-10-21 18:54:43 +02:00
evgeny 5a57b59b50 Test emulator forwarding and partial initialization cleanup 2026-10-21 18:14:03 +02:00
evgeny ff3eb02bff Update emulator uasync lifecycle and document forwarding rules 2026-10-21 18:12:06 +02:00
evgeny 56d5fcd75a Test broadcast UUID lifetime and callback registration contracts 2026-10-21 18:10:43 +02:00
evgeny 484b620016 Preserve broadcast UUIDs until TTL and document callback contracts 2026-10-21 18:08:50 +02:00
evgeny 16f81575ce Consolidate TCP queue and resource lifecycle documentation in header 2026-10-21 17:59:08 +02:00
evgeny 10b039686c Consolidate queue usage and backpressure documentation in header 2026-10-21 17:57:34 +02:00
evgeny 194bf7bb45 Consolidate debug configuration documentation in header 2026-10-21 17:56:13 +02:00
evgeny a288afd8ce Consolidate socket lifecycle and wrapper documentation in header 2026-10-21 17:55:27 +02:00
evgeny 4f3f6d3e60 Consolidate platform adapter documentation in header 2026-10-21 17:55:27 +02:00
evgeny 1c4010ebed Consolidate serialization format and ownership documentation in header 2026-10-21 17:54:20 +02:00
evgeny ace9cb70d0 Consolidate timeout heap ownership and complexity documentation 2026-10-21 17:54:20 +02:00
evgeny af3c7a4217 Consolidate pool lifecycle and diagnostics documentation in header 2026-10-21 17:53:00 +02:00
evgeny e403a2f689 Consolidate memory ownership and allocation documentation in header 2026-10-21 17:53:00 +02:00
evgeny 3944459c29 Consolidate sliding minimum documentation in header 2026-10-21 17:52:02 +02:00
evgeny 48383ef95e Consolidate outgoing IP documentation in header 2026-10-21 17:52:02 +02:00
evgeny f020a6f62b Clarify CPU indicator polling and pending responses 2026-10-21 17:48:35 +02:00
evgeny d23a96a08e Document uasync scheduling, ownership and lifecycle contracts 2026-10-21 17:40:20 +02:00
evgeny dffcd19d21 Clarify queue callback scheduling and ownership contracts 2026-10-21 17:27:04 +02:00
evgeny 9079ab5c24 Fix debug timestamps, default output and log reopening 2026-10-21 17:10:44 +02:00
evgeny 7e39f16e0f Fix allocation size overflow, zero-size OOM and debug category parsing 2026-10-21 17:07:11 +02:00
evgeny 00819654cf Document POSIX descriptor zero exception in socket close 2026-10-21 16:57:44 +02:00
evgeny 346c06f31d Record TCP lifecycle validation and IPv6 selection caveat 2026-10-21 16:52:11 +02:00
evgeny ba7c2873c5 Initialize proxy timers before notifying connected owners 2026-10-21 16:48:25 +02:00
evgeny 0e15234ce1 Clarify TCP lifecycle and platform address selection contracts 2026-10-21 16:46:45 +02:00
evgeny e5fafacf99 Cover TCP timer replacement failures and pending FIN cleanup 2026-10-21 16:45:53 +02:00
evgeny d467d3aa8f Fix TCP connect timer ownership and pending FIN state 2026-10-21 16:44:19 +02:00
evgeny d5e489598d Clarify DNS lifetimes and parser and video plane limits 2026-10-21 16:33:16 +02:00
evgeny 1ff02188ca Fix Silero model cleanup and document runtime configuration 2026-10-21 16:29:44 +02:00
evgeny f31b34d0f0 Clarify serialization limits, cleanup and error codes 2026-10-21 16:18:51 +02:00
evgeny ecd5b9fc9d Clarify memory pool counters and strbuf error documentation 2026-10-21 16:14:48 +02:00
evgeny aca6364378 Correct call routing, media channels and API documentation 2026-10-21 16:11:36 +02:00
evgeny 88e930cc0a docs: correct member file protocol and media download contracts 2026-10-21 16:01:30 +02:00
evgeny be235e01aa docs: correct DM payload, history and transfer cancellation contracts 2026-10-21 15:53:28 +02:00
evgeny 66f004f725 docs: describe actual DM identity derivation and crypto modules 2026-10-21 15:52:45 +02:00
evgeny cbe22899c2 docs: align chat invite and member sync descriptions with code 2026-10-21 15:47:56 +02:00
evgeny df36e504fc docs: remove completed and obsolete project tasks 2026-10-21 15:47:30 +02:00
evgeny 7c814f5cb9 Route desktop codec diagnostics through the shared log
Quiet capability probes, preserve runtime errors, and register the desktop portal application. Add logging and desktop launcher regression coverage.
2026-10-21 15:45:40 +02:00
evgeny 9ba78c084b docs: correct topology API and router timeout descriptions 2026-10-21 15:40:56 +02:00
evgeny d644126cac docs: align ETCP handshake and epoch descriptions with code 2026-10-21 15:37:01 +02:00
evgeny f59f64ea48 docs: remove reviewed header backup files 2026-10-21 15:22:42 +02:00
evgeny f5f106c682 docs: clarify header APIs and preserve originals in .h1 2026-10-21 15:21:27 +02:00
evgeny abf342534f Encrypt P2P call media and validate service IDs
Require signed and encrypted call audio/video using static identity keys. Fix attachment service ID collisions and media download handle lifetime; isolate LAN tests and audit the shared service registry.
2026-10-21 15:06:27 +02:00
evgeny 8e46cf9254 docs: refresh project file descriptions in AGENTS.md 2026-10-21 14:54:06 +02:00
evgeny 15b037aef7 Scroll to latest chat messages with End key 2026-10-21 13:31:50 +02:00
evgeny 69e534304b Darken desktop chat backgrounds and lighter panels 2026-10-21 13:26:05 +02:00
evgeny 4dbc543cd3 Increase contrast of chat scrollbar thumb only 2026-10-21 13:21:30 +02:00
evgeny 4f6808156f Add chat history keyboard scrolling and remove unused PTT setting 2026-10-21 13:05:31 +02:00
evgeny 5870efdf04 Default stream redundancy to zero on Android and desktop 2026-10-21 12:42:23 +02:00
evgeny 34db81c5cf Show green stream watch buttons only for active streams 2026-10-21 12:30:08 +02:00
evgeny a31436e421 Expand chat README and add stream image 2026-10-21 02:31:00 +02:00
evgeny 550f384c69 Tidy stream video worker source formatting 2026-10-21 01:40:04 +02:00
evgeny a6b909b70e Add device regression for independent stream audio and video workers 2026-10-21 01:38:50 +02:00
evgeny fcb0768052 Protect SoundTouch lookahead when catching up live stream audio 2026-10-21 01:38:50 +02:00
evgeny 5d9da0c6b1 Preserve exact source EOF across tempo changes and expose DSP lookahead 2026-10-21 01:38:50 +02:00
evgeny e8ee608ba9 Document independent audio preparation and live stream catchup 2026-10-21 01:28:20 +02:00
evgeny 4e5530aca0 Isolate Android live video decoding from audio preparation 2026-10-21 01:23:35 +02:00
evgeny 4fa3b18cc2 Feed Android stream mixer from prepared PCM without holding DSP lock 2026-10-21 01:18:56 +02:00
evgeny 50cf61e9cd Mix partial stream PCM without copying stale buffer tails 2026-10-21 01:11:35 +02:00
evgeny c6ed01110e Exercise audio continuity during video stalls and generation changes 2026-10-21 01:03:19 +02:00
evgeny 1764603977 Isolate live video decoding from audio preparation in ChatGUI 2026-10-21 01:00:31 +02:00
evgeny 6c619d5f2a Recover stream playback stalls with bounded proportional catchup 2026-10-21 00:56:47 +02:00
evgeny 58c0fc6bba Separate stream audio DSP from bounded PCM consumption 2026-10-21 00:54:30 +02:00
evgeny 32e648ea9e Release audio holds at video EOF and stop Android batches promptly 2026-10-21 00:15:34 +02:00
evgeny b2efab9806 Document per-track completion and Android batch wakeups 2026-10-21 00:13:01 +02:00
evgeny a342316e7a Verify delayed decoders with unequal audio and video tails 2026-10-21 00:13:01 +02:00
evgeny c887780022 Consume Android batches with independent track completion 2026-10-21 00:13:01 +02:00
evgeny a538f99bf0 Expose viewer wake signals and complete audio independently on Android 2026-10-21 00:13:01 +02:00
evgeny 9fb4823df0 Batch Android viewer records and wake readers on data or stop 2026-10-21 00:13:01 +02:00
evgeny a2fb562a22 Drain desktop tracks independently and cancel obsolete media on reset 2026-10-20 23:54:55 +02:00
evgeny 852c05590d Expose track completion to desktop and retain the longer media tail 2026-10-20 23:53:58 +02:00
evgeny a54031081b Deliver independent track ends and invalidate batches on gaps 2026-10-20 23:53:23 +02:00
evgeny 23c4e4efbc Diagnose delayed decoder completion with buffered audio 2026-10-20 23:46:59 +02:00
evgeny 5bc0c1ece4 Document bounded desktop stream batches and worker wakeups 2026-10-20 23:42:28 +02:00
evgeny fbab6eae5d Anchor batch regression at a single initial keyframe 2026-10-20 23:41:12 +02:00
evgeny 0d9c728efe Test bounded batches, track budgets, notifications and FIN 2026-10-20 23:34:57 +02:00
evgeny 039c4a9ed4 Consume desktop media batches with per-track backpressure and wakeups 2026-10-20 23:31:50 +02:00
evgeny 179f248db8 Deliver bounded media batches to desktop consumers 2026-10-20 23:30:10 +02:00
evgeny 589cdcc81f Add bounded viewer batches and readiness notifications 2026-10-20 23:29:23 +02:00
evgeny 6997a82af1 Enable publisher timing logs in diagnostic test runs 2026-10-20 22:32:33 +02:00
evgeny c6f7d50838 Measure stream viewer request and worker delays 2026-10-20 22:30:04 +02:00
evgeny 7ce60ca0f2 Trace desktop stream stalls across playback and preview UI 2026-10-20 22:20:31 +02:00
evgeny b1112981fa Timestamp Android decoder output at the observation point 2026-10-20 21:44:10 +02:00
evgeny a4419b4c89 Validate Android surface timestamps in stream diagnostics 2026-10-20 21:40:20 +02:00
evgeny 1b036e4ea9 Trace stream startup decoding and presentation delays 2026-10-20 21:34:24 +02:00
evgeny f38aa3b08d Log stream startup key age and buffer handoff delays 2026-10-20 21:27:51 +02:00
evgeny c92d36354a List calling radio and multiview features in README 2026-10-20 21:14:57 +02:00
evgeny a0d49c4bbd Add Vibechat overview and group access notes 2026-10-20 21:14:16 +02:00
evgeny e3dbeee6a8 Document desktop stream tile behavior and verification 2026-10-20 20:47:55 +02:00
evgeny 8673673fcb Handle stream tile coordinates with Qt 5 and Qt 6 APIs 2026-10-20 20:46:08 +02:00
evgeny 03e8caf849 Isolate publisher return test and use native widget input 2026-10-20 20:44:42 +02:00
evgeny 2781208361 Exercise native preview clicks and publisher controls escape 2026-10-20 20:42:23 +02:00
evgeny 64064c2cbf Cover desktop local preview and multiview navigation 2026-10-20 20:39:24 +02:00
evgeny e5510e9f62 Connect desktop multiview to publisher preview and controls 2026-10-20 20:37:13 +02:00
evgeny 44db42180e Notify caller when desktop stream controls close 2026-10-20 20:33:23 +02:00
evgeny 6dab5db56a Show silent local preview and focus remote desktop stream tiles 2026-10-20 20:32:52 +02:00
evgeny f1cccb1c92 Document stream controls navigation and Samsung verification 2026-10-20 20:19:28 +02:00
evgeny b26538c48f Keep replacement camera preview active during tile disposal 2026-10-20 20:15:08 +02:00
evgeny 39bca58515 Open Android stream controls from own multiview tile 2026-10-20 20:11:49 +02:00
evgeny ca7e169811 Keep Android publisher screen focused on capture controls 2026-10-20 20:10:36 +02:00
evgeny fe9c3c74bd Record global stream shortcut verification on Samsung 2026-10-20 20:02:25 +02:00
evgeny d7053a2366 Open active stream controls from any Android header 2026-10-20 19:58:55 +02:00
evgeny 59858a726a Share active stream button across Android group and home headers 2026-10-20 19:56:50 +02:00
evgeny c4da659dcb Keep publisher shortcut available in every desktop group 2026-10-20 19:55:11 +02:00
evgeny 5b6ed6ee78 Exclude own stream from remote stream selectors 2026-10-20 19:52:22 +02:00
evgeny 7e95c3043b Add own live stream shortcut to Android chat header 2026-10-20 19:49:27 +02:00
evgeny 352f5b3f94 Show active publisher shortcut in desktop channel header 2026-10-20 19:46:32 +02:00
evgeny 6c74749dd4 Document direct local multiview preview and Samsung verification 2026-10-20 19:27:38 +02:00
evgeny 5c1619420a Show own Android multiview source through silent camera preview 2026-10-20 19:17:59 +02:00
evgeny c4b2052b27 Label multiview tap targets and record Samsung fullscreen verification 2026-10-20 19:06:14 +02:00
evgeny b46f13920c Expand Android multiview tiles on tap without restarting playback 2026-10-20 19:00:40 +02:00
evgeny d7d018d591 Record Samsung multiview fix verification on live streams 2026-10-20 18:51:32 +02:00
evgeny ba26b14f16 Include decoder frame state in Android stream failure logs 2026-10-20 18:46:57 +02:00
evgeny ea5d1fcd45 Consume repeated viewer demand when delivering its reply 2026-10-20 18:45:49 +02:00
evgeny 68fb937063 Record reproduced multiview lifecycle defects and audio test flake 2026-10-20 17:44:24 +02:00
evgeny 03f7d4341d Document multiview ownership, independent clocks and validation 2026-10-20 17:14:08 +02:00
evgeny b3ebc09b9b Wait for shared Android stream output release before starting calls 2026-10-20 17:13:59 +02:00
evgeny c98fdc6297 Reject duplicate Android catalogue entries before updating grid 2026-10-20 17:12:21 +02:00
evgeny b3116173ca Verify shared stream mixing and avoid retrying terminal closures 2026-10-20 17:12:16 +02:00
evgeny 4a44149c00 Translate Android multiview controls and empty state 2026-10-20 17:07:37 +02:00
evgeny 5f0325e817 Expose watch-all streams from Android chat and preserve catalogue on transient errors 2026-10-20 17:07:31 +02:00
evgeny 9e5b2ad435 Add adaptive Android grid with automatic source reconciliation 2026-10-20 17:07:19 +02:00
evgeny 14b152bb5a Keep Android stream surfaces alive until decoder release and fit grid geometry 2026-10-20 17:07:12 +02:00
evgeny 75806249bd Run Android players concurrently and fall back between decoders 2026-10-20 16:58:41 +02:00
evgeny 4e386fc8e3 Mix Android streams on one AudioTrack with independent clocks 2026-10-20 16:58:32 +02:00
evgeny 57d28376b6 Use independent shared viewers in the Android native bridge 2026-10-20 16:58:25 +02:00
evgeny c8bcf41f4a Translate desktop multiview controls and catalogue states 2026-10-20 16:39:59 +02:00
evgeny fd05ec47ab Connect the all-streams window to chat actions and service lifetime 2026-10-20 16:39:50 +02:00
evgeny bad9ee3a0c Add an automatically updated desktop grid of active streams 2026-10-20 16:39:43 +02:00
evgeny 05deb15c91 Embed stream players and verify grid and concurrent playback 2026-10-20 16:39:34 +02:00
evgeny 1b49c4023b Offer all active streams and share catalogue parsing in the GUI 2026-10-20 16:29:17 +02:00
evgeny 103e0b6865 Keep per-stream playback clocks on the shared audio output 2026-10-20 16:29:06 +02:00
evgeny a0b6dd7ef5 Mix live stream PCM into the shared desktop audio output 2026-10-20 16:27:24 +02:00
evgeny 94697c1af0 Preserve desktop catalogue watches across chat selection 2026-10-20 16:27:17 +02:00
evgeny 4fa9d474ab Track live stream catalogues independently for each owner 2026-10-20 16:27:10 +02:00
evgeny 39446b6e2c Use independent viewer sessions in the desktop stream bridge 2026-10-20 16:13:14 +02:00
evgeny 0bc3b62f46 Add independent live stream viewer sessions in the shared core 2026-10-20 15:53:44 +02:00
evgeny 9c7b49e91f Add call status sharing and automatic stream orientation 2026-10-20 15:49:32 +02:00
evgeny f87cda8b13 Record multicast validation and stabilize report timing test 2026-10-20 15:13:59 +02:00
evgeny b250e8a44d Cover multicast report backpressure, chunking and timer cleanup 2026-10-20 15:08:59 +02:00
evgeny 961c3447d0 Create multicast report channel before checking backpressure 2026-10-20 15:05:16 +02:00
evgeny e59bc9857b Test multicast routing, overload and descriptor lifetime 2026-10-20 15:01:27 +02:00
evgeny 4a1a65c3ba Implement multicast descriptors and receiver subscriptions 2026-10-20 14:53:23 +02:00
evgeny e416d69212 Define descriptor-based multicast API and usage contract 2026-10-20 14:44:23 +02:00
evgeny 8d5459f8fa Connect multicast queues to topology updates and builds 2026-10-20 14:34:55 +02:00
evgeny 5aa28a11d6 Register multicast protocol and core lifecycle 2026-10-20 14:34:30 +02:00
evgeny b02ad6f4cf Add multicast branching queues and bounded loss reports 2026-10-20 14:33:40 +02:00
evgeny de6712de91 Record stream settings validation under an isolated display 2026-10-20 02:30:11 +02:00
evgeny 1e49d2554c Limit desktop and Android stream bitrate menus to 12 Mbit/s 2026-10-20 02:29:46 +02:00
evgeny d2f0828917 Bound restored manual stream bitrate choices to 12 Mbit/s 2026-10-20 02:29:00 +02:00
evgeny 390747343b Document background codec preparation and complete probe validation 2026-10-20 01:28:59 +02:00
evgeny 475bec52ca Distinguish encoder KEY checks from receiver capability probes in tests 2026-10-20 01:27:03 +02:00
evgeny 4db89eeb32 Check cancelled codec preparation cannot publish partial capabilities 2026-10-20 01:25:24 +02:00
evgeny 36e8ab228c Verify startup codec preparation keeps GUI responsive and reuses probes 2026-10-20 01:23:21 +02:00
evgeny 91478e2593 Warm Android video codecs in the background and accelerate probe tails 2026-10-20 01:22:52 +02:00
evgeny e3b74de7ef Prepare and cache desktop video codecs once at application startup 2026-10-20 01:10:44 +02:00
evgeny 0cd1955606 Keep all eight decoder probe frames while accelerating the tail 2026-10-20 01:10:29 +02:00
evgeny 8ad5c09474 Finish decoder capability probes after first paced output and drain 2026-10-20 01:07:31 +02:00
evgeny 7ae204ba4f Document Android startup checks and remaining Linux probe delay 2026-10-20 01:05:53 +02:00
evgeny a4b9367344 Trace first video frame and verify capability upgrades preserve RX 2026-10-20 00:58:37 +02:00
evgeny 74313498f2 Cache verified video codecs and publish receive capabilities early 2026-10-20 00:56:53 +02:00
evgeny d71d0dc4af Check fullscreen video fitting without cropping or rotating thumbnails 2026-10-20 00:54:29 +02:00
evgeny 8b438f26a5 Fit fullscreen call video along the screen without rotating controls 2026-10-20 00:50:05 +02:00
evgeny bea8f1f901 Document Samsung surface ACK deadlock and hardware regression results 2026-10-20 00:43:06 +02:00
evgeny dd5dee2767 Preserve legacy timestamp waits and validate actual surface acknowledgements 2026-10-20 00:42:05 +02:00
evgeny 3eccf58c5e Add hardware TextureView ACK regression probe 2026-10-20 00:39:18 +02:00
evgeny 65f7babb2a Register Android video presentation ACK before the frame draw 2026-10-20 00:38:05 +02:00
evgeny 2c828d481d Record full 60 fps Samsung to Unisoc decode verification 2026-10-20 00:26:11 +02:00
evgeny 49317aab75 Document 60 fps camera modes and presentation recovery 2026-10-20 00:25:33 +02:00
evgeny 3a7aab9186 Cancel Android codec verification when the call stops 2026-10-20 00:24:28 +02:00
evgeny 521dce5528 Use verified Android encoder levels and expose negotiated frame rate 2026-10-20 00:21:21 +02:00
evgeny 3037c5fa87 Verify 50 fps backends and decode all frames during display catch-up 2026-10-20 00:16:23 +02:00
evgeny 52d9a347c3 Bound persistent presentation lag and report catch-up decisions 2026-10-20 00:15:42 +02:00
evgeny 8c826fa2e7 Exercise both video orientations at 30, 50 and 60 fps 2026-10-20 00:15:42 +02:00
evgeny 3db52c755c Preserve phase when sampling 50 fps from a 60 fps camera 2026-10-20 00:15:22 +02:00
evgeny 9bc3e7228c Test Android frame pacing and extend hardware diagnostics to 60 fps 2026-10-20 00:13:48 +02:00
evgeny 0a73d140e6 Account for full refresh-cycle GUI acknowledgement latency 2026-10-20 00:12:26 +02:00
evgeny d8d9f7c4a4 Select camera resolution and frame rate in Android calls 2026-10-20 00:11:15 +02:00
evgeny 27cbf2ba44 Recover persistent video lag by skipping decoded presentation 2026-10-20 00:09:57 +02:00
evgeny 4a759115c9 Probe Android video codecs at 30, 50 and 60 fps 2026-10-20 00:09:56 +02:00
evgeny d991b4c63e Preserve complete generated decoder probe byte arrays 2026-10-20 00:08:16 +02:00
evgeny 238ebcc751 Validate 60 fps probe bitstreams in both orientations 2026-10-20 00:07:53 +02:00
evgeny 0830fe4461 Carry selected video frame rate through desktop call settings 2026-10-20 00:07:36 +02:00
evgeny f6701f81f9 Probe desktop decoders independently at 30 and 60 fps 2026-10-20 00:07:08 +02:00
evgeny 0d08c21330 Select decoder probe rate and preserve HEVC level 4.1 2026-10-20 00:06:45 +02:00
evgeny 4f1af6456d Generate real 60 fps video decoder probe fixtures 2026-10-20 00:06:29 +02:00
evgeny a852a9738b Select camera capture rate and verify 30/60 fps pacing 2026-10-20 00:03:01 +02:00
evgeny ee1e28bff9 Carry selected camera frame rate through isolated capture IPC 2026-10-20 00:02:25 +02:00
evgeny 74a4135cdc Accept faster camera capture modes and pace up to 60 fps 2026-10-20 00:01:13 +02:00
evgeny 9d6dc1b469 Document verified Samsung HEVC High tier support 2026-10-19 23:40:38 +02:00
evgeny c0d5db318f Keep codec mode probes consistent with live encoding 2026-10-19 23:38:53 +02:00
evgeny 7be4b8884c Verify production HEVC modes and cross-platform reception 2026-10-19 23:36:58 +02:00
evgeny 08c74894ef Probe HEVC High tier on Android and desktop receivers 2026-10-19 23:34:15 +02:00
evgeny f2d67990f8 Cover High tier negotiation and independent KEY probes 2026-10-19 23:33:51 +02:00
evgeny 7122d70ea6 Negotiate HEVC Main High tier with explicit level limits 2026-10-19 23:32:29 +02:00
evgeny 5c1807a712 Show actual video call codecs on Android 2026-10-19 23:30:13 +02:00
evgeny 9b5743123b Add standalone Samsung HEVC comparison probe 2026-10-19 23:15:40 +02:00
evgeny 833eeb0a8d Document Full HD call modes and verified codec recovery checks 2026-10-19 22:56:35 +02:00
evgeny 72cb1fa829 Clarify camera mode ownership and codec probe structure 2026-10-19 22:54:55 +02:00
evgeny 7b341d9f4f Cancel desktop decoder probing when a call stops 2026-10-19 22:52:40 +02:00
evgeny c211e17af9 Log Android verified camera modes and guard early camera events 2026-10-19 22:49:57 +02:00
evgeny fcdc098415 Refresh call camera modes on device changes outside the GUI 2026-10-19 22:47:45 +02:00
evgeny 6e0d3f3022 Exercise real Full HD codecs and camera buffer boundaries 2026-10-19 22:46:35 +02:00
evgeny cf2fc67a53 Update camera integration test to nonblocking audio clocks 2026-10-19 22:46:09 +02:00
evgeny 902e7bb921 Use explicit HEVC Main tier and diagnose fixture rejection 2026-10-19 22:45:38 +02:00
evgeny bf8dfeec02 Probe desktop Full HD codecs and retain GPU support by size 2026-10-19 22:37:29 +02:00
evgeny 720ee64fd1 Cover Full HD KEY geometry in native validation tests 2026-10-19 22:37:12 +02:00
evgeny fb660f2b3e Select verified KEY fixtures by actual video geometry 2026-10-19 22:35:56 +02:00
evgeny f50f717b35 Generate landscape and portrait Full HD decoder fixtures 2026-10-19 22:35:34 +02:00
evgeny 7a0bc9516c Populate call resolutions from camera modes and test Full HD IPC 2026-10-19 22:35:05 +02:00
evgeny eb11d2ebc5 Carry native camera modes through Full HD capture protocol 2026-10-19 22:33:59 +02:00
evgeny 9a3ef7b622 Expand Android call previews into available screen space 2026-10-19 22:29:02 +02:00
evgeny 51c6bbb96f Queue Android call camera startup until video probing completes 2026-10-19 21:30:02 +02:00
evgeny c3c88457a8 Document independent video scheduling and verified recovery behavior 2026-10-19 21:22:39 +02:00
evgeny 55e87d20bf Verify generation changes and stale ACK isolation after restart 2026-10-19 21:18:03 +02:00
evgeny 9913222e03 Check video queue publication and nonblocking audio clock parity 2026-10-19 21:16:55 +02:00
evgeny 8c22df8f8e Clarify GUI ownership and preserve video queue payload lifetime 2026-10-19 21:16:10 +02:00
evgeny d0232e4c6e Smooth video presentation phase across render latency changes 2026-10-19 21:15:18 +02:00
evgeny 6f542bfae9 Test video timing with slow decoder and missing presentation ACK 2026-10-19 21:13:21 +02:00
evgeny d9843b8cbd Separate video decoding from event-driven presentation timing 2026-10-19 21:12:02 +02:00
evgeny 93f6fa1d52 Allow video playout diagnostics outside the timer thread 2026-10-19 21:09:22 +02:00
evgeny 402c0c0611 Add bounded video SPSC slots and mutex-free wake events 2026-10-19 21:08:41 +02:00
evgeny 9ca0e61361 Expose nonblocking call clock access through both mutexes 2026-10-19 21:07:44 +02:00
evgeny 562bdf54a6 Add nonblocking jitter clock reads for video scheduling 2026-10-19 21:07:44 +02:00
evgeny 193a549d47 Expose lock-free cumulative call video receive count 2026-10-19 21:07:19 +02:00
evgeny 7b38fd0269 Diagnose decoder work crossing video presentation deadlines 2026-10-19 20:57:45 +02:00
evgeny 10018d008e Update call video controls and sensor rotation across Android and desktop 2026-10-19 20:24:39 +02:00
evgeny ae1d05deda Fix FFmpeg D3D11VA implementation selection on Windows 2026-10-19 19:45:00 +02:00
evgeny 64761fd945 Record verified video builds and intermittent stream audio timing assertion 2026-10-19 19:27:50 +02:00
evgeny 5e38e5b71d Use minimal timer access and diagnose failed video image transforms 2026-10-19 19:24:07 +02:00
evgeny 9d45488369 Require live GPU output before EOS and document presentation-clock buffering 2026-10-19 19:21:48 +02:00
evgeny 16bdf95ba3 Prefer verified Android hardware codecs and use measured low-latency decoder pipeline 2026-10-19 19:21:01 +02:00
evgeny 2a08fd8acd Report decoder stage timings and detect real output stalls independently of playback waits 2026-10-19 19:19:27 +02:00
evgeny ecdba8fb63 Measure presentation cadence and stop latency while preserving unaccepted GPU KEY on fallback 2026-10-19 19:16:17 +02:00
evgeny 243233a20b Test delayed decoder output, four slice threads and complete generation drains 2026-10-19 19:15:05 +02:00
evgeny a4a687be4c Recover real GPU decoder failures on CPU without treating ordinary EAGAIN as frame loss 2026-10-19 19:14:03 +02:00
evgeny f9348ce36f Verify actual bitstreams through asynchronous decoder API and CPU encoder fallback 2026-10-19 19:13:00 +02:00
evgeny 5f45b38cfb Exercise acknowledged frame timing, lossless stalls and queue-dependent deceleration 2026-10-19 19:12:23 +02:00
evgeny 8eb869e41c Enable device-backed FFmpeg hardware decoding and verify exact RX queue accounting 2026-10-19 19:11:07 +02:00
evgeny 4bb2e42a7e Keep decoder reserve based on paced probe rather than transient burst depth 2026-10-19 19:09:17 +02:00
evgeny 895cdd5ac2 Apply presentation-clock queue pacing and three decoded slots to Android 2026-10-19 19:08:51 +02:00
evgeny ecae1cc3c2 Drive desktop video from presentation ACKs with three decoded slots and interruptible deadlines 2026-10-19 19:07:51 +02:00
evgeny 713a972c01 Decode call video through bounded submit/drain with verified GPU and four CPU slice threads 2026-10-19 19:06:52 +02:00
evgeny dc22014bcc Use four CPU encoder workers and retain GPU priority with software fallback 2026-10-19 19:04:08 +02:00
evgeny 28f3b264b8 Anchor video pacing to presentation acknowledgements and total backlog 2026-10-19 19:03:48 +02:00
evgeny baeb4973dd Expose RX frame depth and wake decoding when complete video arrives 2026-10-19 19:02:51 +02:00
evgeny c92f7991ec Record Windows startup GUI stalls and capture-gap scheduling correction 2026-10-19 18:25:34 +02:00
evgeny 40dcc76adb Trace GUI event loop stalls separately from video painting and dispatch 2026-10-19 18:24:57 +02:00
evgeny 47033718ad Apply capture gap recovery cadence consistently on Android 2026-10-19 18:23:25 +02:00
evgeny 526ee5ec8b Test queued capture gaps without dropping frames or delaying ready video 2026-10-19 18:23:25 +02:00
evgeny 9bffe49e62 Bound video display cadence independently of capture timestamp gaps 2026-10-19 18:22:47 +02:00
evgeny 642fc34bcc Bound video recovery correction before media timestamp arithmetic 2026-10-19 17:45:17 +02:00
evgeny 12e9e4da37 Verify GPU backlog drains fully without additional camera inputs 2026-10-19 17:44:14 +02:00
evgeny d266c5da25 Respect GPU pipeline capacity before submitting a new camera frame 2026-10-19 17:43:45 +02:00
evgeny 0f22be6885 Check audio source rate at the actual PCM span boundary 2026-10-19 17:42:49 +02:00
evgeny b7ae7ef700 Document lossless recovery contract and verify actual audio source rate 2026-10-19 17:40:51 +02:00
evgeny a9b91145a6 Document sequential presentation diagnostics and simplify GUI frame ownership 2026-10-19 17:39:54 +02:00
evgeny 4ed2c4bbab Wake video transmission on router queue readiness without polling delay 2026-10-19 17:38:31 +02:00
evgeny 5f1985f9ab Verify RX drain and explicit decoder rejection semantics 2026-10-19 17:37:24 +02:00
evgeny 943f9f2646 Drain receiver backlog before admitting new production and avoid double waiting 2026-10-19 17:35:57 +02:00
evgeny 02e7bbd9af Acknowledge Android frames after RenderThread consumption on all supported APIs 2026-10-19 17:34:44 +02:00
evgeny b11a1beb44 Test lossless mailbox retry after partial video transmission 2026-10-19 17:33:25 +02:00
evgeny 322947777c Stop invalid encoder output explicitly while preserving transient backpressure retries 2026-10-19 17:32:49 +02:00
evgeny c0422a73c0 Drain accepted MediaCodec inputs before bitrate fallback replacement 2026-10-19 17:31:46 +02:00
evgeny 0e53a9f5dd Log retained video batches and verify every GPU input across bitrate changes 2026-10-19 17:31:15 +02:00
evgeny 486d3314ef Acknowledge Android texture consumption before presenting the next frame 2026-10-19 17:30:03 +02:00
evgeny 13f09cfc20 Drain old GPU frames before bitrate replacement and poll outputs while blocked 2026-10-19 17:29:10 +02:00
evgeny 5f772fa97f Drain GPU outputs independently of capture and verify lossless recovery 2026-10-19 17:28:13 +02:00
evgeny 0bc2715a30 Preserve Android encoder outputs and throttle raw input before encoding 2026-10-19 17:25:50 +02:00
evgeny 0a2700a387 Use measured media tempo for sequential Android video playout 2026-10-19 17:25:16 +02:00
evgeny 4e9cce8a01 Present desktop video sequentially through GUI paint acknowledgement 2026-10-19 17:23:58 +02:00
evgeny 0eb743d627 Retain video mailbox tails and pause raw input under backpressure 2026-10-19 17:22:51 +02:00
evgeny 284a1268e4 Preserve encoded chain on temporary video transport backpressure 2026-10-19 17:22:09 +02:00
evgeny 7cbe5375d4 Define audio paced lossless video scheduling interface 2026-10-19 17:21:07 +02:00
evgeny 02a11f7d0a Diagnose Windows call video timing and queue losses 2026-10-19 17:20:12 +02:00
evgeny 21a20da5ee Coordinate Android call audio ownership and improve video controls 2026-10-19 16:51:51 +02:00
evgeny 39ed42da2e Translate call policies and verify desktop phone button colors 2026-10-19 15:20:25 +02:00
evgeny 94e79e5630 Color Android member phone actions using live call presence 2026-10-19 15:20:25 +02:00
evgeny a1f8eaeec2 Show explicit caller choice and already-active radio outcome on Android 2026-10-19 15:20:25 +02:00
evgeny 187fc7fee8 Handle Telecom presentation and pending caller choice notifications 2026-10-19 15:20:25 +02:00
evgeny bd2dd056d6 Honor Telecom incoming UI permission and report phone busy outcomes 2026-10-19 15:20:25 +02:00
evgeny 4cebd0be54 Wire incoming call policy and caller continuation through JNI 2026-10-19 15:20:25 +02:00
evgeny 7d5399cd62 Add five independent Android incoming call preferences 2026-10-19 15:20:25 +02:00
evgeny 97b537c005 Document incoming call policies, busy confirmation and radio status replies 2026-10-19 15:20:04 +02:00
evgeny 558b2a2ac0 Describe ephemeral phone presence in existing BGP flags 2026-10-19 15:19:22 +02:00
evgeny a9d63480cf Document incoming policy and already-active radio contracts 2026-10-19 15:18:49 +02:00
evgeny 27b0f64ec0 Record media regression failures found by full test run 2026-10-19 15:18:09 +02:00
evgeny cda46c0b82 Translate and test already-active radio response with packet loss 2026-10-19 15:05:27 +02:00
evgeny 4deee21d9f Notify caller when subscriber already listens to the channel radio 2026-10-19 15:05:09 +02:00
evgeny 5a318f6f60 Isolate packet loss fixtures between call policy scenarios 2026-10-19 15:03:59 +02:00
evgeny babcb7786a Log phone policy regression state and allow bounded teardown coverage 2026-10-19 15:02:13 +02:00
evgeny b21aeaa6f8 Test normal call busy confirmation and all incoming rejection policies 2026-10-19 14:59:55 +02:00
evgeny 79b4235960 Cover radio phone policy, BGP color flags, retries and caller choice timeout 2026-10-19 14:59:17 +02:00
evgeny 4683c98c08 Apply independent call and radio blocking before phone busy handling 2026-10-19 14:56:58 +02:00
evgeny a72668a491 Represent independent incoming call preferences as one policy mask 2026-10-19 14:56:58 +02:00
evgeny 03a4462407 Reject phone busy calls before any incoming UI when policy is enabled 2026-10-19 14:55:55 +02:00
evgeny a2fdbb6afb Define automatic rejection policy for phone busy subscribers 2026-10-19 14:55:55 +02:00
evgeny d4cd80d3dd Translate phone presence and caller confirmation controls 2026-10-19 14:54:23 +02:00
evgeny 37cf6089e7 Add caller decision controls and colored phone presence on desktop 2026-10-19 14:54:01 +02:00
evgeny b68116eedf Expose phone busy event to desktop call interface 2026-10-19 14:54:01 +02:00
evgeny 6a4a0f45d8 Separate phone status publication from incoming caller warning 2026-10-19 14:51:12 +02:00
evgeny b7aaf268af Wait for caller override before ringing a phone-busy radio subscriber 2026-10-19 14:48:39 +02:00
evgeny be367a08b6 Gate incoming calls on explicit caller decision when phone is busy 2026-10-19 14:47:59 +02:00
evgeny 6ad9c8c80b Define pending decision state for telephone busy warning 2026-10-19 14:46:47 +02:00
evgeny 7ed1a9b167 Carry phone presence in existing BGP and member flag bytes 2026-10-19 14:46:47 +02:00
evgeny 505185430c Define live phone presence and call continuation API 2026-10-19 14:46:16 +02:00
evgeny 1d28ced4f4 Replace Android member radio badge with a green radio call button 2026-10-19 14:30:35 +02:00
evgeny fdd39e82e2 Show desktop radio subscription on the call icon and allow Android icon tint 2026-10-19 14:30:35 +02:00
evgeny aca14bfb70 Select the radio ringtone in both Android ringtone playback paths 2026-10-19 14:25:27 +02:00
evgeny 7693db7b4d Expose radio ringtone enable and volume settings on desktop 2026-10-19 14:25:12 +02:00
evgeny 30f79f7a8d Play the dedicated desktop ringtone for incoming radio calls 2026-10-19 14:25:11 +02:00
evgeny cf4edbbf62 Add a distinct three-tone radio call ringtone for desktop and Android 2026-10-19 14:25:11 +02:00
evgeny 9712a0a583 Check desktop call buttons, online changes, drags, and shifted radio mute control 2026-10-19 14:22:46 +02:00
evgeny cd58903101 Connect member row call actions and retain invitations in the context menu 2026-10-19 14:21:52 +02:00
evgeny a8c2ce8401 Render and handle speaker, radio call, and phone buttons in desktop member rows 2026-10-19 14:21:37 +02:00
evgeny dad819282a Expose live member availability to desktop action buttons 2026-10-19 14:21:37 +02:00
evgeny 04acb6f836 Check radio timer ownership through public service lifecycle 2026-10-19 14:14:52 +02:00
evgeny c0626e0387 Verify idle radio calls have no timers and reject invalid invitation expiry 2026-10-19 14:13:45 +02:00
evgeny e61dc8cd06 Wait for Android channel loading once before radio activation 2026-10-19 14:11:49 +02:00
evgeny a0e440c0f5 Use readable radio call states and completion reasons in logs 2026-10-19 14:11:49 +02:00
evgeny 1b3269f76b Require active subscription on radio acceptance and verify live BGP gating 2026-10-19 14:10:35 +02:00
evgeny 613a75becc Schedule only active radio deadlines and reject late terminal answers 2026-10-19 14:09:10 +02:00
evgeny 5f9e9975d3 Initialize radio router channels before checking send backpressure 2026-10-19 14:07:39 +02:00
evgeny a73d829080 Exercise radio invitations over routed network with deadlines and packet loss 2026-10-19 14:05:29 +02:00
evgeny a831cf6295 Translate desktop radio invitation windows and outcomes 2026-10-19 14:04:45 +02:00
evgeny b57c8e5041 Move Android message action to member details and translate radio call UI 2026-10-19 14:03:24 +02:00
evgeny 25376197db Open channel radio from Android calls and move invitation into user details 2026-10-19 14:02:56 +02:00
evgeny 62a976aef5 Add Android radio call controls and incoming overlay 2026-10-19 14:01:52 +02:00
evgeny 9bd9f04bcf Dispatch Android radio call events and clear them before core restart 2026-10-19 14:01:28 +02:00
evgeny 949c300c53 Set radio invitation timeout to one minute 2026-10-19 14:01:27 +02:00
evgeny 23ece16940 Own pending Android radio calls independently of Activity and ViewModel 2026-10-19 14:00:44 +02:00
evgeny dbb07df7e9 Support incoming radio ringtone and notification actions on Android 2026-10-19 14:00:43 +02:00
evgeny b48a91853d Confirm Android radio activation and support explicit channel selection 2026-10-19 13:59:35 +02:00
evgeny 34890ea709 Bridge Android radio invitation commands to the core thread 2026-10-19 13:59:34 +02:00
evgeny a97c915663 Fix radio signaling and desktop channel type references 2026-10-19 13:58:39 +02:00
evgeny 3f97dafbf7 Show desktop radio calls and activate channel radio after acceptance 2026-10-19 13:58:06 +02:00
evgeny 427794fed8 Expose explicit desktop radio activation for accepted invitations 2026-10-19 13:57:30 +02:00
evgeny 858ba38caf Add radio call action to desktop channel members 2026-10-19 13:57:29 +02:00
evgeny 310d997af3 Deliver radio invitation events to desktop UI 2026-10-19 13:57:29 +02:00
evgeny 849f1c8618 Start and stop radio invitation service with chat lifecycle 2026-10-19 13:56:35 +02:00
evgeny b0fe2d66af Coordinate busy state between telephone and radio invitations 2026-10-19 13:56:35 +02:00
evgeny 7f4ac2fadf Register radio invitation service and shared core events 2026-10-19 13:56:17 +02:00
evgeny 639be7fcc2 Add channel radio invitation signaling and bounded result cache 2026-10-19 13:56:06 +02:00
evgeny a84b226b18 chatgui: enlarge member availability indicator and remove white outline 2026-10-19 13:23:40 +02:00
evgeny 214498f892 Open broadcast controls when selecting the local stream 2026-10-19 12:54:43 +02:00
evgeny 62633bc924 Show the group stream icon only for active broadcasts
Open a single active stream directly and show a selection for multiple streams. Move stream publishing and management into the group menu.
2026-10-19 12:51:56 +02:00
evgeny ffe563bb9f Enable VibeChat UTUN service and package the Wintun driver
Add a settings button to open the current config in an external editor. Wait for core, UTUN and chat startup before reporting readiness; verify startup failures and restarts. Include Wintun DLL and license notices in Windows builds and installers.
2026-10-19 12:33:35 +02:00
evgeny bdb6bf80a4 Free Android stream preview space with start and stop icons 2026-10-19 12:18:42 +02:00
evgeny d0bbe8444e Refine Android stream icons and show channel in title 2026-10-19 12:08:57 +02:00
evgeny 1084cd10ec Compact Android stream controls into one icon toolbar 2026-10-19 11:51:33 +02:00
evgeny ec545a8665 Remove the minimized-to-tray notification 2026-10-19 10:44:29 +02:00
evgeny d7f30d7bb1 Activate the existing VibeChat window on repeated launches 2026-10-19 10:29:12 +02:00
evgeny 50017b7f74 Sync configured chat names and embed the Windows application icon 2026-10-19 09:54:42 +02:00
evgeny 7140775d8d Preserve source context when applying the live QP dependency patch 2026-10-18 22:30:28 +02:00
evgeny 5568331510 Keep the QP adaptation tooltip readable in source 2026-10-18 22:26:23 +02:00
evgeny 242d0f5493 Verify the QP ceiling constrains real HEVC output under bitrate pressure 2026-10-18 22:24:55 +02:00
evgeny 6e0b842858 Test live QP driver updates and rejection handling against patched FFmpeg 2026-10-18 22:23:23 +02:00
evgeny 2aa890201d Package live AMF QP support with the FFmpeg build profile 2026-10-18 22:20:44 +02:00
evgeny aff8a21ae4 Stop raising QP as soon as measured bitrate returns to target 2026-10-18 22:11:48 +02:00
evgeny fae794d39e Persist broadcast QP controls and verify smooth rate feedback 2026-10-18 22:11:29 +02:00
evgeny fe86a16cac Support live AMF QP ceiling updates in bundled FFmpeg 2026-10-18 22:08:47 +02:00
evgeny 00698537ad Add QP ceiling and adaptation controls to the broadcast window 2026-10-18 22:07:36 +02:00
evgeny df02aa8170 Adapt the stream QP ceiling from measured encoded bitrate 2026-10-18 22:06:34 +02:00
evgeny 0070221045 Apply configured QP ceilings and expose live encoder adjustment capability 2026-10-18 22:05:53 +02:00
evgeny 54b50f4177 Define stream QP ceiling and smooth bitrate feedback controller 2026-10-18 22:05:33 +02:00
evgeny a51af91718 Use AMF remote desktop latency mode for HEVC broadcasts 2026-10-18 21:51:54 +02:00
evgeny bd92ac0c88 Ensure the minimal Windows FFmpeg build includes HEVC header parsing 2026-10-18 19:52:41 +02:00
evgeny 4b88402f84 Verify FFmpeg diagnostic callback restoration 2026-10-18 19:49:04 +02:00
evgeny 0cfc29d9eb Restore FFmpeg diagnostics after Qt multimedia initializes its logger 2026-10-18 19:48:34 +02:00
evgeny d1ba26f8ac Log converted encoder input detail alongside HEVC quantization 2026-10-18 19:46:35 +02:00
evgeny 6d5a626b9b Include HEVC header tracing in bundled FFmpeg and round-trip tests 2026-10-18 19:45:36 +02:00
evgeny f7853f145c Trace HEVC key frame quantization and reduce AMF log noise 2026-10-18 19:44:38 +02:00
evgeny 78b147f806 Expose AMF driver diagnostics and effective encoder settings 2026-10-18 18:50:20 +02:00
evgeny b8bc9b409a Fix Android stream layout and show measured publisher bitrate 2026-10-18 18:24:30 +02:00
evgeny e246357f92 Document valid HEVC PPS support and shared startup verification 2026-10-18 17:33:13 +02:00
evgeny aedb0e6170 Cover HEVC PPS flags and incompatible AMF startup output 2026-10-18 17:21:05 +02:00
evgeny f8827a9466 Accept valid HEVC PPS flags and verify call GPU probes fully 2026-10-18 17:19:57 +02:00
evgeny 0730fb6bdd Описать GPU-путь звонка, границы буферизации и проверку задержки 2026-10-18 17:10:17 +02:00
evgeny 5e672f01d9 Проверить отмену пачки видеокадров при завершении звонка 2026-10-18 17:05:54 +02:00
evgeny 66526ad4a5 Проверить готовую пачку GPU через транспорт и обновить JNI видеопорта 2026-10-18 17:05:00 +02:00
evgeny 86a278ee99 Передавать готовую пачку GPU атомарно через TX mailbox 2026-10-18 17:01:22 +02:00
evgeny 0343825746 Показывать фактическую глубину GPU-конвейера без дополнительной TX-очереди 2026-10-18 16:57:49 +02:00
evgeny 09f48403c5 Проверить GPU-конвейер на задержке, пакетной выдаче и отказе замены 2026-10-18 16:55:31 +02:00
evgeny 2facdb6949 Сохранять выдачу старого GPU до первого IDR замены 2026-10-18 16:54:36 +02:00
evgeny ae8eeab490 Выдавать готовые кадры GPU без дополнительного ожидания в TX 2026-10-18 16:51:55 +02:00
evgeny 6715acdef8 Добавить GPU-конвейер звонка с исходными PTS и подтверждением смены по IDR 2026-10-18 16:50:33 +02:00
evgeny 3ed9acb384 Устранить сжатие панели звонка и распараллелить HEVC внутри кадра 2026-10-18 16:28:36 +02:00
evgeny 21e4821e22 Ограничить камеру звонка до 30 fps перед конвертацией 2026-10-18 16:27:43 +02:00
evgeny f6051d3bcd Показывать этапы первого долгого кадра в интервале 2026-10-18 16:18:42 +02:00
evgeny 5a55bcb500 Диагностика задержек программного видео на desktop 2026-10-18 16:17:51 +02:00
evgeny 03ff431d78 Avoid Windows interface macro collision in local transport 2026-10-18 15:29:34 +02:00
evgeny 4c62f45984 Bound local discovery traffic and validate invitations 2026-10-18 15:18:40 +02:00
evgeny 80b23997c0 Use stream category filters in desktop stream tests 2026-10-18 15:10:59 +02:00
evgeny f6f8647091 Decouple file network test from temporary packet logs 2026-10-18 15:10:57 +02:00
evgeny 0e2253cdc8 Remove Android video timing probes and categorize radio logs 2026-10-18 15:10:06 +02:00
evgeny 044a20d4a0 Clean desktop stream lifecycle and event bridge logging 2026-10-18 15:09:24 +02:00
evgeny b40ba9308f Route desktop call and PTT diagnostics to their categories 2026-10-18 15:08:57 +02:00
evgeny 74e2238b7f Categorize file transfer and browser logs and reduce I/O noise 2026-10-18 15:08:42 +02:00
evgeny 81ee235f73 Keep file request diagnostics in media and drop packet noise 2026-10-18 15:07:45 +02:00
evgeny dcdc700d8d Remove temporary per-packet file routing diagnostics 2026-10-18 15:06:54 +02:00
evgeny 81de8747e2 Move video call and stream diagnostics to module categories 2026-10-18 15:06:16 +02:00
evgeny 28826e050c Add local UDP discovery and invitation delivery for chat clients 2026-10-18 14:46:04 +02:00
evgeny aad295c9d0 Render encrypted group attachments correctly on Android 2026-10-18 13:14:56 +02:00
evgeny b3495349e1 Offer attachment encryption after selecting a file 2026-10-18 11:47:53 +02:00
evgeny 7e08135bba Remove unpublished cipher block indexes during recovery 2026-10-18 10:47:24 +02:00
evgeny 057bdb3112 Recover cancelled unpublished encryption jobs without retaining keys 2026-10-18 10:46:56 +02:00
evgeny a31ade1054 Exercise group encryption choice through real Qt input events 2026-10-18 10:44:23 +02:00
evgeny 98f71735a0 Document encrypted group attachment format and grant lifecycle 2026-10-18 10:42:16 +02:00
evgeny 1896a19eba Explain exact encrypted container rejection reasons 2026-10-18 10:41:27 +02:00
evgeny 8a46a53a21 Verify cache restoration and channel cleanup for encrypted attachments 2026-10-18 10:40:24 +02:00
evgeny 5d2522b8de Account decrypted copies in media quota and protect active containers 2026-10-18 10:39:29 +02:00
evgeny feb327afc7 Preserve durable publication and restore evicted decrypted files 2026-10-18 10:39:03 +02:00
evgeny 3c395dfe5d Share durable encrypted file publication with direct messages 2026-10-18 10:39:03 +02:00
evgeny 4844253f12 Size storage-role query for complete signed-role predicate 2026-10-18 10:37:19 +02:00
evgeny 01e1772615 Use signed storage role and reject revoked key recipients 2026-10-18 10:37:12 +02:00
evgeny fd61aedce3 Use signed storage role in attachment replication fixture 2026-10-18 02:31:55 +02:00
evgeny 9f36fb0ddd Release fixture history gate after verified member synchronization 2026-10-18 02:26:30 +02:00
evgeny de1da1c9b1 Translate encrypted attachment controls and request states 2026-10-18 02:26:01 +02:00
evgeny 3f48a145ae Trace group history readiness in attachment network test 2026-10-18 02:25:35 +02:00
evgeny 6c8e2763e5 Expose manual download API and distinguish storage replication in test 2026-10-18 02:19:45 +02:00
evgeny 945208ce77 Register encrypted attachment unit and network tests 2026-10-18 02:19:11 +02:00
evgeny 62a4f60508 Test encrypted containers and consent delivery across restart 2026-10-18 02:19:10 +02:00
evgeny ada1406d79 Keep membership signature regressions on shared verifier 2026-10-18 02:18:21 +02:00
evgeny ade9401e05 Fix desktop attachment UI include path 2026-10-18 02:17:54 +02:00
evgeny ad7050ad2c Fix shared cipher linkage and transport type includes 2026-10-18 02:17:18 +02:00
evgeny 7cffb15c73 Add author request list with individual key approvals 2026-10-18 02:17:08 +02:00
evgeny 2b7def369d Render locked group attachments and local decrypted media 2026-10-18 02:16:29 +02:00
evgeny 9f813424bb Deliver attachment key state changes to desktop UI 2026-10-18 02:15:25 +02:00
evgeny 4703a46eb9 Add per-send encryption toggle for group files and voice 2026-10-18 02:15:16 +02:00
evgeny 61d1561612 Bind encrypted manifests and request keys after verified download 2026-10-18 02:14:31 +02:00
evgeny 41696b7e0b Route encrypted group preparation through key owner 2026-10-18 02:14:26 +02:00
evgeny e5a1fc5a48 Start and stop attachment key owner with chat service 2026-10-18 02:13:54 +02:00
evgeny 60e040677b Declare attachment grant service and lifecycle state 2026-10-18 02:13:40 +02:00
evgeny 321ed8e747 Implement durable encrypted attachment grants and requests 2026-10-18 02:13:28 +02:00
evgeny e45401449e Share complete membership verification for private grants 2026-10-18 02:10:11 +02:00
evgeny 6a90d79d04 Add authenticated encrypted attachment container 2026-10-18 02:09:30 +02:00
evgeny b4ef4ec919 Switch message cipher consumers to shared media API 2026-10-18 02:08:52 +02:00
evgeny 9c22b04cb0 Use common file cipher primitives in direct messages 2026-10-18 02:08:45 +02:00
evgeny d6db3d18ce Extract shared CCM and streaming media cryptography 2026-10-18 02:08:29 +02:00
evgeny be295f768c feat(chat): add local DM deletion and improve Android member list
Delete local conversations atomically, retain replay protection and sequence counters, and guard asynchronous media work by history generation. Add Android avatar menu and confirmation, persistent file cleanup, and regression coverage.

Fix unnamed member wire padding and compact Android member action spacing.

Validation: 158 core tests passed; targeted DM tests, desktop build, Android unit tests and debug APK build passed.
2026-10-18 02:04:05 +02:00
evgeny 97710b609f test(nat): require OPEN for public TCP listeners receiving connections 2026-10-18 01:02:08 +02:00
evgeny 75d383f89d docs(nat): describe passive incoming connection evidence 2026-10-18 00:59:11 +02:00
evgeny a20a219063 fix(core): free pending ping payloads when destroying instance 2026-10-18 00:57:11 +02:00
evgeny 6075b4cd0f test(nat): verify public incoming profiles on isolated routed network 2026-10-18 00:55:37 +02:00
evgeny 49385ab91c test(nat): isolate active measurements from incoming transport evidence 2026-10-18 00:54:51 +02:00
evgeny 094a8bb499 test(nat): cover accepted UDP and TCP profiles and LAN exclusions 2026-10-18 00:42:35 +02:00
evgeny df2f6ff300 feat(nat): confirm direct open profile from accepted public connections 2026-10-18 00:41:53 +02:00
evgeny 37e092672a fix(nat): retain cold ingress reservation after partial measurements 2026-10-17 23:53:09 +02:00
evgeny 7bf877b7d6 test(nat): exercise full retry budget with high RTT and lost reports 2026-10-17 23:45:35 +02:00
evgeny 305b61ccab fix(nat): advance after an unfinished pair uses its full retry budget 2026-10-17 23:43:21 +02:00
evgeny 1d1f5d081f test(nat): reserve fresh helper before publishing it during recheck 2026-10-17 23:41:51 +02:00
evgeny edc78cbf4a test(nat): cover helper rotation, preserved OPEN and budget resume 2026-10-17 23:39:17 +02:00
evgeny a72e514dc5 fix(nat): keep fresh ingress checks active during profile rechecks 2026-10-17 23:38:52 +02:00
evgeny 6ad3674904 fix(nat): continue helper traversal after partial mapping results 2026-10-17 23:37:10 +02:00
evgeny 7c7b9646f3 test(android): distinguish untranslated and unmeasured interface addresses 2026-10-17 23:20:55 +02:00
evgeny 575a7bfe32 fix(android): show no NAT and inbound status instead of interface 2026-10-17 23:20:55 +02:00
evgeny cb8e654639 fix(android): preserve measured profiles in member address details 2026-10-17 23:20:55 +02:00
evgeny 3f7cfe1c86 fix(chatgui): distinguish untranslated addresses from unmeasured profiles 2026-10-17 23:19:25 +02:00
evgeny a485b10e63 fix(chatgui): show measured NAT profile in member addresses 2026-10-17 23:18:57 +02:00
evgeny 3e29a7ca1c test(android): expect distinct NAT? and EIM? address labels 2026-10-17 22:17:53 +02:00
evgeny 6cbcb860d6 fix(android): distinguish unmeasured NAT from NAT with unknown type 2026-10-17 22:07:59 +02:00
evgeny 86c08092af feat(android): show NAT profile and external address for own sockets 2026-10-17 22:07:58 +02:00
evgeny 1dfe349eb8 fix(android): consume local socket snapshots on core events 2026-10-17 22:07:58 +02:00
evgeny c69f8d8975 fix(nat): notify GUI and include profiles in Android socket snapshots 2026-10-17 22:07:58 +02:00
evgeny 60104ea093 fix(chatgui): label detected NAT with unknown type as NAT? 2026-10-17 22:02:36 +02:00
evgeny 2513e9ae81 tests: finish cold UDP detection before opening additional TCP links 2026-10-17 21:22:22 +02:00
evgeny d2a8e6fda7 db: accept TCP IP-only addresses while preserving stored projections 2026-10-17 19:58:54 +02:00
evgeny 2c7c3c9e8b tests: allow TCP IP observations from existing incoming links 2026-10-17 19:54:18 +02:00
evgeny 857092508a docs: explain TCP NAT presence and IP-only addresses 2026-10-17 19:53:34 +02:00
evgeny b588f8e9f4 tests: verify TCP IP-only detection across real NAT gateways 2026-10-17 19:52:39 +02:00
evgeny 62aee9f01c tests: cover TCP NAT presence and zero-port address publication 2026-10-17 19:50:33 +02:00
evgeny c42d75c2f5 nat: limit helper selection and topology retries to UDP 2026-10-17 19:49:42 +02:00
evgeny 5c0db6ce9e nat: remove TCP listener probes from endpoint detection 2026-10-17 19:47:37 +02:00
evgeny 6363c8b7ea nat: publish TCP external IPs with an unknown port 2026-10-17 19:46:33 +02:00
evgeny 5eb3526186 etcp: exchange observed IPs on established TCP links 2026-10-17 19:46:02 +02:00
evgeny 95f209d098 nat: determine TCP translation from one public IP observation 2026-10-17 19:45:29 +02:00
evgeny d7ad58b380 docs: clarify STUN result fields and API usage 2026-10-17 18:50:39 +02:00
evgeny dbe2640e74 nat: reject local STUN paths as Internet mapping evidence 2026-10-17 18:50:10 +02:00
evgeny 7a40ba536e tests: verify STUN estimates and ETCP promotion in isolated networks 2026-10-17 18:48:06 +02:00
evgeny 8983026625 docs: describe same-interface STUN estimates and working endpoint addresses 2026-10-17 18:47:41 +02:00
evgeny f5d923a671 tests: cover tentative STUN mapping, expiry and native priority 2026-10-17 18:46:38 +02:00
evgeny 417b848b50 nat: publish observed working addresses with tentative STUN profiles 2026-10-17 18:45:20 +02:00
evgeny 88ac08333e nat: use same-interface STUN mapping as tentative EIM 2026-10-17 18:45:02 +02:00
evgeny aede36388f docs: define EIM? as matching mapping with unknown inbound access 2026-10-17 18:27:16 +02:00
evgeny 906f247588 tests: verify tentative EIM with known helper and unknown inbound access 2026-10-17 18:25:53 +02:00
evgeny fad9fbe6c3 android: label unverified EIM and observed unknown NAT addresses 2026-10-17 18:19:47 +02:00
evgeny b539a6f338 gui: display EIM? when unsolicited ingress remains unverified 2026-10-17 18:19:23 +02:00
evgeny 58755dd75e nat: distinguish EIM with unverified unsolicited ingress 2026-10-17 18:19:23 +02:00
evgeny bbb8cff4a6 nat: prefer fresh public helpers while retaining known mapping candidates 2026-10-17 18:10:23 +02:00
evgeny 3c321ac59c docs: explain observed NAT candidates in signed node snapshots 2026-10-17 18:05:30 +02:00
evgeny eb09a675c7 nat: preserve the NAT address kind when mapping is unknown 2026-10-17 18:01:09 +02:00
evgeny 4284233e6f nat: keep unchanged public addresses distinct from unknown NAT 2026-10-17 17:59:22 +02:00
evgeny 5a8b05c50f nat: try known mapping peers before observer-only fallback 2026-10-17 17:57:15 +02:00
evgeny b9a5981f5c gui: shorten unknown NAT status to NAT? 2026-10-17 17:55:32 +02:00
evgeny 78f71ddbb0 nat: distinguish public interface addresses from unknown NAT candidates 2026-10-17 17:53:20 +02:00
evgeny 37461bb938 nat: finish observer-only rounds and document unknown-type candidates 2026-10-17 17:50:28 +02:00
evgeny 71abf74bfc tests: cover unknown NAT candidates and stalled ingress control 2026-10-17 17:48:55 +02:00
evgeny 79d8789410 nat: prioritize mapping candidates and label unknown NAT explicitly 2026-10-17 17:47:18 +02:00
evgeny 6037819269 nat: retain observed candidates and continue mapping after ingress failures 2026-10-17 17:45:24 +02:00
evgeny b96ae1a718 docs: introduce NAT detector results and basic usage 2026-10-17 17:18:07 +02:00
evgeny 6a2164717e docs: describe NAT detector state and public API arguments 2026-10-17 17:12:40 +02:00
evgeny eb3dd7f237 docs: explain NAT detection through its main idea and steps 2026-10-17 17:06:04 +02:00
evgeny d70d6cb66a tests: ignore stale helper probes and document local source rejection 2026-10-17 16:56:21 +02:00
evgeny 83efa229ed nat: reject local probe sources and preserve internet profiles on LAN 2026-10-17 16:54:07 +02:00
evgeny 308eb07742 docs: align endpoint and STUN contracts with the implemented detector 2026-10-17 16:32:08 +02:00
evgeny d5715a1f4f tests: wait for the requested NAT round instead of an earlier automatic result 2026-10-17 16:26:35 +02:00
evgeny 27a87412c5 tests: verify STUN diagnostics without publishing a foreign-port profile 2026-10-17 16:23:18 +02:00
evgeny 7673316dc8 docs: specify NAT v4 independent mapping reports and receiver-verified ingress 2026-10-17 16:19:47 +02:00
evgeny 4fab849917 nat: align probe timing and test receiver IP rejection and endpoint failover 2026-10-17 16:06:21 +02:00
evgeny 38447da5e8 nat: retry partial ingress measurements and keep known-IP anchor replies available 2026-10-17 16:02:01 +02:00
evgeny 8158d6a435 tests: exercise NAT mapping, filtering, delayed ingress and contaminated history 2026-10-17 15:57:49 +02:00
evgeny 3cb971fcba tests: wait for READY group routes before measuring NAT 2026-10-17 15:56:19 +02:00
evgeny 110dd82932 tests: separate STUN diagnostics and allow complete NAT probe rounds 2026-10-17 15:51:32 +02:00
evgeny 26ab3b13b4 nat: separate routed mapping reports from unsolicited UDP ingress 2026-10-17 15:43:38 +02:00
evgeny df80c5745d transport: support one-way authenticated NAT probes 2026-10-17 15:24:57 +02:00
evgeny 1dc26bf715 Add bounded mouse zoom and panning to live stream viewer 2026-10-17 01:49:35 +02:00
evgeny 93dc21f3c8 Exercise capture conversion failure with distinct source generations 2026-10-17 01:30:48 +02:00
evgeny fd73fae77f Keep stream capture failures and cursor snapshots tied to their source 2026-10-17 01:30:06 +02:00
evgeny 9c7d088f8b Compare retained image pixels without assuming SIMD color rounding 2026-10-17 01:30:05 +02:00
evgeny 64bbbdc631 Guard RGB conversion bounds and use padded frames in file playback 2026-10-17 01:28:42 +02:00
evgeny f81b93ad0d Allocate padded RGB frames for FFmpeg video conversion 2026-10-17 01:28:04 +02:00
evgeny 88e3e27145 Show cursor in Windows stream preview 2026-10-17 01:02:46 +02:00
evgeny bf729502a1 Improve Windows screen capture frame rate 2026-10-17 00:38:29 +02:00
evgeny f5660a4642 Hide window capture sources 2026-10-17 00:19:28 +02:00
evgeny e440c62339 Use native Windows monitor handle for cursor overlay 2026-10-17 00:16:56 +02:00
evgeny 4a6f95cebd Add native Windows cursor overlay to screen broadcasts 2026-10-16 23:50:52 +02:00
evgeny 49d55a25a1 Poll stream image thread cleanup from GUI loop 2026-10-16 23:09:29 +02:00
evgeny 1e21912dc6 Trace stream image worker and GUI shutdown waits 2026-10-16 22:45:58 +02:00
evgeny a63af43e59 Handle AMF periodic keyframes 2026-10-16 21:52:10 +02:00
evgeny 98d0040b27 Fix AMF live encoder startup probe 2026-10-16 21:26:58 +02:00
evgeny 2a439754ba Fix Windows FFmpeg configure link flags 2026-10-16 19:42:47 +02:00
evgeny 4401c4b1e4 Fix libaom build with NASM 3 2026-10-16 19:27:51 +02:00
evgeny 8cb656dbdc Minimize FFmpeg features for ChatGUI 2026-10-16 19:08:53 +02:00
evgeny eb98d2c724 Fetch x265 tags before configuring version metadata 2026-10-16 19:04:25 +02:00
evgeny f90cbae94a Enable AMD AMF in Windows ChatGUI FFmpeg build 2026-10-16 18:50:42 +02:00
evgeny 5116496565 Record thread exit evidence from latest Windows stream log 2026-10-16 18:09:51 +02:00
evgeny 7a2fa9e199 Move captured frame conversion to persistent worker 2026-10-16 18:08:42 +02:00
evgeny 5c9bf9e47c Log capture conversion thread and completion 2026-10-16 18:05:58 +02:00
evgeny 8d7ab6a1a6 Keep a converted frame for encoder reconfiguration 2026-10-16 18:05:07 +02:00
evgeny 97e20d2f1c Keep Qt video frame conversion off the publisher worker 2026-10-16 18:04:27 +02:00
evgeny c27ee36cfb Record passing publisher lifecycle regression suite 2026-10-16 17:39:12 +02:00
evgeny 37eccd06ea Record adaptive audio validation and confirmed stream startup failure 2026-10-16 17:38:32 +02:00
evgeny 16e547d15d Drain publisher while asynchronous microphone teardown completes 2026-10-16 17:34:21 +02:00
evgeny 114c0b3e2f Aggregate recurring audio glitch logs between buffer changes 2026-10-16 17:33:32 +02:00
evgeny 688d559787 Wait for native thread exit outside GUI and cover FIN drain in tests 2026-10-16 17:31:20 +02:00
evgeny 0f5a5580c5 Test publisher shutdown with GUI-dependent thread-local cleanup 2026-10-16 17:27:15 +02:00
evgeny 85101765eb Keep GUI responsive until publisher threads finish resource cleanup 2026-10-16 17:26:30 +02:00
evgeny fb2a615165 Reproduce Android audio startup deadlock with real Opus playback 2026-10-16 17:25:19 +02:00
evgeny c03fe3da71 Prime late stream audio without waiting for its playback clock 2026-10-16 17:24:25 +02:00
evgeny 0c26daf3f5 Document adaptive buffer limits and preserve failed-open backoff 2026-10-16 17:18:13 +02:00
evgeny b463fc3309 Keep adaptive requests above baseline and test backend fault events 2026-10-16 17:17:22 +02:00
evgeny e37b3be7da Defer new shared subscribers during microphone buffer resizing 2026-10-16 17:15:51 +02:00
evgeny ca09073a52 Test gradual audio buffer adaptation and nonblocking microphone stop 2026-10-16 17:13:59 +02:00
evgeny efe9e8311c Detach shared capture safely while its audio buffer is resizing 2026-10-16 17:12:51 +02:00
evgeny 3c73c7d645 Keep microphone buffer resizing outside the GUI close path 2026-10-16 17:11:57 +02:00
evgeny 0b58bf3c87 Increase audio buffer gradually after repeated backend glitches 2026-10-16 17:10:31 +02:00
evgeny 360d5dddaa Expose WASAPI and Linux audio buffer glitches to device owners 2026-10-16 17:08:55 +02:00
evgeny 38eae06161 Document microphone watchdogs and pending stream diagnostics 2026-10-16 17:04:46 +02:00
evgeny c37be1c3e8 Trace stream audio startup demand and link Windows COM cleanup 2026-10-16 16:58:01 +02:00
evgeny 014a110fd5 Verify stopping during asynchronous microphone cleanup 2026-10-16 16:53:10 +02:00
evgeny 69cee1bec4 Check continuing H265 screen video and avoid waiting on failed input 2026-10-16 16:51:43 +02:00
evgeny 2de9ac85a2 Test missing microphone PCM in calls and radio without GUI stalls 2026-10-16 16:50:44 +02:00
evgeny cb84d1049f Translate call and radio microphone timeout messages 2026-10-16 16:49:47 +02:00
evgeny 91357ebf90 Show microphone timeout errors in call and radio controls 2026-10-16 16:49:09 +02:00
evgeny 110af9b0c5 Keep normal capture lifecycle synchronous after healthy input 2026-10-16 16:49:09 +02:00
evgeny 432f2dcf00 Stop radio transmission when microphone PCM stops arriving 2026-10-16 16:48:34 +02:00
evgeny 26f6e2ee83 Disable stalled call microphones and close capture asynchronously 2026-10-16 16:48:10 +02:00
evgeny 48cb101047 Expose microphone PCM timeout to shared capture consumers 2026-10-16 16:48:10 +02:00
evgeny 05f1fe485c Trace stream video progress and detect missing microphone PCM 2026-10-16 16:47:25 +02:00
evgeny 349869f732 Record stream startup validation and Windows follow-up 2026-10-16 16:21:11 +02:00
evgeny 230687d155 Wait for asynchronous microphone release in stream tests 2026-10-16 16:19:02 +02:00
evgeny de71617e9f Initialize COM for capture cleanup and release completed leases 2026-10-16 16:19:02 +02:00
evgeny 543c9decba Exclude retiring capture sessions from new subscriptions 2026-10-16 16:15:20 +02:00
evgeny 9629e9311c Defer CPU rejection until dialog destruction completes 2026-10-16 16:15:07 +02:00
evgeny 21f4de492c Test stream startup consent and nonblocking microphone failure 2026-10-16 16:13:35 +02:00
evgeny 7a660688af Translate stream startup failures and CPU confirmation 2026-10-16 16:12:37 +02:00
evgeny 9500581e0b Gate stream encoding on microphone input and CPU consent 2026-10-16 16:11:04 +02:00
evgeny 3ba7601ba4 Close failed stream capture without blocking the GUI 2026-10-16 16:03:14 +02:00
evgeny 86da11b2e1 Separate hardware and software live encoder selection 2026-10-16 15:58:25 +02:00
evgeny 903568d9d6 Apply the selected language immediately in first-run setup 2026-10-16 15:34:31 +02:00
evgeny debfccd7fa Add language selection to the ChatGUI first-run setup 2026-10-16 15:34:20 +02:00
evgeny 91620cec4e Add nickname setup on the first ChatGUI launch 2026-10-16 15:32:33 +02:00
evgeny 5074bad57c Fix Android avatar viewer bounds around system bars 2026-10-16 15:17:12 +02:00
evgeny 0475df8412 Close Android stream viewer after playback ends 2026-10-16 15:03:31 +02:00
evgeny 7454bebb14 Verify PipeWire node addressing with both cursor modes 2026-10-16 14:51:46 +02:00
evgeny ea971b0856 Preserve packed RGB formats and fix PipeWire test setup 2026-10-16 14:50:50 +02:00
evgeny 5725cfb686 Document PipeWire dependency and translate capture status 2026-10-16 14:38:45 +02:00
evgeny 3228cce0fb Test Wayland portal lifecycle and real PipeWire video delivery 2026-10-16 14:38:28 +02:00
evgeny 44c9c3bcaf Use native portal capture for Wayland broadcasts 2026-10-16 14:35:33 +02:00
evgeny ce0fa1f063 Add portal and PipeWire capture with embedded cursor support 2026-10-16 14:34:47 +02:00
evgeny cf3c8c7c34 Test X11 cursor placement and live visibility controls 2026-10-16 14:16:12 +02:00
evgeny ae1682bbe2 Document XFixes dependency and translate cursor controls 2026-10-16 14:14:37 +02:00
evgeny 7c1955f029 Add a persistent cursor toggle to X11 broadcast controls 2026-10-16 14:14:04 +02:00
evgeny 5753177f29 Include the cursor in X11 stream frames by default 2026-10-16 14:14:03 +02:00
evgeny e24ab45725 Add native X11 cursor composition for screen frames 2026-10-16 14:13:10 +02:00
evgeny d60f8e4b1b Preserve stream capture settings and refine audio playback 2026-10-16 14:05:54 +02:00
evgeny 198ebc0acb Restore the current preview when reopening stream controls 2026-10-16 14:02:30 +02:00
evgeny 00e9ed227a Capture the selected X11 monitor through QScreen 2026-10-16 14:00:56 +02:00
evgeny ac8e701406 Keep live capture bound to the selected monitor instance 2026-10-16 13:57:57 +02:00
evgeny 3d2f90e8ae Distinguish monitors with identical Qt screen names 2026-10-16 13:56:57 +02:00
evgeny 218249f9a5 Apply measured screen geometry through the GUI configuration path 2026-10-16 13:54:40 +02:00
evgeny 9bacde3bc1 Select proportional screen and advertised camera stream modes 2026-10-16 13:53:33 +02:00
evgeny 6228873ce2 Resolve desktop capture modes from source capabilities 2026-10-16 13:51:38 +02:00
evgeny ea46c56bc1 Define source-specific desktop stream video modes 2026-10-16 13:50:03 +02:00
evgeny bb5759728a Record live SM wake and reconnect verification on colo2 2026-10-16 13:12:01 +02:00
evgeny fc526d16c0 Test stream announcements after foreground wake and packet reordering 2026-10-16 12:55:10 +02:00
evgeny 919c3d76fe Fix peer standby cancellation on normal keepalive from a new link 2026-10-16 12:46:27 +02:00
evgeny 700034eec8 Add broadcast RS settings to desktop and Android 2026-10-16 09:49:58 +02:00
evgeny 6cdc2ce4dc Allow Android audio toggles during capture startup and test native control 2026-10-16 01:56:43 +02:00
evgeny 8f71a1161b Release stream radio suppression on END and test shared PCM mute 2026-10-16 01:55:28 +02:00
evgeny 7e7d86e4d3 Add Android live stream audio switch and document radio behavior 2026-10-16 01:54:32 +02:00
evgeny e3fb8377ab Track Android stream audio state independently of microphone ownership 2026-10-16 01:53:56 +02:00
evgeny c4fc4e9b1e Mute only the Android stream PCM branch during live publication 2026-10-16 01:52:31 +02:00
evgeny 25d27bfffe Add Android native control for source audio state 2026-10-16 01:51:37 +02:00
evgeny 559d66ae20 Test stream audio toggles and viewer scoped radio suppression 2026-10-16 01:49:30 +02:00
evgeny 2a572af2bb Label stream audio controls in English and Russian 2026-10-16 01:48:50 +02:00
evgeny e47ff579b3 Keep desktop stream mute controls synchronized with publication 2026-10-16 01:48:39 +02:00
evgeny f4d4b6033e Expose stream audio control to the desktop publisher 2026-10-16 01:48:39 +02:00
evgeny c9c1bb7cf1 Publish live audio toggles through the shared stream actor 2026-10-16 01:48:07 +02:00
evgeny db7264e271 Suppress source radio playback only while viewing audible streams 2026-10-16 01:47:44 +02:00
evgeny d14f9233ed Separate effective radio silence from stored member mute 2026-10-16 01:47:11 +02:00
evgeny 0f2e81bd0f Advertise stream audio state in signed source options 2026-10-16 01:46:39 +02:00
evgeny d766a50447 Keep Android capture ownership until device release and update busy messages 2026-10-16 01:34:37 +02:00
evgeny 829e4d41fa Document shared microphone ownership and test distinct desktop inputs 2026-10-16 01:29:55 +02:00
evgeny b523282e8e Allow simultaneous stream publication and radio in Android UI 2026-10-16 01:28:59 +02:00
evgeny 2038e33d8d Reset radio detector after Android capture queue discontinuities 2026-10-16 01:21:36 +02:00
evgeny 051d453c2d Validate Android input routes and isolate capture queue diagnostics 2026-10-16 01:20:34 +02:00
evgeny 9d69cce8e8 Connect Android stream and radio to shared capture and preserve stream audio mode 2026-10-16 01:18:26 +02:00
evgeny 4a49c830d1 Add bounded Android microphone subscriptions with independent ownership 2026-10-16 01:13:23 +02:00
evgeny d4eb1da233 Test shared microphone lifecycle and stream priority 2026-10-16 01:06:59 +02:00
evgeny 95942382bc Share desktop publisher microphone with radio while preserving stream format 2026-10-16 01:06:09 +02:00
evgeny 05ed516cf5 Use shared capture subscription for desktop radio 2026-10-16 01:05:14 +02:00
evgeny 6d7b3e8e02 Add shared desktop microphone sessions with stream profile priority 2026-10-16 00:25:25 +02:00
evgeny 7c5b76b482 Add minimized Windows autostart and preserve terminal output 2026-10-15 21:27:32 +02:00
evgeny abdca1acc7 Load chat background from embedded resources 2026-10-15 20:47:51 +02:00
evgeny 3e4d5d235f Isolate video call camera capture from the GUI process 2026-10-15 20:16:59 +02:00
evgeny 0b136ae2ab Fix Windows deployment classification of AVRT.dll 2026-10-15 18:55:35 +02:00
evgeny b88d76a8ab Fix Windows stream test and compact Android call diagnostics 2026-10-15 18:35:08 +02:00
evgeny 7477da42a5 Reduce Linux voice capture latency and verify device timing 2026-10-15 17:26:09 +02:00
evgeny 3276204196 docs: record live video flush latency and remaining clock gaps 2026-10-15 16:48:04 +02:00
evgeny 612fb7816d docs: record bounded video flush lifecycle and validation 2026-10-15 16:44:57 +02:00
evgeny 9886bc2fde call: continue bounded video flush without waiting for media ticks 2026-10-15 16:38:49 +02:00
evgeny ff51e30a55 Record final video timing measurements and identify fragment budget continuation gap 2026-10-15 16:30:13 +02:00
evgeny a373180c10 Document video playout timing contract tests and remaining live cadence issues 2026-10-15 16:24:02 +02:00
evgeny 717d965fec Keep Android wake deadlines absolute and report causes of the largest video gaps 2026-10-15 16:22:18 +02:00
evgeny 0d65c5bb74 Exercise delayed receive workers UI stalls and call restart with real video decoding 2026-10-15 16:14:51 +02:00
evgeny b211da9a04 Use shared video playout on Android without blocking decoder input while waiting 2026-10-15 16:13:48 +02:00
evgeny 5255133f27 Pace desktop call video independently and coalesce immediate UI publication 2026-10-15 15:58:33 +02:00
evgeny eb43876bb6 Timestamp video availability before receive worker scheduling 2026-10-15 15:57:05 +02:00
evgeny 408c0be021 Test video cadence against data stalls scheduler delays and audio corrections 2026-10-15 15:56:31 +02:00
evgeny 7f9eef8579 Add shared absolute video playout scheduler with bounded audio phase 2026-10-15 15:54:53 +02:00
evgeny 2ff14519fa Record measured desktop Android video latency and presentation timing limits 2026-10-15 15:25:07 +02:00
evgeny 24ba454821 Trace receive decode and UI publication for the reverse video direction 2026-10-15 15:18:35 +02:00
evgeny d7788b87ac Distinguish TextureView update notifications from committed frames 2026-10-15 15:18:35 +02:00
evgeny d1dae1ac2a Measure Android video decode audio wait and surface presentation separately 2026-10-15 15:06:24 +02:00
evgeny e2102bbbcc Trace sampled video frames through desktop encoding and transport 2026-10-15 15:04:51 +02:00
evgeny 19a135d552 Document live low latency route tests and Samsung headset MMAP fallback 2026-10-15 14:58:17 +02:00
evgeny 23c8a190dd Use AudioTrack for headsets whose physical MMAP routing is unreliable 2026-10-15 14:56:24 +02:00
evgeny 29b01237aa Keep call headset hotplug automatic after selecting speaker manually 2026-10-15 14:52:41 +02:00
evgeny 8add40ec00 Reopen call output on confirmed routes and recompute AEC from physical latency 2026-10-15 14:46:03 +02:00
evgeny a165372bbe Preserve device timestamp offsets across output reopen and counter wrap 2026-10-15 14:46:03 +02:00
evgeny 3226865ad7 Test route-local retry policy and output position continuity 2026-10-15 14:42:04 +02:00
evgeny 075ffc4d3b Add bounded AAudio voice output with AudioTrack fallback 2026-10-15 14:40:41 +02:00
evgeny a3854f2836 Allow call playback to invalidate a replaced hardware clock 2026-10-15 14:36:25 +02:00
evgeny f836ba67ce Expose device clock suspension without resetting jitter buffer 2026-10-15 14:36:25 +02:00
evgeny 0a56c01e29 Suspend hardware clock during output replacement without resetting media 2026-10-15 14:36:03 +02:00
evgeny ede911e7e4 Use bounded AAudio write pacing in probe and trace audio controls 2026-10-15 14:36:00 +02:00
evgeny 7a6ecf700c Trace headset endpoint changes and bound AAudio probe queue 2026-10-15 14:30:59 +02:00
evgeny 9d422dd798 Probe AAudio nonblocking writes alongside callback output 2026-10-15 14:27:21 +02:00
evgeny 43522936ff Record Android low latency output probe and route recovery results 2026-10-15 14:23:23 +02:00
evgeny e68df70aad Extend audio probe with route roundtrip and 40 ms control buffer 2026-10-15 14:19:03 +02:00
evgeny 11a3a20655 Add isolated Android AudioTrack and AAudio voice output probe 2026-10-15 14:14:48 +02:00
evgeny 9d948d399e Record live call results and Samsung voice output FAST rejection 2026-10-15 14:05:57 +02:00
evgeny 54e672420c Record measured output latency and validation of low-latency call output 2026-10-15 14:02:03 +02:00
evgeny 1a97219c43 Invalidate output measurements across concurrent Android route changes 2026-10-15 13:59:54 +02:00
evgeny b3ed8b06e7 Back off PCM reserve probes after starvation and exclude output pauses 2026-10-15 13:59:01 +02:00
evgeny 9c71115363 Localize output latency metrics and synchronize worker assertion 2026-10-15 13:57:41 +02:00
evgeny e1bb12d16a Expose desktop output latency and verify hardware clock transitions 2026-10-15 13:57:03 +02:00
evgeny 3083cc05dc Account for complete output callbacks and test adaptive PCM reserve 2026-10-15 13:56:38 +02:00
evgeny cfa3ed1f61 Adapt desktop call PCM reserve to output period and underruns 2026-10-15 13:55:49 +02:00
evgeny cb9852e8d5 Use Android low-latency output and presentation timestamps for call clocks 2026-10-15 13:55:49 +02:00
evgeny b6b0726bfe Document shared delay processing and verified frame alignment 2026-10-15 13:36:18 +02:00
evgeny c244808b77 Support Android C++11 in shared delay processor 2026-10-15 13:24:04 +02:00
evgeny b7b043777b Exercise network recovery and changing device block sizes 2026-10-15 13:23:28 +02:00
evgeny 26c91e6295 Test phase alignment after device silence and keep diagnostics current 2026-10-15 13:22:24 +02:00
evgeny 6896038f30 Include device silence in delay control timing 2026-10-15 13:20:50 +02:00
evgeny 22a03c5880 Report PCM mode, exact tempo and frame phase in call diagnostics 2026-10-15 13:19:39 +02:00
evgeny c1cc3c459b Specify jitter reserve at startup and expose delay diagnostics 2026-10-15 13:19:39 +02:00
evgeny aaff1a5494 Integrate delay processing with call jitter and verify PCM handoffs 2026-10-15 13:18:18 +02:00
evgeny 5f9262dd9a Register shared delay processor and its test target 2026-10-15 13:14:30 +02:00
evgeny 3f13860eac Align direct playback to source frames using bounded acceleration 2026-10-15 13:14:30 +02:00
evgeny 962067c2ac Add shared PCM delay processor with lossless mode boundaries 2026-10-15 13:10:45 +02:00
evgeny ff16adda17 Expose SoundTouch starvation with a 20 ms jitter reserve 2026-10-15 12:39:00 +02:00
evgeny f1aec9bb2d Lower shared call jitter reserve floor to 20 ms 2026-10-15 12:36:54 +02:00
evgeny 39c5274819 Record call teardown regression coverage and verified builds 2026-10-15 12:28:54 +02:00
evgeny 92f62a0121 Validate terminal call packets and document end acknowledgements 2026-10-15 12:26:40 +02:00
evgeny e6954b4152 Acknowledge call termination across router epoch changes 2026-10-15 12:24:55 +02:00
evgeny 3f41676a3d Define acknowledged call termination and enable route tracing 2026-10-15 12:18:47 +02:00
evgeny 59fb020711 Cancel stale video port commands and expose hangup regression 2026-10-15 12:15:07 +02:00
evgeny 1b07a8a708 Keep call metrics visible outside settings on both clients 2026-10-15 12:06:27 +02:00
evgeny 7d75afd4c5 Fix false queue-full detection in audio diagnostics 2026-10-15 11:52:07 +02:00
evgeny 233056a83a Document call latency measurements and capture period 2026-10-15 11:41:51 +02:00
evgeny 3c750d018c Measure Android capture age and device startup stages 2026-10-15 11:40:43 +02:00
evgeny 4cc7d5eaf4 Report call audio processing and send queue timing 2026-10-15 11:38:47 +02:00
evgeny 4476e875ab Measure call audio processing and send queue residence 2026-10-15 11:38:36 +02:00
evgeny dba813e8c5 Report desktop voice queue and capture processing latency 2026-10-15 11:36:58 +02:00
evgeny 96725caf03 Request 20 ms call capture and test output latency reports 2026-10-15 11:35:53 +02:00
evgeny 378d4103a8 Measure backend playback latency outside audio callbacks 2026-10-15 11:35:26 +02:00
evgeny 91030de5ea Cover immediate buffer probe failures and document tuning 2026-10-15 11:11:45 +02:00
evgeny 50bcf5539a Adapt Android call output buffer to device underruns 2026-10-15 11:10:08 +02:00
evgeny cbc7b296bb Add tested adaptive playback device buffer policy 2026-10-15 11:07:39 +02:00
evgeny 92a942ea8b Document continuous capture timing and device queue tuning scope 2026-10-15 03:37:45 +02:00
evgeny 80f6997e1b Keep capture PCM timestamps continuous across backend timing updates 2026-10-15 03:35:59 +02:00
evgeny 6765cf186d Handle headset routing and cancel pending endpoint requests on shutdown 2026-10-15 03:16:03 +02:00
evgeny d944418676 Test capture clock diagnostics and document routing ownership 2026-10-15 03:13:19 +02:00
evgeny 1da19276e5 Trace raw capture timing and PulseAudio latency without callback logging 2026-10-15 03:12:16 +02:00
evgeny a7c67ee488 Activate Telecom before call audio and adopt late connections 2026-10-15 03:11:45 +02:00
evgeny 9c4219d837 Use Telecom as the sole route owner for Android calls 2026-10-15 03:11:05 +02:00
evgeny a439b222da Bound Android call AudioTrack working buffer and pace PCM by device 2026-10-15 03:04:43 +02:00
evgeny a82c2ede6a Clarify audio diagnostic timing and reporter lifetime 2026-10-15 02:58:42 +02:00
evgeny b51e2c0817 Move AudioTrack diagnostic queries off playback and time operations separately 2026-10-15 02:58:42 +02:00
evgeny ac2029c82b Document audio trace fields and verify diagnostic reads preserve jitter events 2026-10-15 02:49:01 +02:00
evgeny c5efa56939 Move repetitive successful video key validation logs to trace 2026-10-15 02:47:45 +02:00
evgeny b4092d1bd6 Record bounded call audio traces and report event windows off playback thread 2026-10-15 02:47:33 +02:00
evgeny 39dbfa7673 Expose filtered Android call buffer diagnostics over JNI 2026-10-15 02:46:15 +02:00
evgeny f341d1db4b Provide call audio diagnostics without consuming stats events 2026-10-15 02:44:35 +02:00
evgeny e53ea2e04a Expose non-destructive jitter buffer diagnostic snapshots 2026-10-15 02:44:10 +02:00
evgeny d1cbb64884 Reduce Android call playback startup buffering and log output queue 2026-10-15 02:37:15 +02:00
evgeny 4c2d8aa4cc Prevent delayed camera startup from shifting video into the future 2026-10-15 02:32:35 +02:00
evgeny ac3801761e Keep video latency diagnostics within their clock domains 2026-10-15 02:29:34 +02:00
evgeny 15e4fcb710 Measure capture clock offset and video audio-barrier latency 2026-10-15 02:29:09 +02:00
evgeny 4447f25e37 Record live camera FPS diagnosis and guard ownership 2026-10-15 02:21:16 +02:00
evgeny f84b24806e Keep UVC camera frame rate stable during video calls 2026-10-15 02:20:32 +02:00
evgeny 459b1ecc01 Display every new desktop call preview frame 2026-10-15 02:18:54 +02:00
evgeny 55293483da Document video timeout diagnosis and independent Android rotation 2026-10-15 02:14:14 +02:00
evgeny fc0559c974 Preserve fractional frame budget when downsampling cameras 2026-10-15 02:13:42 +02:00
evgeny f8e960c844 Retain jitter tolerance across desktop camera phase drift 2026-10-15 02:12:16 +02:00
evgeny 5666da8859 Bound Android bitrate wait by requested keyframe cadence 2026-10-15 02:11:45 +02:00
evgeny 0c214dfca0 Rotate call video independently of Android call controls 2026-10-15 02:11:23 +02:00
evgeny 78246e249e Document measured call FPS loss and bounded audio synchronization queue 2026-10-15 02:07:04 +02:00
evgeny fe2ccb07e1 Keep 60 fps downsampling away from half-period ties 2026-10-15 02:05:29 +02:00
evgeny ee9af6533d Avoid accumulated rounding in call frame pacing 2026-10-15 02:05:08 +02:00
evgeny f3eed42c3f Preserve desktop camera cadence under timestamp jitter 2026-10-15 02:04:26 +02:00
evgeny 209f2ceb7c Queue video frames while awaiting matching audio timestamps 2026-10-15 02:04:25 +02:00
evgeny 95ab911caf Track desktop video frame flow and update call preview geometry 2026-10-15 01:59:59 +02:00
evgeny b665191b9e Render Android call camera preview directly through Camera2 2026-10-15 01:59:42 +02:00
evgeny 572392511b Measure call video throughput and audio gating losses 2026-10-15 01:58:25 +02:00
evgeny 4f91f575a1 Document Android call video navigation and preview limits 2026-10-15 01:53:47 +02:00
evgeny 5cd2df9125 Opt in to stable system bar inset measurements 2026-10-15 01:53:12 +02:00
evgeny 964c17f1bf Add Android call controls navigation and expandable video thumbnails 2026-10-15 01:52:38 +02:00
evgeny 64f1fe4dfb Add call video navigation labels 2026-10-15 01:51:35 +02:00
evgeny 129f809b7b Ignore stale proximity events after listener replacement 2026-10-15 01:44:08 +02:00
evgeny e7ff764ed0 Document video orientation and proximity-gated speaker activation 2026-10-15 01:43:24 +02:00
evgeny f61e1c0a9f Defer video speaker activation until the phone is away from the ear 2026-10-15 01:42:17 +02:00
evgeny 1b3d9ff8ee Distinguish unknown proximity from confirmed far before speaker changes 2026-10-15 01:42:17 +02:00
evgeny 97e3b3e9ef Match Android call screen orientation to the received video 2026-10-15 01:39:08 +02:00
evgeny a58cfcedc6 Document separate video and call controls windows 2026-10-15 01:37:11 +02:00
evgeny 170a6b0d57 Translate separate call video window actions 2026-10-15 01:36:33 +02:00
evgeny 9f8fb64e6e Show call video in a separate resizable desktop window 2026-10-15 01:36:14 +02:00
evgeny 401ca96d7d Record hardware bitrate verification and final call video checks 2026-10-15 01:31:40 +02:00
evgeny e1b29af8ce Keep queued video frames during in-place bitrate changes 2026-10-15 01:30:55 +02:00
evgeny 1376f9f059 Validate bitrate ceilings and recovery without requiring filler data 2026-10-15 01:29:20 +02:00
evgeny eeb3ca5927 Avoid uncached per-pixel chroma writes in codec probe 2026-10-15 01:26:35 +02:00
evgeny 5e22da3098 Document call video layout and clock discontinuity fixes 2026-10-15 01:25:22 +02:00
evgeny 13feaca674 Use bounded contrast and bulk YUV writes in hardware rate probe 2026-10-15 01:23:34 +02:00
evgeny 607952b709 Verify hardware rate changes on continuous noisy video 2026-10-15 01:14:27 +02:00
evgeny 1b0b429835 Exercise asynchronous rate changes with continuous video output 2026-10-15 01:13:54 +02:00
evgeny 0dec628e49 Prepare call video bitrate encoders without pausing capture 2026-10-15 01:13:36 +02:00
evgeny 152d89f0dc Apply Android call bitrate changes in place on validated keyframes 2026-10-15 01:11:02 +02:00
evgeny 8582d2e2b0 Log call video surface lifetime and preview visibility 2026-10-15 00:50:36 +02:00
evgeny 5c6f71c602 Keep Camera2 frames when timestamp jitter crosses nominal frame duration 2026-10-15 00:49:52 +02:00
evgeny 6ff21c12ef Regress video playback across repeated audio clock epoch changes 2026-10-15 00:49:08 +02:00
evgeny b1390a9542 Ignore stale Android call surfaces during activity replacement 2026-10-15 00:48:12 +02:00
evgeny 1c5ef12c8f Keep call display format internal to the Android application 2026-10-15 00:47:42 +02:00
evgeny b6961f5fbc Localize Android call video settings 2026-10-15 00:46:38 +02:00
evgeny 2db038beee Separate Android full screen call video from controls and settings 2026-10-15 00:46:26 +02:00
evgeny 898154b2b6 Keep desktop video in a resizable window and retain decoder on audio clock updates 2026-10-15 00:45:22 +02:00
evgeny 21c22bd911 Preserve Android video decoder across audio clock corrections and expose previews 2026-10-15 00:44:45 +02:00
evgeny a0db643acb Place camera thumbnail inside remote picture and document call layout 2026-10-15 00:34:07 +02:00
evgeny 87a2075dec Keep call diagnostics in settings and translate video controls 2026-10-15 00:32:40 +02:00
evgeny 434b59b1c7 Show remote calls full screen with camera picture in picture 2026-10-15 00:31:50 +02:00
evgeny 4a7d64b2aa Expose call camera preview and clear inactive remote video 2026-10-15 00:31:03 +02:00
evgeny 21ab48b6c3 Document desktop call encoder locale regression and validation 2026-10-15 00:07:07 +02:00
evgeny ef397a6b48 Fix call encoder levels under comma decimal locales 2026-10-15 00:06:20 +02:00
evgeny faee775dff Record the incomplete cross-device AVC decoder probe accurately 2026-10-14 23:45:34 +02:00
evgeny 8b4b278f3c Document the corrected video contract and verified hardware matrix 2026-10-14 23:45:01 +02:00
evgeny 61bcf28720 Verify Android hardware bitstreams through the production Qt decoder 2026-10-14 23:40:48 +02:00
evgeny 5d115c3dac Add cross-device decoding to the explicit debug video probe 2026-10-14 23:40:47 +02:00
evgeny 6910e07c31 Surface failed video configuration and camera capture stalls 2026-10-14 23:40:01 +02:00
evgeny 5f16c79559 Preserve bitrate bounds on format changes and discard late desktop pictures 2026-10-14 23:19:18 +02:00
evgeny 047927a7a5 Check VPS HRD and log actual codec constraints on rejection 2026-10-14 23:19:18 +02:00
evgeny 005d891fb4 Bound video send debt and test decoder acceptance and rejection 2026-10-14 23:18:40 +02:00
evgeny 634ef222a4 Fallback desktop video on decoder rejection and verify receive-only codecs 2026-10-14 23:17:25 +02:00
evgeny f47b7167a2 Fallback Android video after explicit decoder rejection 2026-10-14 23:16:55 +02:00
evgeny 8703e2678a Expose remote video decoder acceptance to Android workers 2026-10-14 23:16:55 +02:00
evgeny afe9437c49 Deliver decoder results through the thread-safe call video port 2026-10-14 23:16:20 +02:00
evgeny d32c8ed6e3 Report decoder acceptance separately from keyframe recovery 2026-10-14 23:16:20 +02:00
evgeny 31a0c4f1a3 Validate HEVC progressive VUI and signaled HRD limits 2026-10-14 23:15:12 +02:00
evgeny aad77cf415 Share normative HRD limits with the bitstream validator 2026-10-14 23:15:11 +02:00
evgeny ea5ef4abc2 Probe hardware rate transitions and clear stale call port commands 2026-10-14 23:13:45 +02:00
evgeny 96e8f6c0ac Test real decoder fixtures and malformed video parameter sets 2026-10-14 23:13:25 +02:00
evgeny 2f1de49e87 Negotiate actual Android modes and replace single encoder on IDR 2026-10-14 23:13:01 +02:00
evgeny fe16509293 Advertise desktop decoder capabilities independently of encoders 2026-10-14 23:12:15 +02:00
evgeny 47cca28d39 Probe Android encoders and decoders independently with actual formats 2026-10-14 23:12:00 +02:00
evgeny 4eece00a2d Add independent AVC and HEVC decoder probe bitstreams 2026-10-14 23:11:00 +02:00
evgeny 5e15fc0d3f Expose common bitstream inspection and actual format checks to Android 2026-10-14 23:10:12 +02:00
evgeny 68e5905d9e Adapt call transport tests and desktop setup to profile negotiation 2026-10-14 23:09:51 +02:00
evgeny 3063270a67 Cover directional profiles and normative DPB limits in video tests 2026-10-14 23:09:30 +02:00
evgeny c7184b55d8 Validate desktop call IDRs with the common bitstream contract 2026-10-14 23:08:47 +02:00
evgeny ce2560ef4b Wire shared video validator and log negotiated profiles 2026-10-14 23:08:46 +02:00
evgeny 53c6437301 Add shared video parameter set and independent IDR validation 2026-10-14 23:08:10 +02:00
evgeny 520f7a29a7 Separate video profiles and network bitrate from encoder VBV 2026-10-14 23:06:40 +02:00
evgeny a06bbc34df Record platform video integration and verified hardware contract blockers 2026-10-14 22:52:15 +02:00
evgeny b2c6500fea Test platform video port over a real bidirectional call transport 2026-10-14 22:50:25 +02:00
evgeny b01d54a431 Request constrained AVC explicitly and diagnose hardware SPS limits 2026-10-14 22:49:31 +02:00
evgeny 156b250432 Exercise production call decoder across real bitrate transitions 2026-10-14 22:46:18 +02:00
evgeny 9becdac331 Add explicit device instrumentation for call codec verification 2026-10-14 22:46:10 +02:00
evgeny ba2a66341a Add Android call video display and live camera codec bitrate controls 2026-10-14 22:45:34 +02:00
evgeny a59ebcd6db Own Android call video in the foreground call service 2026-10-14 22:45:34 +02:00
evgeny 04f4d29e4f Add Android call camera and keyframe encoder replacement worker 2026-10-14 22:44:32 +02:00
evgeny edf7e4d2c5 Render Android call video against the audio playback clock 2026-10-14 22:43:12 +02:00
evgeny 5dbc5bcf59 Probe Android call encoders through real YUV encode and decode 2026-10-14 22:42:16 +02:00
evgeny c986221cb6 Expose shared video negotiation and bounded queues to Android JNI 2026-10-14 22:40:48 +02:00
evgeny d01bc395fa Use current FFmpeg profile constants and qualify video port status 2026-10-14 22:39:56 +02:00
evgeny af35d03399 Add live call video controls and display to the desktop call window 2026-10-14 22:39:13 +02:00
evgeny e682f30ca9 Expose active call bitrate range through the shared video port 2026-10-14 22:38:39 +02:00
evgeny cabae34791 Connect desktop camera workers to negotiated call video and audio clock 2026-10-14 22:37:50 +02:00
evgeny b091aa0df2 Decode call video with bounded input and verified output metadata 2026-10-14 22:36:32 +02:00
evgeny 57a59df983 Bind shared call video port to desktop and Android core lifetime 2026-10-14 22:36:03 +02:00
evgeny 5e37aaa2fb Add bounded cross-thread video port for platform call workers 2026-10-14 22:35:25 +02:00
evgeny 65d7ec875e Record final codec envelope builds and regression results 2026-10-14 22:27:48 +02:00
evgeny 78d3511174 Document negotiated video envelopes and remaining platform integration 2026-10-14 22:27:16 +02:00
evgeny f055f8a9df Use fresh capture PTS after discarded video and diagnose rejected frames 2026-10-14 22:22:02 +02:00
evgeny 8c12dfc02f Verify pending video is discarded when peer capabilities shrink 2026-10-14 22:14:30 +02:00
evgeny ea65d87ec1 Round-trip real codecs using the negotiated level and reject invalid envelopes 2026-10-14 22:10:42 +02:00
evgeny 3f61549769 Open desktop call encoders from the shared negotiated video format 2026-10-14 22:10:21 +02:00
evgeny 534f319d86 Test directional codec envelopes and reject out-of-cap video in live calls 2026-10-14 22:08:48 +02:00
evgeny fa53139163 Bind call rate control and frame admission to negotiated codec envelopes 2026-10-14 22:07:04 +02:00
evgeny d4e6bc5980 Negotiate call video level and bitrate VBV envelopes in the shared protocol 2026-10-14 22:05:55 +02:00
evgeny 2a22d625e1 Record validated desktop call encoder and remaining integration boundaries 2026-10-14 22:01:47 +02:00
evgeny cab992228c Preserve explicit VBV and report inactive call encoder state 2026-10-14 21:57:58 +02:00
evgeny 0f570edbd0 Exercise real H264 and H265 calls across delayed bitrate switches 2026-10-14 21:55:11 +02:00
evgeny 327478475c Bound encoder rate requests by the negotiated mode ceiling 2026-10-14 21:46:53 +02:00
evgeny 3b3ba3c495 Add verified call profiles and defer bitrate replacement until IDR 2026-10-14 21:46:13 +02:00
evgeny 43a90ff23b Build shared live video library independently from stream publication 2026-10-14 21:44:16 +02:00
evgeny 45720ebaba Use shared live encoder types in stream publication 2026-10-14 21:44:16 +02:00
evgeny dca1adc173 Extract live video encoder naming for reuse by calls and broadcasts 2026-10-14 21:43:54 +02:00
evgeny f89494fb4f Specify deferred bitrate changes and validate IDR before releasing old encoder 2026-10-14 21:39:24 +02:00
evgeny abf7ccf566 Probe x265 bitrate switching at scheduled IDR with encoder replacement 2026-10-14 21:38:05 +02:00
evgeny e20f4113a4 Record x265 reconfiguration defect and validated dependency patch 2026-10-14 21:33:41 +02:00
evgeny 5d8c6b8ff3 Add measured x265 bitrate adaptation probe for call backends 2026-10-14 21:32:02 +02:00
evgeny 4b414deb6e Record successful bitrate range regression checks 2026-10-14 21:21:21 +02:00
evgeny a6b8594219 Document call bitrate range settings and validation 2026-10-14 21:19:11 +02:00
evgeny 6980bc7a88 Expose the adaptive call video range in Android audio settings 2026-10-14 21:17:46 +02:00
evgeny cc341221d4 Verify live bitrate range changes and incompatible codec limits 2026-10-14 21:10:12 +02:00
evgeny f04e3fd995 Persist the Android call video bitrate range atomically 2026-10-14 21:10:12 +02:00
evgeny 6cbb13d11d Translate call bitrate range settings 2026-10-14 21:08:13 +02:00
evgeny 0fe9a779f0 Add call video bitrate range controls to desktop settings 2026-10-14 21:07:50 +02:00
evgeny 03d8f71be4 Test video adaptation from poor connectivity to the 4 Mbps ceiling 2026-10-14 21:07:08 +02:00
evgeny 4abc5ef6e7 Apply live call bitrate range within the codec ceiling 2026-10-14 21:06:26 +02:00
evgeny 5789a83ea2 Define atomic call video bitrate range with 300–4000 kbps defaults 2026-10-14 21:06:07 +02:00
evgeny 6b409a5006 Record video transport validation and codec negotiation gap 2026-10-14 20:59:51 +02:00
evgeny ed848f94eb Bound video encoder commands and verify generation-safe feedback 2026-10-14 20:53:53 +02:00
evgeny 23bd954b79 Drive call video rate control from endpoint delivery snapshots 2026-10-14 20:52:41 +02:00
evgeny 2c9904c345 Handle video capability exchange racing with call acceptance 2026-10-14 20:50:32 +02:00
evgeny 996d4a89f5 Exercise video switching and backpressure in a real call 2026-10-14 20:49:54 +02:00
evgeny b466ec30e3 Acknowledge video capabilities before sending frame data 2026-10-14 20:48:58 +02:00
evgeny 609221e384 Separate call audio and video from signaling queues 2026-10-14 20:48:27 +02:00
evgeny 14a56d9b33 Add bounded call video transport and capability exchange 2026-10-14 20:47:44 +02:00
evgeny dffde9691d Share call session ownership with the video transport 2026-10-14 20:46:02 +02:00
evgeny b00c6a2b6d Test call video negotiation and frame wire boundaries 2026-10-14 20:44:42 +02:00
evgeny c8bbce6a94 Define call video capabilities and bounded frame assembly 2026-10-14 20:43:42 +02:00
evgeny 5fbea21482 Record call clock validation results 2026-10-14 19:42:56 +02:00
evgeny 120a865152 Record the implemented call timing protocol and next stages 2026-10-14 19:42:32 +02:00
evgeny 610773fa90 Document and log the call source clock contract 2026-10-14 19:38:59 +02:00
evgeny 4a55c30f95 Verify capture clock metadata over the call transport 2026-10-14 19:37:25 +02:00
evgeny a978bb7b06 Exercise call clocks through Opus and jitter recovery 2026-10-14 19:36:51 +02:00
evgeny e975d24a73 Track Android call capture and playback clocks 2026-10-14 19:36:11 +02:00
evgeny 023f3a335e Pass call audio timing across Android JNI 2026-10-14 19:36:11 +02:00
evgeny 9fe5cd4c27 Test call capture timestamps and device progress 2026-10-14 19:35:56 +02:00
evgeny c041882f5f Observe capture timestamps and device progress in voice tests 2026-10-14 19:30:28 +02:00
evgeny 0195732b4e Keep call audio regressions explicit about untimed sources 2026-10-14 19:29:05 +02:00
evgeny 2f3eb66c4c Preserve measured versus estimated device timing in voice mixer 2026-10-14 19:29:04 +02:00
evgeny 39bfedca86 Drive desktop call clock from rendered PCM and capture timing 2026-10-14 19:28:38 +02:00
evgeny 7bac999d93 Handle source discontinuities and reject stale device epochs 2026-10-14 19:27:54 +02:00
evgeny 9aba8e278c Preserve capture PTS and expose call playback clock to platforms 2026-10-14 19:27:03 +02:00
evgeny ab283eebdc Expose measured Opus codec delay for capture clock mapping 2026-10-14 19:26:09 +02:00
evgeny 96e7983128 Attach source PTS maps to call jitter and playback epochs 2026-10-14 19:25:30 +02:00
evgeny dc3cf4f227 Update call transport tests for explicit capture metadata 2026-10-14 19:24:51 +02:00
evgeny 2e1908a52b Preserve capture clock across call delivery and validate media peer 2026-10-14 19:24:51 +02:00
evgeny ab04b14237 Carry capture timestamps and Opus delay in call media protocol 2026-10-14 19:24:22 +02:00
evgeny fe2fa73cc0 Test audio timeline boundaries and sustained SoundTouch playback 2026-10-14 19:23:40 +02:00
evgeny 991d6bad43 Map produced audio to source PTS and bounded device progress 2026-10-14 19:22:32 +02:00
evgeny b5bd45c4da Define bounded audio timeline and playback clock contracts 2026-10-14 19:21:20 +02:00
evgeny 4939ee7fb0 Account for transposer prefill in source coordinates 2026-10-14 19:19:50 +02:00
evgeny 811b980883 Verify exact SoundTouch provenance and unchanged PCM output 2026-10-14 19:17:46 +02:00
evgeny 1d43578335 Expose optional live source tracking through SoundTouch 2026-10-14 19:16:45 +02:00
evgeny edcd34d942 Report copied and overlapping source ranges from TDStretch 2026-10-14 19:16:03 +02:00
evgeny ceb06bafcf Define SoundTouch source spans and unity interpolation contract 2026-10-14 19:15:43 +02:00
evgeny 183e66e97d Record video foundation checks and unresolved audio clock contract 2026-10-14 18:46:57 +02:00
evgeny d441d9634d Document measured SoundTouch clock drift before video integration 2026-10-14 18:44:15 +02:00
evgeny 05d9e3f22e Check SoundTouch source time against identifiable audio samples 2026-10-14 18:42:46 +02:00
evgeny 801050bacb Exercise video rate feedback with simulated channel changes 2026-10-14 18:41:33 +02:00
evgeny f24a8ca6d3 Add video bitrate regulator with bounded debt feedback 2026-10-14 18:40:35 +02:00
evgeny 0bbd4d6c34 Verify router ACK accounting across retries and epoch resets 2026-10-14 18:34:33 +02:00
evgeny 6a1e7a90dd Record video call implementation contracts and milestones 2026-10-14 18:33:42 +02:00
evgeny 859bb733af Build and test router delivery history boundaries 2026-10-14 18:33:05 +02:00
evgeny f87c6a93bc Expose per-service payload backlog and acknowledged delivery 2026-10-14 18:32:28 +02:00
evgeny 61faca84d7 Add aligned router delivery metering for video adaptation 2026-10-14 18:31:52 +02:00
evgeny 0de5479e8e Document live zoom validation and intermittent STCP test failure 2026-10-14 17:44:51 +02:00
evgeny 8eef506eed Enable pinch zoom and bounded panning in Android live viewer 2026-10-14 17:41:12 +02:00
evgeny c39610f079 Share video zoom geometry between Android viewers 2026-10-14 17:36:38 +02:00
evgeny 7ff95623c1 Document fullscreen live viewer behavior and SM checks 2026-10-14 17:31:08 +02:00
evgeny 931631a4a9 Show Android live streams fullscreen with automatic orientation and timed controls 2026-10-14 17:28:16 +02:00
evgeny d912d53979 Record SM audio playback validation and native snapshot test flake 2026-10-14 17:21:10 +02:00
evgeny ded0d401c7 Match stream audio queue to device buffer and document playback ownership 2026-10-14 17:19:04 +02:00
evgeny 0fdc6e435b Fix Android stream audio backpressure without blocking video 2026-10-14 17:16:45 +02:00
evgeny 91d1a7994e Record stream role lookup measurements and architecture decisions 2026-10-14 16:52:31 +02:00
evgeny a4f508feed Read stream supernode role directly by member ID 2026-10-14 16:49:40 +02:00
evgeny d2f8e57118 Record keyframe recovery fixes and remaining architecture review 2026-10-14 16:34:30 +02:00
evgeny 7dd1176545 Document complete keyframe admission and shared media opening 2026-10-14 16:32:15 +02:00
evgeny 97fea6e809 Use shared media opening in desktop and Android viewers 2026-10-14 16:30:38 +02:00
evgeny 1809416dce Add atomic media reader opening with selected epoch validation 2026-10-14 16:26:41 +02:00
evgeny d75aea1a09 Remove unused media receiver and test the shared ring cursor 2026-10-14 16:24:32 +02:00
evgeny d209ab94ba Gate media cursor revision changes on a complete keyframe 2026-10-14 16:22:15 +02:00
evgeny afa8fd307a Reproduce media cursor revision admission inconsistency 2026-10-14 16:10:26 +02:00
evgeny 84472cc276 Clarify stream bootstrap and log bounded publisher drain 2026-10-14 16:05:17 +02:00
evgeny d0ddc9ae8c Fix partial stream bootstrap and release drained upstream flows 2026-10-14 16:02:24 +02:00
evgeny 1815de059e Record direct preview verification and camera teardown ownership 2026-10-14 15:58:31 +02:00
evgeny 12ad2bf14b Release all preview session outputs when the camera device closes 2026-10-14 15:44:08 +02:00
evgeny 117087d56b Distinguish camera preview failure from a stopped stream 2026-10-14 15:43:24 +02:00
evgeny 2ee3103c8c Avoid double sensor rotation in direct Android camera preview 2026-10-14 15:43:24 +02:00
evgeny b8a2d7a555 Detach direct camera preview immediately when UI goes to background 2026-10-14 15:30:03 +02:00
evgeny 78054b2c1d Measure direct preview frame age using camera timestamp clocks 2026-10-14 15:19:49 +02:00
evgeny 30dae7d7ed Document direct Android camera preview and surface ownership 2026-10-14 15:14:42 +02:00
evgeny 548e64c963 Use direct camera preview instead of a local stream decoder 2026-10-14 14:58:03 +02:00
evgeny e8a63ec40d Send Android camera frames directly to preview surfaces 2026-10-14 14:58:03 +02:00
evgeny 5630bedc51 Record verified AV1 playback fix and regression coverage 2026-10-14 14:25:20 +02:00
evgeny 9ef18b6324 Release Android inflight state at decoder EOS before audio tail 2026-10-14 14:17:35 +02:00
evgeny 7b4dfc062a Document demand driven playback and delayed decoder lifecycle 2026-10-14 14:10:36 +02:00
evgeny 8d0504c759 Exercise codec output delay explicitly in the desktop fixture 2026-10-14 14:04:45 +02:00
evgeny 5ce2877fc0 Bound desktop codec work and drain delayed frames through EOF 2026-10-14 13:57:58 +02:00
evgeny c02222610b Preserve observed decoder lead while its pipeline drains 2026-10-14 13:49:57 +02:00
evgeny 360a6efafc Verify outstanding read replies and explicit video demand 2026-10-14 13:49:14 +02:00
evgeny 74c96ba5e9 Let desktop codecs read ahead while keeping presentation bounded 2026-10-14 13:43:23 +02:00
evgeny 049480408a Test frozen clock decoding and single Android read responses 2026-10-14 13:37:43 +02:00
evgeny 31b10435d3 Use explicit video demand in ring and network reader tests 2026-10-14 13:36:38 +02:00
evgeny 9331c26b22 Distinguish Android decoder startup from waiting for a keyframe 2026-10-14 13:36:38 +02:00
evgeny f72e2fca94 Drive Android decoding by codec readiness and bounded working frames 2026-10-14 13:36:38 +02:00
evgeny 223c9a153c Request the next video frame through JNI without a time horizon 2026-10-14 13:36:38 +02:00
evgeny 5a3999fbdb Prepare one Android media response per player request 2026-10-14 13:36:38 +02:00
evgeny ac339bb61f Prepare one desktop media response per player request 2026-10-14 13:27:10 +02:00
evgeny 33f8dacbfc Expose demand driven video reads through the stream handle 2026-10-14 13:25:50 +02:00
evgeny f317d3e989 Read the next live video frame independently of presentation time 2026-10-14 13:25:50 +02:00
evgeny 47119c73a4 Record completed desktop broadcast implementation and checks 2026-10-14 13:00:15 +02:00
evgeny 4e7b8f5c1a Keep desktop stream failure visible after draining 2026-10-14 12:58:09 +02:00
evgeny c3512b787e Test fatal desktop audio failure after successful capture 2026-10-14 12:56:49 +02:00
evgeny 470001bbab End desktop publication on fatal audio worker failure 2026-10-14 12:56:26 +02:00
evgeny b25070acbd Document shared live publisher and desktop capture lifecycle 2026-10-14 12:54:44 +02:00
evgeny e99a1b9450 Verify live desktop codec changes preserve the audio epoch 2026-10-14 12:53:24 +02:00
evgeny b5f84d4a9b Include complete video frame type in the raw preview integration test 2026-10-14 12:45:44 +02:00
evgeny 16dc3a13aa Verify live bitrate decoder continuity and capture installer completeness 2026-10-14 12:41:56 +02:00
evgeny 8fda2b0751 Format the isolated encoder capability and live setup passes 2026-10-14 12:38:41 +02:00
evgeny 7ad0aa61af Update live x264 bitrate and exclude encoders without forced key support 2026-10-14 12:32:33 +02:00
evgeny e4f9a48c2c Reuse static screen frames across encoder changes and test rollback 2026-10-14 12:27:50 +02:00
evgeny c08415a255 Translate capture termination and frame conversion errors 2026-10-14 12:11:36 +02:00
evgeny 2caa9504c9 Report capture termination and preserve explicit microphone selection 2026-10-14 12:11:36 +02:00
evgeny ee54626de9 Describe own stream management and system screen selection 2026-10-14 12:08:01 +02:00
evgeny 80bd17a28e Open own broadcast controls and keep rejected voice recording idle 2026-10-14 12:08:01 +02:00
evgeny ba17f9908d Render raw preview at 30 fps and preserve fallback audio sample timing 2026-10-14 12:06:45 +02:00
evgeny 27f3726124 Inspect capture plugin dependencies and recognize XWayland sessions 2026-10-14 11:49:04 +02:00
evgeny 97c460e2f1 Observe capture activation raw frames and encoder generations 2026-10-14 11:44:11 +02:00
evgeny 945c75ea07 Install capture dependencies and verify Multimedia plugin deployment 2026-10-14 11:26:50 +02:00
evgeny 7f7bb1c939 Verify forced keyframes and isolate encoder probes from live timestamps 2026-10-14 11:25:38 +02:00
evgeny 7720b7036d Deploy Qt Multimedia dependencies with Windows broadcasts 2026-10-14 11:21:48 +02:00
evgeny 1b85406b13 Trace encoder responses to explicit random access frame requests 2026-10-14 11:21:11 +02:00
evgeny f85f45fbd3 Validate publisher lifecycle in optional codec builds 2026-10-14 10:21:40 +02:00
evgeny 4a4397ace1 Start portal encoder deadlines after the first permitted capture frame 2026-10-14 10:20:50 +02:00
evgeny 240568870a Test desktop codec round trips and real screen publication 2026-10-14 10:19:44 +02:00
evgeny 29313cce54 Reserve broadcast capture exclusively while preserving shared voice I/O 2026-10-14 10:17:48 +02:00
evgeny 386998598e Restore bitrate controls and wait for explicit portal source selection 2026-10-14 02:58:45 +02:00
evgeny d934978fb2 Translate desktop broadcast controls and capture diagnostics 2026-10-14 02:57:56 +02:00
evgeny ce461521e4 Defer source failures and end calls that cannot reserve audio capture 2026-10-14 02:56:36 +02:00
evgeny 50fad4ceff Preserve video color range and handle capture worker failures 2026-10-14 02:55:36 +02:00
evgeny ce46677dfb Keep voice recording from opening an already owned microphone 2026-10-14 02:54:30 +02:00
evgeny b3c698be25 Prevent radio capture from competing with a broadcast microphone 2026-10-14 02:54:20 +02:00
evgeny c1d7460254 Own publication independently of its window and reserve call capture 2026-10-14 02:54:19 +02:00
evgeny ab01c6dc48 Expose broadcast setup even when a channel has no live sources 2026-10-14 02:53:22 +02:00
evgeny c91bb2828f Keep raw preview and source permissions independent of video encoding 2026-10-14 02:52:36 +02:00
evgeny f96443e1a6 Add desktop broadcast controls and aggregate send indicators 2026-10-14 02:51:41 +02:00
evgeny 701a7d0eac Capture camera screen and stereo microphone for desktop publication 2026-10-14 02:49:12 +02:00
evgeny 0055b86b19 Encode low latency desktop video with codec specific keyframes 2026-10-14 02:44:59 +02:00
evgeny 77acdb2d65 Expose radio compressor settings to publication workers 2026-10-14 02:35:55 +02:00
evgeny 125f740a5f Use the shared publisher in Android and desktop stream bridges 2026-10-14 02:34:16 +02:00
evgeny f5d112a712 Share bounded media publication and send telemetry across frontends 2026-10-14 02:32:14 +02:00
evgeny a5f8b8431d Verify video profile fallback without excluding a supported codec 2026-10-14 02:14:24 +02:00
evgeny 499ce21cfc Use available encoder profiles when the preferred video profile is absent 2026-10-14 02:14:24 +02:00
evgeny 643420b644 Record codec and telemetry validation with precise decoder diagnostics 2026-10-14 02:11:59 +02:00
evgeny 49ca49fa1e Assemble partial codec setup and test real out of band decoder configuration 2026-10-14 02:01:44 +02:00
evgeny b4aeb1a3fd Keep preview layout stable and document codec configuration exchange 2026-10-14 01:56:02 +02:00
evgeny 39297d1a07 Explain unavailable video decoders and restored capture settings 2026-10-14 01:47:32 +02:00
evgeny de67de33df Report codec capabilities and document the expanded Android media header 2026-10-14 01:45:50 +02:00
evgeny bf12bd5b05 Count only current viewer media against the preparation limit 2026-10-14 01:43:44 +02:00
evgeny 990713c7a7 Retain complete codec parameter sets across separate configuration callbacks 2026-10-14 01:41:12 +02:00
evgeny a4e6683c5b Exercise modern codec playback and partial video queue telemetry 2026-10-14 01:32:40 +02:00
evgeny 06bd9947ce Test codec keyframes and bitrate selection across camera changes 2026-10-14 01:21:04 +02:00
evgeny 305eacbe95 Verify all media codec IDs and key configuration bounds 2026-10-14 01:20:00 +02:00
evgeny 876a5a65ec Test aggregate send windows with duplicate ACKs and connection teardown 2026-10-14 01:20:00 +02:00
evgeny 3e7197702b Decode all supported video formats from uAV2 on desktop 2026-10-14 01:14:38 +02:00
evgeny c815d06ab0 Add video codec and bitrate controls with aggregate send buffer indicators 2026-10-14 01:13:28 +02:00
evgeny 7a5a94bb64 Keep ten hertz publication telemetry separate from preview state 2026-10-14 01:12:31 +02:00
evgeny 1ac7a40432 Capture and play codec specific access units with live bitrate changes 2026-10-14 01:11:49 +02:00
evgeny d209049f71 Enumerate supported surface encoders and model codec specific keyframes 2026-10-14 01:10:43 +02:00
evgeny 5677e8ec5f Initialize the publication media format with the selected codec 2026-10-14 01:09:30 +02:00
evgeny cd8a5392d5 Expose source codec selection and aggregate send telemetry through JNI 2026-10-14 01:09:30 +02:00
evgeny 8b6c72b704 Publish bounded 100 ms send telemetry from the Android media actor 2026-10-14 01:08:20 +02:00
evgeny 4d03a2db53 Expose pending source deliveries without counting ETCP inflight twice 2026-10-14 01:06:57 +02:00
evgeny b6652c7c7f Record unique acknowledged packets at the instance transport boundary 2026-10-14 01:06:56 +02:00
evgeny ac89601fc5 Add instance lifetime ACK windows and exclusive ETCP send queue snapshots 2026-10-14 01:06:32 +02:00
evgeny 7e693fcc97 Define uAV2 codec IDs and signed keyframe configuration envelopes 2026-10-14 01:06:32 +02:00
evgeny 84246bea89 Record adaptive playback validation and ring reader lifecycle 2026-10-14 00:13:08 +02:00
evgeny 2aafe172d6 Reject large audio timestamp jumps before narrowing PLC packet counts 2026-10-14 00:11:31 +02:00
evgeny 359770ecb4 Await all relay ACCEPTs through the bounded pending-buffer retry 2026-10-14 00:09:12 +02:00
evgeny 366cf94e15 Describe shared ring playback, corrected reserve history and stereo catchup 2026-10-14 00:07:52 +02:00
evgeny c0c4310f08 Clarify desktop demand reader ownership and preparation bounds 2026-10-14 00:06:23 +02:00
evgeny ef0fc85eb9 Document bounded read continuation and retained source ownership after FIN 2026-10-14 00:05:49 +02:00
evgeny 88a2b66050 Keep prior network race fixtures independent of the playback scenario clock 2026-10-14 00:04:52 +02:00
evgeny be7c3c5811 Bound desktop decoder work and drain Android codec with monotonic EOS timestamps 2026-10-14 00:04:08 +02:00
evgeny 9c01535c78 Exercise paused ring readers and graceful source close across live nodes 2026-10-14 00:02:59 +02:00
evgeny 3649e45087 Reseek on media discontinuities and bound disabled track timestamps 2026-10-14 00:01:57 +02:00
evgeny 35521fe188 Verify bounded assembly of large retained frames without new packets 2026-10-13 23:46:30 +02:00
evgeny 5937904b4a Test delayed readers without frontend queue growth and isolate codec fixtures 2026-10-13 23:41:10 +02:00
evgeny 98655284b2 Discard stale playback snapshots before reseeking a media cursor 2026-10-13 23:39:45 +02:00
evgeny 6743f84146 Verify stereo playback tempo, pitch and original media clock mapping 2026-10-13 23:35:13 +02:00
evgeny 3010fe25db Hold video only playback clocks on starvation and drain finished audio 2026-10-13 23:32:27 +02:00
evgeny a12665bbd1 Drive desktop ring playback from corrected history and physical audio time 2026-10-13 23:30:06 +02:00
evgeny e1abcb5d69 Observe actual silent device intervals when adapting playback history 2026-10-13 23:26:55 +02:00
evgeny 5c75487446 Continue bounded media assembly without waiting for new network data 2026-10-13 23:24:04 +02:00
evgeny f3bc2909e1 Initialize Android playback before handling its first media packet 2026-10-13 23:18:43 +02:00
evgeny acf374df4e Use adaptive ring backed playback and stereo tempo on Android 2026-10-13 23:16:06 +02:00
evgeny e6e276aed3 Expose shared playback timing and stereo time stretch to Android 2026-10-13 23:03:29 +02:00
evgeny 541518db81 Keep finished source data alive until delayed media readers close 2026-10-13 22:53:38 +02:00
evgeny 9781f138ed Switch Android media delivery to demand reads from the shared RS ring 2026-10-13 22:53:13 +02:00
evgeny f655f2c1d5 Keep playback audio compatible with the Android voice build 2026-10-13 22:46:02 +02:00
evgeny 8d317dbb85 Keep viewer snapshots scoped and normalize Opus packet cadence 2026-10-13 22:45:07 +02:00
evgeny 650b65078a Switch desktop media delivery to bounded demand reads from the shared ring 2026-10-13 22:42:16 +02:00
evgeny 2c7bd78a64 Track stereo time stretched audio through the device playback timeline 2026-10-13 22:37:09 +02:00
evgeny 9f37c54a3c Expose demand based audio and video reads on local stream handles 2026-10-13 22:30:47 +02:00
evgeny 866ca0923d Give audio device lead and video decode lead independent read limits 2026-10-13 22:30:47 +02:00
evgeny 75bd6f1ce1 Notify media readers when indexed playback data becomes available 2026-10-13 22:29:24 +02:00
evgeny 5c12ba253e Test independent playback readers over one authenticated stream ring 2026-10-13 22:21:29 +02:00
evgeny 485e655fd8 Separate media reader cursors from live stream receive progress 2026-10-13 22:15:00 +02:00
evgeny dc7cebaa65 Register indexed media reads and retain only two working blocks 2026-10-13 22:12:51 +02:00
evgeny 126aa9df10 Read media on demand through indexed cursors over the RS ring 2026-10-13 22:09:46 +02:00
evgeny 4ae8fc7c45 Reuse authenticated immutable RS subsets for playback reads 2026-10-13 22:04:55 +02:00
evgeny 2fac28b7e9 Build and register the shared stream playback controller test 2026-10-13 22:04:16 +02:00
evgeny 8e3ec4cb84 Test adaptive playback against a delayed audio device queue 2026-10-13 22:04:16 +02:00
evgeny b2645aed9d Add a shared playback reserve clock with corrected minute history 2026-10-13 21:56:10 +02:00
evgeny 05f2a3602a Record signed variable RS batch implementation and validation 2026-10-13 20:35:53 +02:00
evgeny 3b922ced78 Cover early BOUND in the delayed helper confirmation regression 2026-10-13 20:28:56 +02:00
evgeny 43a600c5a5 Test immediate relay forwarding and log batch authentication 2026-10-13 20:25:40 +02:00
evgeny 01d7fd5bef Document embedded batch signatures and resize pressure fixtures 2026-10-13 20:20:17 +02:00
evgeny 98d1e40bf3 Validate short media batches with an embedded source signature 2026-10-13 20:14:06 +02:00
evgeny e96b813fcb Test batch authentication after losses and recovery after rejection 2026-10-13 20:13:45 +02:00
evgeny f15c97d11a Size Android publisher buffers for the signed batch format 2026-10-13 20:09:14 +02:00
evgeny 6a5bb84d71 Centralize default stream input capacity with batch overhead 2026-10-13 20:09:14 +02:00
evgeny 0e9478d033 Account variable stream parts and continue after rejected batches 2026-10-13 20:08:07 +02:00
evgeny 47b713ea69 Publish actual part lengths and expose rejected batch status 2026-10-13 20:07:02 +02:00
evgeny cee5b26c13 Sign stream batches before RS and authenticate reconstructed data 2026-10-13 20:06:29 +02:00
evgeny 7c8470f44a Define variable stream parts with a signed RS batch envelope 2026-10-13 20:03:08 +02:00
evgeny 3477a8844c Allow RS batches to choose their block size from input length 2026-10-13 19:59:57 +02:00
evgeny 3f6cc5da52 Document callback-driven supplier discovery and verification 2026-10-13 18:16:37 +02:00
evgeny f2c5187ef1 Retry pending supplier buffers without resetting error backoff 2026-10-13 18:06:58 +02:00
evgeny da0a9ad316 Wake stream supplier discovery on Merkle and BGP callbacks 2026-10-13 17:55:19 +02:00
evgeny fec21ab5c5 Record live startup cancellation diagnosis and relay ownership 2026-10-13 17:27:41 +02:00
evgeny ccc9210232 Retain selected relay issuance before delayed READY and ACCEPT 2026-10-13 17:20:36 +02:00
evgeny 325abd8d6f Document verified 100ms stream bootstrap and aligned playback 2026-10-13 16:47:39 +02:00
evgeny 1d7f70b31c Preserve Opus preroll state across timestamp jitter and short gaps 2026-10-13 16:39:37 +02:00
evgeny eda8dfbc91 Cover paused sources and duplicate relay controls during startup 2026-10-13 16:32:53 +02:00
evgeny 8206e53b5c Warm Android Opus preroll and start audio at the first keyframe 2026-10-13 16:30:41 +02:00
evgeny 79701feef1 Drain startup audio on END and validate Opus fixture failures 2026-10-13 16:27:12 +02:00
evgeny f42b085e12 Align desktop audio startup with the first decoded video frame 2026-10-13 16:26:11 +02:00
evgeny 60aaa16327 Confirm relay readiness after reconstructing first FEC batch 2026-10-13 16:22:00 +02:00
evgeny ad20f954c5 Race initial stream suppliers with relay delay of 100ms 2026-10-13 16:14:35 +02:00
evgeny e708117259 Send relay state transitions without retry delays 2026-10-13 16:09:13 +02:00
evgeny 5def12351f Wake stream selection on validated control responses 2026-10-13 16:06:26 +02:00
evgeny 44cfebf898 Record repeated stream delivery stall during stereo verification 2026-10-13 15:30:59 +02:00
evgeny 63ccff67a2 Record stereo and rotation verification on SM and desktop 2026-10-13 15:22:56 +02:00
evgeny 50005b108f Generate continuous stereo Opus in live playback regression 2026-10-13 14:39:08 +02:00
evgeny 727106f4e9 Document stereo live audio and compact rotation controls 2026-10-13 14:30:07 +02:00
evgeny c251de4f77 Verify stereo media format and AGC channel balance 2026-10-13 14:24:59 +02:00
evgeny 8bee8c10ff Replace stream rotation menu with three marked toggle buttons 2026-10-13 14:24:59 +02:00
evgeny 214a98efb1 Play live stereo on desktop and verify channel separation 2026-10-13 14:17:26 +02:00
evgeny e06e9b0e22 Capture encode and play stereo audio in Android live streams 2026-10-13 14:15:44 +02:00
evgeny 22b62699ac Describe stereo Opus in live media blocks 2026-10-13 14:14:24 +02:00
evgeny 5f0fff5fd6 Record CAMCORDER and live radio compressor verification 2026-10-13 14:06:12 +02:00
evgeny 2a2394b34d Verify live stream compression and document shared radio AGC 2026-10-13 13:46:24 +02:00
evgeny 2e0eb89ad3 Capture CAMCORDER audio and apply radio AGC changes live 2026-10-13 13:40:08 +02:00
evgeny 6bd14c53b5 Compress stream audio before Opus using radio settings 2026-10-13 13:39:12 +02:00
evgeny 149e4d43e5 Record live desktop playback verification after queue fix 2026-10-13 13:22:51 +02:00
evgeny c72a66ba06 Record desktop shutdown remaining after core teardown 2026-10-13 13:07:51 +02:00
evgeny 736fb92ed7 Document live playback queue bounds and latency regression 2026-10-13 12:57:37 +02:00
evgeny ef67e3ed2c Preserve video frames until audio clock reaches their PTS 2026-10-13 12:55:56 +02:00
evgeny 1d984f0d0b Reproduce live video starvation behind audio output latency 2026-10-13 12:51:42 +02:00
evgeny cff92f3dac Remove obsolete debug option from configuration fixture 2026-10-13 12:18:01 +02:00
evgeny f953ab5a8f Test all debug categories and record Android settings fixes 2026-10-13 12:12:30 +02:00
evgeny 5f5d4f964a Preserve Android debug level and accept all core categories 2026-10-13 12:10:38 +02:00
evgeny 7fa7a42673 Expose native debug categories and fix Android level fields 2026-10-13 12:10:08 +02:00
evgeny df400a90aa Record partial stream recovery failure from SM and colo2 logs 2026-10-13 11:36:40 +02:00
evgeny 14e2cdc23d Document supernode stream assistance and completed validation 2026-10-13 10:18:44 +02:00
evgeny bbd13215a4 Apply router backpressure and preserve reusable relay attempts 2026-10-13 09:42:46 +02:00
evgeny e73fab0a73 Verify relay handoff expiry, replay protection and complete teardown 2026-10-13 09:38:50 +02:00
evgeny 035a2eb6d0 Retain and release media replication connection ownership 2026-10-13 09:28:51 +02:00
evgeny 57d2828496 Separate stream routing from member files and scope relay activation 2026-10-13 09:14:06 +02:00
evgeny 8dbfb4d297 Exercise supernode relay delivery and preserve ended receive handles 2026-10-13 01:28:23 +02:00
evgeny 29d91d4b19 Activate supernode stream relays on demand and release idle owners 2026-10-13 01:20:24 +02:00
evgeny 661cf032fe Define routed stream relay requests and generation-scoped replies 2026-10-13 01:15:09 +02:00
evgeny cd5e658fb9 Add internal ownership for exact-epoch stream relays 2026-10-13 01:15:09 +02:00
evgeny 8883002e3f Distinguish relay-only stream subscriptions in member profiles 2026-10-13 01:15:09 +02:00
evgeny 2cc9834e4c Use integral scores to select stream suppliers 2026-10-13 01:14:38 +02:00
evgeny 83c652a810 Merge remote-tracking branch 'origin/master' 2026-10-13 01:08:11 +02:00
evgeny 3a6dacf378 Add stream camera controls and preserve playback on changes 2026-10-13 01:00:53 +02:00
evgeny 72bcbd98e8 Handle SIGPIPE during TCP stream test teardown 2026-10-13 00:58:40 +02:00
evgeny 6a92eec983 Log stream probe backoff and pending bootstrap state 2026-10-13 00:56:18 +02:00
evgeny 3adc4a0be4 Isolate stream bootstrap teardown from shared test events 2026-10-13 00:55:12 +02:00
evgeny bf0b90d362 Exercise initial stream reinit on a single TCP link 2026-10-13 00:51:36 +02:00
evgeny 00b78fd629 Preserve pending stream requests across session reinit 2026-10-13 00:40:53 +02:00
evgeny 5ddfc582f7 Merge remote-tracking branch 'origin/master' 2026-10-12 22:38:49 +02:00
evgeny db2771e8de Fix Windows interface macro collision in NCD test 2026-10-12 22:38:23 +02:00
evgeny 29a8fb2531 Check source discovery requested before desktop startup 2026-10-12 22:31:28 +02:00
evgeny 061eb0f0f8 Preserve initial channel watches until desktop core attaches 2026-10-12 22:30:38 +02:00
evgeny 17606c66b8 Document reactive stream discovery and desktop playback ownership 2026-10-12 22:29:00 +02:00
evgeny 34349bd663 Require selected epochs and verify multiple live publishers 2026-10-12 22:28:15 +02:00
evgeny e9982a2db6 Verify live Opus output and volume with real null audio devices 2026-10-12 22:26:41 +02:00
evgeny dd034a9d42 Cover channel closure and keep stream selection usable for long lists 2026-10-12 22:21:10 +02:00
evgeny cacd377d55 Use only shared source events in Android streaming UI and tests 2026-10-12 22:20:28 +02:00
evgeny dad67360b9 Remove obsolete Android stream polling and simplify lifecycle tests 2026-10-12 22:20:28 +02:00
evgeny 6e596f34a5 Notify viewers when channels close and refresh resumed playback state 2026-10-12 22:18:04 +02:00
evgeny 4e1c36955a Test desktop stream discovery, playback, pressure and teardown 2026-10-12 22:16:31 +02:00
evgeny e5d37f6e74 Translate desktop live stream controls and playback states 2026-10-12 22:15:11 +02:00
evgeny 7addfb5aa3 Keep live decoding low latency and preserve publisher stop controls 2026-10-12 22:15:11 +02:00
evgeny b4ebaa8863 Open desktop stream playback windows from channel sources 2026-10-12 22:12:41 +02:00
evgeny fe7734b9e0 Show live sources and select a stream in desktop channel headers 2026-10-12 22:12:41 +02:00
evgeny 10f197c817 Decode and play desktop live H264 and Opus streams 2026-10-12 22:11:35 +02:00
evgeny 0a5b9b50f1 Connect desktop stream bridge to core lifecycle and events 2026-10-12 22:09:39 +02:00
evgeny d9821fa89c Add bounded desktop live media bridge and source lifecycle tests 2026-10-12 22:07:35 +02:00
evgeny e3d9ac9d31 Report Android stream close reasons and epoch changes 2026-10-12 22:01:38 +02:00
evgeny d5ed19ee34 Open Android live streams from the channel header 2026-10-12 22:01:38 +02:00
evgeny ba3dd70f6c Keep Android live sources in reactive channel state 2026-10-12 22:00:12 +02:00
evgeny ba3dfa9fcc Watch Android stream sources and validate selected epochs 2026-10-12 21:58:56 +02:00
evgeny f91894aa1b Expose verified stream sources and channel watch events 2026-10-12 21:57:20 +02:00
evgeny 90d3206071 Confirm new TCP sessions and bound reciprocal challenge retries
Negotiate new TCP sessions even when the peer epoch is known, and let the retry timer send reciprocal challenges to avoid feedback loops when confirmations are lost.

Cover peer restarts, simultaneous connects, REALITY, confirmation loss, second links, stale confirmations and deadlines. Remove forced client resets and count only validated test payloads in the UDP reconnect test.

Validation: 149 tests passed; proxy, burst and load integration tests passed; Android assembleDebug succeeded.
2026-10-12 21:29:41 +02:00
evgeny d372438b6e Reuse automatically completed profiles in the NAT fixture 2026-10-12 20:57:29 +02:00
evgeny d2b96d1340 Release cold priority after a completed partial measurement 2026-10-12 20:48:33 +02:00
evgeny a32eef9ed1 Wait for the actual DOWN timer in the owner-release test 2026-10-12 20:46:43 +02:00
evgeny c7be1ebbbe Preserve cold NAT trials in the real network fixture 2026-10-12 20:43:39 +02:00
evgeny 391059c967 Refresh signed socket metadata when changing the failure-test profile 2026-10-12 20:33:31 +02:00
evgeny bea9ca88a6 Publish the test peer profile through the instance-owned snapshot 2026-10-12 20:33:16 +02:00
evgeny 1b659335b9 Test failure recheck eligibility and coalescing on a blocked UDP peer 2026-10-12 20:33:03 +02:00
evgeny 61e3ad9d24 Start a cold measurement when automatic detection is enabled 2026-10-12 20:31:45 +02:00
evgeny 406f569989 Protect cold ingress before the third routed node is announced 2026-10-12 20:29:33 +02:00
evgeny 3294f67dbf Log cold candidate eligibility and reservation changes 2026-10-12 20:27:34 +02:00
evgeny 2dc7e8d206 Document failure recheck limits and validate requested endpoints 2026-10-12 20:26:12 +02:00
evgeny 3b894753b1 Trigger NAT rechecks after direct timeout and exhausted reverse 2026-10-12 20:25:42 +02:00
evgeny 952fbe68e2 Schedule bounded endpoint rechecks after compatible UDP failure 2026-10-12 20:25:20 +02:00
evgeny 99382cf823 Cancel UDP route probes with their contexts before socket teardown 2026-10-12 20:21:02 +02:00
evgeny ceab874abc Wait for complete NAT cycles and test an indirect private helper 2026-10-12 20:18:21 +02:00
evgeny 2354e94347 Expose pending NAT candidate cycles in local diagnostic status 2026-10-12 20:17:44 +02:00
evgeny 2ba011d3fd Document grouped control, isolated helper ports and ingress correlation 2026-10-12 20:07:49 +02:00
evgeny 179aa0ef35 Release received packets when cold NAT priority defers PONG 2026-10-12 20:05:47 +02:00
evgeny bb01f243fe Allow routed observers in the NAT integration assertions 2026-10-12 19:54:39 +02:00
evgeny 3635bcf3a9 Keep connection-manager background pings out of cold NAT trials 2026-10-12 19:44:28 +02:00
evgeny e60b03916a Test automatic detection in another group over a TCP routed helper 2026-10-12 19:30:42 +02:00
evgeny 9a24ac6bdf Try controlled ingress before asking the broker for helper mapping 2026-10-12 19:27:58 +02:00
evgeny f71e94c9d8 Trace the first UDP contacts that affect cold NAT classification 2026-10-12 19:17:06 +02:00
evgeny 8906e29102 Keep background PONG replies from contaminating cold ingress checks 2026-10-12 19:01:13 +02:00
evgeny 9bd113054c Defer reachability probes while the first NAT ingress is protected 2026-10-12 18:50:08 +02:00
evgeny 9324b8af8c Give initial NAT measurements bounded priority without opening filters 2026-10-12 18:50:08 +02:00
evgeny 585834ffbe Isolate helper traffic from the measured working sockets 2026-10-12 18:47:54 +02:00
evgeny 96fa1990b0 Keep detection attempt counters and network fixtures group-aware 2026-10-12 18:20:47 +02:00
evgeny 0ee431b490 Prefer untouched helper IPs and exercise NAT through real groups 2026-10-12 18:13:29 +02:00
evgeny 8990574c1d Complete grouped NAT control and cancel tasks with group lifetime 2026-10-12 18:08:47 +02:00
evgeny a0a4493ef2 Measure helper endpoints and protect controlled UDP ingress trials 2026-10-12 17:57:22 +02:00
evgeny 77ca1ec3dd Select NAT candidates across groups and route authenticated control 2026-10-12 17:50:45 +02:00
evgeny 2e7da4f3cc Define grouped NAT probe tags and a routed control service 2026-10-12 17:43:27 +02:00
evgeny a97589e4a6 Check desktop address rendering with compact NAT profiles 2026-10-12 17:18:23 +02:00
evgeny 53c32e5669 Document compact NAT profiles and stable measurement lifetime 2026-10-12 17:13:22 +02:00
evgeny 5429431567 Invalidate changed endpoints and verify the Android address contract 2026-10-12 17:11:56 +02:00
evgeny 6272c8a4ad Verify compact address snapshots, migration and Android JSON 2026-10-12 17:10:48 +02:00
evgeny 89f031f4f9 Keep STUN round diagnostics separate from published NAT profiles 2026-10-12 17:07:35 +02:00
evgeny ba29adf6ef Test stable published profiles across repeated NAT measurements 2026-10-12 17:06:35 +02:00
evgeny 61132a3e84 Align socket monitoring with compact address profiles 2026-10-12 17:05:23 +02:00
evgeny db8a5ee2df Retain stable NAT results and compact the address database 2026-10-12 17:04:59 +02:00
evgeny e47ae480f5 Remove time-dependent profile matching from connection setup 2026-10-12 17:01:28 +02:00
evgeny f900cda9c0 Use the last published address profile without a time limit 2026-10-12 17:01:02 +02:00
evgeny 38eaae8bb6 Remove measurement IDs and timestamps from NODEINFO profiles 2026-10-12 17:00:38 +02:00
evgeny d2e62874bb Fix Android member details layout and address formatting 2026-10-12 16:40:40 +02:00
evgeny a96d5926c7 Fix Android external network monitoring and startup readiness
Select frontend network callbacks for auto_sockets=android instead of opening a denied netlink monitor. Preserve monitor descriptor ownership, keep native monitoring failures fatal, and release caller config on failed Android core creation and restart.

Register ConnectivityManager before requesting startup, report readiness after SERVICE_STARTED, and refresh addresses after initialization with serialized updates and generation checks. Add regression coverage for denied netlink, socket failures, address updates, and fd=0 preservation. Record the separate cancellation issue for startup before readiness.

Validation: full check.sh passed, including proxy/burst/load integrations. Clean Android build installed successfully on SM-A525F and Q8; logcat confirms both cores initialized and entered the poll loop with external monitoring and post-start address refresh.
2026-10-12 16:01:26 +02:00
evgeny 72e2c89e3b Fix sockless TCP keepalive and require complete UTUN initialization
Receive keepalive through the owning ETCP link, preserve RX deadlines, and report routing metrics without TUN. Use independent netlink requests and track installed routes for rollback. Reject incomplete resource initialization and release partially constructed cores while preserving caller config ownership.

Validated startup failure and rollback, padded keepalive, standby, STCP traffic, transport lifecycle, dynamic sockets, and daemon startup. Real TUN/netlink checks passed in an isolated Linux network namespace; modified modules passed FreeBSD compilation checks.
2026-10-12 14:24:32 +02:00
evgeny bb256dccdc Extend Android playback counters across long running streams 2026-10-12 12:54:13 +02:00
evgeny a043dc9a42 Bound audio writes and release codecs after failed initialization 2026-10-12 11:49:09 +02:00
evgeny 98dcaae853 Close viewers on malformed media and release terminal owners immediately 2026-10-12 11:46:13 +02:00
evgeny eee67ac9d9 Close local preview when its publication ends 2026-10-12 11:36:07 +02:00
evgeny af580d9d90 Record Android viewer lifecycle and persisted snapshot diagnostics 2026-10-12 11:31:26 +02:00
evgeny 039c0f71f5 Pause viewers in the background and keep visible playback awake 2026-10-12 11:30:36 +02:00
evgeny 150b3c3bdb Explain invalid media metadata and codec transitions in logs 2026-10-12 11:26:27 +02:00
evgeny 1dcf6a79ce Reject overlapping core ownership of Android stream handles 2026-10-12 11:26:27 +02:00
evgeny 18661f0ef0 Constrain live preview while keeping stream controls visible 2026-10-12 11:21:30 +02:00
evgeny ef76ca5de3 Document audiovisual stream format and Android ownership contracts 2026-10-12 07:24:13 +02:00
evgeny dac5f98052 Select local preview from the verified source snapshot 2026-10-12 07:22:43 +02:00
evgeny c2c202e716 Mute local preview and clear Android media queues at detach 2026-10-12 07:22:43 +02:00
evgeny c1b1713ab1 Verify Android AVC normalization and keyframe configuration 2026-10-12 02:47:15 +02:00
evgeny e8adf03826 Preserve stream control events when discarding delayed media 2026-10-12 02:42:01 +02:00
evgeny 1209d8e4b7 Keep video clock moving through audio underruns and drain final playback 2026-10-12 02:30:36 +02:00
evgeny e09ee919a5 Release capture before service ownership and report startup failures 2026-10-12 02:29:26 +02:00
evgeny c9743ee85d Exercise Android stream queues and teardown against a live core 2026-10-12 02:29:08 +02:00
evgeny 0c613eacbc Show verified source snapshots without borrowing core pointers 2026-10-12 02:28:02 +02:00
evgeny 6c692399c2 Build verified live source snapshots in the core thread 2026-10-12 02:27:44 +02:00
evgeny 4fc1f31651 Prevent competing Android capture while broadcasting 2026-10-12 02:07:04 +02:00
evgeny d4ca812578 Open live streams from Android channel navigation 2026-10-12 02:07:04 +02:00
evgeny 8502d6f2d7 Add localized Android stream controls and player screen 2026-10-12 02:06:27 +02:00
evgeny ee851b4aa6 Play live streams with bounded queues and audio clock synchronization 2026-10-12 02:04:38 +02:00
evgeny f2a5dde761 Declare stream camera permissions and cancel capture at core restart 2026-10-12 02:02:48 +02:00
evgeny e4f2e647d7 Own live camera sessions in an Android foreground service 2026-10-12 02:02:48 +02:00
evgeny c50d5066a9 Capture Android AVC and Opus on a shared monotonic timeline 2026-10-12 02:01:09 +02:00
evgeny 2f707366f3 Expose session scoped Android stream operations 2026-10-12 01:51:10 +02:00
evgeny 6f544b4ca2 Add Android JNI transport and independent stream Opus codecs 2026-10-12 01:51:10 +02:00
evgeny 0ee1163a0b Bind Android media queues to core lifetime and source epochs 2026-10-12 01:50:07 +02:00
evgeny 938289a6f0 Include Android streaming bridge and refine malformed wire tests 2026-10-12 01:49:40 +02:00
evgeny 36fceec6b2 Bridge Android live streams through bounded session queues 2026-10-12 01:49:07 +02:00
evgeny fc3518f5ba Test media boundaries FEC losses and keyframe recovery 2026-10-12 01:46:06 +02:00
evgeny 5c23c10c04 Register audiovisual stream sources and regression target 2026-10-12 01:46:06 +02:00
evgeny 1389bac261 Define FEC protected audiovisual blocks and frame assembly 2026-10-12 01:45:08 +02:00
evgeny 47caca82a1 Expose atomic publication of stream batches 2026-10-12 01:43:31 +02:00
evgeny baeaef3ab1 Preserve logical block boundaries in stream sources 2026-10-12 01:43:21 +02:00
evgeny 4f9fefe9e5 Test and document NCD ownership protecting CM direct probes beyond down timeout 2026-10-12 01:34:44 +02:00
evgeny fdaa20977d Recheck expired down lifetime when the last NCD owner releases it 2026-10-12 01:34:44 +02:00
evgeny 11f1a14063 Retain down ETCP connections while local NCD handles exist 2026-10-12 01:34:44 +02:00
evgeny 0d7f7630b0 Expose actual NCD handle ownership for an ETCP transport 2026-10-12 01:34:44 +02:00
evgeny 6704a18897 Clarify independent RX watchdog and sleep-aware graceful close deadlines 2026-10-12 01:34:44 +02:00
evgeny 24c64967fd Propagate remote UDP link DOWN to the connection expiry timer 2026-10-12 01:34:44 +02:00
evgeny 85b4af5b1d Shorten RX deadline immediately when a sleeping peer wakes 2026-10-12 01:34:44 +02:00
evgeny 2e66cdb122 Preserve cancellation of pending outgoing attempts and size packet fixture correctly 2026-10-12 01:34:44 +02:00
evgeny b57221f6b5 Document ETCP down retention and separate RX lifetime from keepalive sending 2026-10-12 01:34:44 +02:00
evgeny 1c53ef597d Register lifecycle regressions and cancel timers owned by synthetic fixtures 2026-10-12 01:34:44 +02:00
evgeny 0ba8380db4 Test incoming ETCP expiry, reconnect, sleep and graceful close 2026-10-12 01:34:44 +02:00
evgeny f090d697c4 Initialize inbound graceful close and honor remote transport ownership 2026-10-12 01:34:44 +02:00
evgeny 3cfdb9e6ec Keep a finite RX deadline while ETCP keepalive sending is suspended 2026-10-12 01:34:44 +02:00
evgeny 034be69ac1 Expire ETCP transports after 30 seconds continuously down 2026-10-12 01:34:44 +02:00
evgeny b852058501 Record validated STUN fallback, sanitizer checks and IPv6 follow-up 2026-10-12 01:31:29 +02:00
evgeny b3830e0264 Document signed profile source and reproducible isolated STUN bench 2026-10-12 01:28:49 +02:00
evgeny c19f835334 Document implemented interface STUN fallback and provenance semantics 2026-10-12 01:27:40 +02:00
evgeny 9f907d0f31 Stop NAT detectors before service callbacks and verify STUN restart isolation 2026-10-12 01:25:59 +02:00
evgeny 152ebfef48 Release retained STUN port history during final core destruction 2026-10-12 01:24:00 +02:00
evgeny 3d5f009380 Keep STUN port history owned by the core across configuration reloads 2026-10-12 01:24:00 +02:00
evgeny 3735b78328 Verify STUN cache expiry and preserve partial ETCP measurement priority 2026-10-12 01:20:56 +02:00
evgeny 709b0a3271 Wait for the real observer link and distribute STUN bench scripts 2026-10-12 01:15:31 +02:00
evgeny 8c5e4ab691 Exercise STUN fallback and ETCP promotion with real namespace NAT 2026-10-12 01:14:09 +02:00
evgeny a581cb2086 Add isolated STUN fallback scenario and equivalent NAT for test ports 2026-10-12 01:12:28 +02:00
evgeny f433413c49 Test deduplicated STUN groups and fallback promotion through real ETCP 2026-10-12 01:01:11 +02:00
evgeny 164a3a1f0f Keep connection attempts available for STUN hints and verify signed provenance 2026-10-12 00:58:50 +02:00
evgeny 9aa56e6b56 Show NAT profile origin and recheck interface STUN from control 2026-10-12 00:58:50 +02:00
evgeny 76d2e58f19 Expose ETCP and STUN profile provenance in monitor protocol 2026-10-12 00:58:32 +02:00
evgeny 01b0992c39 Select interface STUN fallback only when ETCP lacks independent observers 2026-10-12 00:57:54 +02:00
evgeny 735ac36579 Sign endpoint profile provenance and restrict external-address publication 2026-10-12 00:56:43 +02:00
evgeny 6beae21ec4 Group STUN probes by interface path and refresh independent-port results 2026-10-12 00:55:29 +02:00
evgeny 8b8b73983b Plan interface STUN profiles as fallback for unavailable ETCP probes 2026-10-12 00:42:14 +02:00
evgeny 37e897d0d2 Plan unified STUN and ETCP endpoint measurements 2026-10-12 00:09:13 +02:00
evgeny 5c83aac96e Adapt STUN diagnostics to endpoint profiles and helper sockets 2026-10-12 00:03:07 +02:00
evgeny 39dd038831 Record ASAN validation of endpoint detector lifecycle 2026-10-12 00:01:40 +02:00
evgeny ff69552a61 Measure owned endpoint mapping and inbound reachability separately 2026-10-12 00:01:40 +02:00
evgeny b54307a0df Ignore local hardware directory 2026-10-12 00:01:40 +02:00
evgeny 65f05c86ed Save workspace state before address detection redesign 2026-10-12 00:01:40 +02:00
evgeny 5eb1c86acf Include stream bench validation notes in source distribution 2026-10-12 00:01:40 +02:00
evgeny b8f8818eef Record validated stream bench runs and explicit RPC error diagnostics 2026-10-12 00:01:40 +02:00
evgeny 89c6482a96 Record clean NAT startup and monotonic restart contract in stream specification 2026-10-12 00:01:40 +02:00
evgeny d44b4bc682 Test repeated restart before deferred GAP and prohibit replay under overload 2026-10-12 00:01:40 +02:00
evgeny 802134c846 Preserve delivered cursor when stream restarts from a stale supplier 2026-10-12 00:01:40 +02:00
evgeny 1969ca5535 Require monotonic consumer cursor across stream receive restart 2026-10-12 00:01:40 +02:00
evgeny 5084670820 Recreate bench network after calibration to clear NAT and conntrack state 2026-10-12 00:01:40 +02:00
evgeny e78eaf45f7 Document namespace stream bench and completion after consumer drain 2026-10-12 00:01:40 +02:00
evgeny a877ba8a73 Keep automatic stream recovery active until signed END is drained 2026-10-12 00:01:40 +02:00
evgeny a4e899807c Clear closed fixture handles before terminal stream regression 2026-10-12 00:01:40 +02:00
evgeny d99b0e5525 Reproduce automatic stream stall after early END with incomplete FEC batch 2026-10-12 00:01:40 +02:00
evgeny 46ac9a03b9 Add forced TCP bench mode and verify observed source timestamp wraps 2026-10-12 00:01:40 +02:00
evgeny 57bf4e490d Bound namespace startup and measure LAN quality before stream tests 2026-10-12 00:01:40 +02:00
evgeny 649be77fa3 Use core clock for stream metrics and require new data after WAN recovery 2026-10-12 00:01:40 +02:00
evgeny 5c1bb06294 Expose source clock and stream metrics in bench snapshots 2026-10-12 00:01:40 +02:00
evgeny 284b603e6e Account initial GAP independently of first delivered stream record 2026-10-12 00:01:40 +02:00
evgeny e92e08e5cc Document stream lab and test namespace isolation guards 2026-10-12 00:01:40 +02:00
evgeny 3dc682087a Wait for verified Merkle profiles as well as BGP reachability 2026-10-12 00:01:40 +02:00
evgeny ebe3232268 Apply optional load collection only to peer queue telemetry 2026-10-12 00:01:40 +02:00
evgeny 0eae531706 Include local BGP presence and make queue diagnostics opt-in 2026-10-12 00:01:40 +02:00
evgeny e9e93ef26b Wire live stream scenarios into isolated bench launcher 2026-10-12 00:01:40 +02:00
evgeny fe4791d6f5 Add sixteen-node live JOIN and streaming scenarios 2026-10-12 00:01:39 +02:00
evgeny 6332e75e61 Add real uTun stream bench node with payload and latency validation 2026-10-12 00:01:39 +02:00
evgeny 856fbc61b1 Keep NAT probes independent of cached conntrack and DHCP timers 2026-10-12 00:01:39 +02:00
evgeny 47948fe847 Add rootless isolated stream bench launcher 2026-10-12 00:01:39 +02:00
evgeny 0b1a081e34 Implement isolated stream bench networking and NAT calibration 2026-10-12 00:01:39 +02:00
evgeny 0172aa3081 Create dedicated stream network-namespace bench topology 2026-10-12 00:01:39 +02:00
evgeny 4096691eb2 Record full-suite and final sanitizer verification for automatic streaming 2026-10-12 00:01:39 +02:00
evgeny fc63b801f9 Clarify generation discovery on every stream INFO probe 2026-10-12 00:01:39 +02:00
evgeny af1f3cc007 Document nonce-bound unilateral stream generation recovery 2026-10-12 00:01:39 +02:00
evgeny 6b18ad45ea Restore automatic suppliers after proven resynchronization and test unilateral generation changes 2026-10-12 00:01:39 +02:00
evgeny a8cd802aad Resynchronize unilateral peer generations through nonce-bound INFO without epoch ping-pong 2026-10-12 00:01:39 +02:00
evgeny 9a648219cf Document automatic stream selection, wire v3, active-mode ownership and verified behavior 2026-10-12 00:01:39 +02:00
evgeny 6749d09025 Enforce distinct manual and automatic upstream ownership 2026-10-12 00:01:39 +02:00
evgeny 0d2d7c913d Keep optimizer diagnostic format strings within repository line limits 2026-10-12 00:01:39 +02:00
evgeny 090964a786 Log provider offers and measured reasons for confirmed stream migrations 2026-10-12 00:01:39 +02:00
evgeny fcbdbd6002 Test probe burst, second-level admission and complete-cycle timing boundaries 2026-10-12 00:01:39 +02:00
evgeny c6bf960eec Isolate monotonic probe scheduling policy for precise boundary checks 2026-10-12 00:01:39 +02:00
evgeny ab4b75986d Reopen closed peer ownership and drive reverse requests from current stream attempts 2026-10-12 00:01:39 +02:00
evgeny 7144bbe694 Verify deferred standby restoration and END-only automatic streams 2026-10-12 00:01:39 +02:00
evgeny 03814b9660 Tie active-mode holds to stream ownership and deliver signed END for empty live sources 2026-10-12 00:01:39 +02:00
evgeny bccf8c6b38 Preserve frontend activity requests while stream owners hold the core active 2026-10-12 00:01:39 +02:00
evgeny 1308e8201e Verify direct, reverse-only and incompatible NCD capability checks without side effects 2026-10-12 00:01:39 +02:00
evgeny aa3fb9cac7 Admit reverse-capable providers without requiring an outgoing-compatible address 2026-10-12 00:01:39 +02:00
evgeny 22342b310c Bound stale offer storage, detect initial DATA stalls and report verified FEC protection 2026-10-12 00:01:39 +02:00
evgeny 2d9616eca5 Refresh matching active path RTT through probes and isolate integration event counters 2026-10-12 00:01:39 +02:00
evgeny 7db3008e5c Wait for the actual automatic stream source in Merkle subscription checks 2026-10-12 00:01:39 +02:00
evgeny 31d0db939a Include NCD compatibility API in automatic stream integration test 2026-10-12 00:01:39 +02:00
evgeny bc49b452f9 Exercise automatic stream selection, INFO-only probes and verified overlap failover 2026-10-12 00:01:39 +02:00
evgeny 99359f987c Use observed provider quality and keep empty-source queue accounting bounded 2026-10-12 00:01:39 +02:00
evgeny cb9ef94501 Keep protocol and overload fixtures under explicit manual upstream control 2026-10-12 00:01:39 +02:00
evgeny 94c482ed35 Handle empty sources and next-batch tail subscriptions without startup races 2026-10-12 00:01:39 +02:00
evgeny ef48fa2a37 Enable automatic upstream selection for subscriptions with lifecycle-safe cleanup 2026-10-12 00:01:39 +02:00
evgeny 576c79657b Implement stream supplier probe cycles, FEC-aware selection and overlap migration 2026-10-12 00:01:39 +02:00
evgeny 3db2130b56 Define per-stream automatic selection settings and diagnostic manual mode 2026-10-12 00:01:39 +02:00
evgeny 0b9b395879 Keep SUBSCRIBE deadlines and prevent ambiguous pending INFO correlation 2026-10-12 00:01:39 +02:00
evgeny 35ad47c653 Implement DATA-free stream probes with ranges, paths and load offers 2026-10-12 00:01:39 +02:00
evgeny 984153b90a Expose monotonic age of verified substream progress 2026-10-12 00:01:39 +02:00
evgeny 9cd285ddb0 Define INFO-only stream probes and provider offer snapshots 2026-10-12 00:01:39 +02:00
evgeny ca5a374042 Expose side-effect-free NCD transport compatibility using shared link matching 2026-10-12 00:01:39 +02:00
evgeny ee8baae160 Record full and sanitizer checks for stream overload delivery 2026-10-12 00:01:39 +02:00
evgeny f10078e482 Document timestamp metrics and negotiated stream overload cuts 2026-10-12 00:01:39 +02:00
evgeny 0282fe7e17 Keep newer INFO probes alive when old queued probes expire 2026-10-12 00:01:39 +02:00
evgeny 4142c7691a Expire stale stream control without retaining orphan issuances 2026-10-12 00:01:39 +02:00
evgeny 223bb145e6 Compare overload request history when checking CPU-only load 2026-10-12 00:01:39 +02:00
evgeny c9ce8d3a74 Expose overload admission recovery and wait for it in tests 2026-10-12 00:01:39 +02:00
evgeny bee7bdaa70 Expose stream load and network APIs with C linkage 2026-10-12 00:01:39 +02:00
evgeny 695741d9d3 Exclude stale growth and pending cuts from global overload budgets 2026-10-12 00:01:39 +02:00
evgeny 01324d2ebb Avoid an extra FEC cut for a marginal overload safety reserve 2026-10-12 00:01:39 +02:00
evgeny 3d6c843958 Verify global stream pressure requires backlog as well as CPU 2026-10-12 00:01:39 +02:00
evgeny 97495377b2 Assert rate-based parity cuts rather than a fixed close count 2026-10-12 00:01:39 +02:00
evgeny 1174d71a06 Wait for CLOSED delivery after unblocking overload control 2026-10-12 00:01:39 +02:00
evgeny 108e97b550 Validate partial overload cuts preserve six FEC data streams 2026-10-12 00:01:39 +02:00
evgeny a4e080da91 Prefer FEC parity cuts and refresh receive progress on inspection 2026-10-12 00:01:39 +02:00
evgeny f11592be3e Exercise stream overload under real ETCP backpressure 2026-10-12 00:01:39 +02:00
evgeny 61df1204f8 Expect expired arrival intervals to leave the load window 2026-10-12 00:01:39 +02:00
evgeny df474a33d0 Check stream pressure timing, accounting and wide metric fields 2026-10-12 00:01:39 +02:00
evgeny c10e0c7e3c Negotiate frozen-rate stream cuts with bounded overload grace 2026-10-12 00:01:39 +02:00
evgeny a07c53a397 Measure stream issuances, probe RTT and pending data demand 2026-10-12 00:01:39 +02:00
evgeny 921340ae60 Track signed timestamp progress in each local stream subflow 2026-10-12 00:01:39 +02:00
evgeny fabd7ac91f Define stream pressure negotiation and timestamp metric snapshots 2026-10-12 00:01:39 +02:00
evgeny fec1916a58 Observe stream cache arrivals and exact pending block age 2026-10-12 00:01:39 +02:00
evgeny e90ad5acbd Account stream demand and confirm sustained queue pressure 2026-10-12 00:01:39 +02:00
evgeny ddc386d16c Record ASAN and full checks for live NCD stream delivery 2026-10-12 00:01:39 +02:00
evgeny 007f5d21b2 Document live NCD stream delivery, wire format and limits 2026-10-12 00:01:39 +02:00
evgeny 67152f6b7b Verify independent stream groups share one direct ETCP session 2026-10-12 00:01:39 +02:00
evgeny ee36c578e2 Reject premature stream completion and failed ACCEPT enqueue 2026-10-12 00:01:39 +02:00
evgeny 0b202bb3e7 Bound stream path allocations and validate wire rejection cases 2026-10-12 00:01:39 +02:00
evgeny fc80186043 Restart stream receives and expose confirmed delivery gaps 2026-10-12 00:01:39 +02:00
evgeny d6f935d779 Locate recoverable stream batches when skipping delivery gaps 2026-10-12 00:01:39 +02:00
evgeny 109936f846 Revalidate stream peers and respect asynchronous ETCP reset 2026-10-12 00:01:39 +02:00
evgeny 09e86a16f6 Exercise live stream relay, common start, leases and epochs 2026-10-12 00:01:39 +02:00
evgeny d83e85b8ca Report stream ring bounds and handle expired and final cursors 2026-10-12 00:01:39 +02:00
evgeny c2179b3959 Implement NCD stream issuances, ring cursors and routed reverse 2026-10-12 00:01:39 +02:00
evgeny 36c4ff6050 Connect local stream lifetime and signed END to network delivery 2026-10-12 00:01:39 +02:00
evgeny b31aa9f71d Retain stream block paths and locate gaps in ring delivery 2026-10-12 00:01:39 +02:00
evgeny 18fe51ad25 Define direct stream messages, paths and signed completion 2026-10-12 00:01:39 +02:00
evgeny 12c0de503d Document installer builds and register packaging checks 2026-10-12 00:01:39 +02:00
evgeny 804007c997 Test Windows installer contents and build error propagation 2026-10-12 00:01:39 +02:00
evgeny 6d3c555ac4 Record successful full stream integration checks 2026-10-12 00:01:39 +02:00
evgeny fa79b7d30c Document stream CHAT integration and signed member option format 2026-10-12 00:01:39 +02:00
evgeny 5f9cb86ff0 Package only imported DLLs and log firewall command output 2026-10-12 00:01:39 +02:00
evgeny a62540800b Cancel local streams when signed membership is invalidated 2026-10-12 00:01:39 +02:00
evgeny c250e703f7 Create VibeChat installer from Windows BAT entry points 2026-10-12 00:01:39 +02:00
evgeny 197696075c Release owned BGP path entries before removing their queues 2026-10-12 00:01:39 +02:00
evgeny 78e0312fee Validate stream lifecycle and Merkle BGP integration 2026-10-12 00:01:39 +02:00
evgeny 1b406202b5 Integrate installer generation into Windows build scripts 2026-10-12 00:01:39 +02:00
evgeny 2e70c3baed Add VibeChat Windows installer and runtime packaging 2026-10-12 00:01:39 +02:00
evgeny 30106c8f93 Attach stream subscriptions to CHAT lifecycle and member profiles 2026-10-12 00:01:39 +02:00
evgeny b14f00a4b2 Check Windows ChatGUI config writability directly 2026-10-12 00:01:38 +02:00
evgeny 753aa4217d Use LocalAppData fallback for Windows ChatGUI storage 2026-10-12 00:01:38 +02:00
evgeny b6f80668d1 Define signed stream member options and BGP eligibility 2026-10-12 00:01:38 +02:00
evgeny a33666b778 Add atomic updates for signed member profile options 2026-10-12 00:01:38 +02:00
evgeny ec82d95a03 Add signed FEC stream core and source buffering 2026-10-12 00:01:38 +02:00
evgeny 2c5deafe56 Add independent STUN NAT probes with RFC 5780 discovery 2026-10-11 23:35:47 +02:00
evgeny 9cc996b775 Document Reed-Solomon module and update streaming design 2026-10-10 19:45:14 +02:00
evgeny 2967d2fb71 Fix matrix inverse syntax and make bounded conversions explicit 2026-10-10 19:38:08 +02:00
evgeny d9d4a7807f Test Reed-Solomon recovery and integrate codec into builds 2026-10-10 19:37:07 +02:00
evgeny 831018f161 Add batch Reed-Solomon codec with caller-owned buffers 2026-10-10 19:35:10 +02:00
evgeny 84a171dcac tests: keep build script regressions outside the regular check 2026-10-10 17:48:20 +02:00
evgeny 8de64174bb tests: remove unnecessary barriers from parallel checks 2026-10-10 17:42:31 +02:00
evgeny 69a99c7860 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	tests/Makefile.am
2026-10-10 17:25:40 +02:00
evgeny c85a8116ef tests: run checks in parallel with available CPU count by default 2026-10-10 17:14:29 +02:00
evgeny b16da44e42 tests: assign STCP ping and REALITY fallback ports at bind 2026-10-10 16:54:56 +02:00
evgeny 7ae2f74643 tests: discover live endpoints in forked JOIN and network polls 2026-10-10 16:51:38 +02:00
evgeny 482d2787fb tests: retain automatically assigned endpoints across restart 2026-10-10 16:48:19 +02:00
evgeny 12d4e2433e dummynet: route between explicit independently bound endpoints 2026-10-10 16:46:19 +02:00
evgeny 70b55e4626 tests: assign CM phase listeners and failure endpoints dynamically 2026-10-10 16:40:56 +02:00
evgeny e836090a02 tests: allocate live and blackhole multilink endpoints dynamically 2026-10-10 16:39:17 +02:00
evgeny 4b499a076a tests: derive NCD fixture addresses from bound sockets 2026-10-10 16:37:36 +02:00
evgeny b0ca182fc5 tests: preserve diagnostic filters when wiring peer endpoints 2026-10-10 16:35:07 +02:00
evgeny 45a6320b5e tests: assign JOIN, member files and DM endpoints dynamically 2026-10-10 16:31:54 +02:00
evgeny e4fe534382 tests: isolate media delivery endpoints and scratch files 2026-10-10 16:23:09 +02:00
evgeny 336b6871f3 tests: allocate broadcast, avatar and invite listeners dynamically 2026-10-10 16:14:53 +02:00
evgeny e25d5cc220 tests: use bound endpoints for CM and database synchronization 2026-10-10 16:11:00 +02:00
evgeny 101266d1a7 tests: assign independent CM and chat sync listener ports 2026-10-10 16:07:43 +02:00
evgeny efdfb1d835 tests: inherit the ready echo listener on an automatic port 2026-10-10 16:06:07 +02:00
evgeny 4e2cc0e35a tests: hold OS-assigned HTTP and SOCKS listener ports 2026-10-10 16:04:19 +02:00
evgeny 08865b2909 tests: allocate UDP proxy and NAT transport ports automatically 2026-10-10 15:56:28 +02:00
evgeny 0af9a3092b tests: allocate ports independently in every STCP scenario 2026-10-10 15:55:28 +02:00
evgeny dc0dbbf720 headless: expose OS-assigned control and audio listener ports 2026-10-10 15:51:04 +02:00
evgeny 91909fbb1e tests: use independent ports for radio and call topologies 2026-10-10 15:45:38 +02:00
evgeny ce8144b79e tests: assign independent ports to BGP topology listeners 2026-10-10 15:44:00 +02:00
evgeny 0c09252dd6 tests: assign Merkle and DM media fixture ports dynamically 2026-10-10 15:40:34 +02:00
evgeny 98529b3268 tests: allocate STCP traffic and link listener ports automatically 2026-10-10 15:32:47 +02:00
evgeny 0dcecdd7b2 tests: remove PID ports and allocate SOCKS listeners dynamically 2026-10-10 15:30:48 +02:00
evgeny 44522027e7 tests: use ephemeral ports for ETCP traffic fixtures 2026-10-10 15:29:46 +02:00
evgeny 3f4a67f552 tests: assign NAT and route ping ports through bind zero 2026-10-10 15:28:48 +02:00
evgeny 4afd5df55f tests: use bound ephemeral ports for ETCP and IPv6 peers 2026-10-10 15:27:47 +02:00
evgeny 991fc0a2c1 transport: publish ports assigned by the operating system 2026-10-10 15:26:08 +02:00
evgeny 4908cb7e19 Document shared FreeBSD builds and locate Bash through PATH 2026-10-10 15:25:58 +02:00
evgeny c16334d551 Run build regressions with native FreeBSD tools and Windows binaries 2026-10-10 15:25:58 +02:00
evgeny e0ba199e20 Share incremental builds on FreeBSD and select portable hardening flags 2026-10-10 15:25:58 +02:00
evgeny d6c6a4d111 Use verified incremental builds instead of mandatory clean 2026-10-10 15:25:58 +02:00
evgeny 62a853e73c Detect compiler path changes and verify safe configuration cleanup 2026-10-10 15:25:58 +02:00
evgeny ff2eae2efb Verify build mode transitions and propagate GUI job counts 2026-10-10 15:25:58 +02:00
evgeny ff9d205d5b Track build configuration and preserve incremental header dependencies 2026-10-10 15:25:58 +02:00
evgeny 6437665a53 tests: accelerate session, recovery and SOCKS retry timers 2026-10-10 15:11:26 +02:00
evgeny aec9536522 tests: await proxy server readiness and retain worker failures 2026-10-10 15:10:09 +02:00
evgeny f843b553c7 tests: accelerate proxy regression event waits 2026-10-10 15:08:47 +02:00
evgeny d9c8e006b5 tests: accelerate standby phases and group connection retries 2026-10-10 15:06:56 +02:00
evgeny 361c7df812 tests: accelerate radio timers and run it among short checks 2026-10-10 14:55:53 +02:00
evgeny a1fdca09f6 tests: accelerate Merkle setup and drain waits 2026-10-10 14:54:39 +02:00
evgeny 7506da41f0 tests: keep Merkle stress records changing and verify final versions 2026-10-10 14:53:37 +02:00
evgeny 17777c540b tests: finish CPU load phases after a complete real window 2026-10-10 14:51:59 +02:00
evgeny 91081fc5c7 tests: accelerate autosocket waits and payload verification 2026-10-10 14:49:42 +02:00
evgeny 3eee716b50 tests: run short checks before slow and load scenarios 2026-10-10 14:22:06 +02:00
evgeny 0448a84286 tests: replace slow disk sleep with six-second clock-driven gate 2026-10-10 14:19:27 +02:00
evgeny 3baaa42c82 tests: accelerate BGP waits and require single-link DOWN and UP 2026-10-10 14:17:21 +02:00
evgeny 39594ad800 tests: accelerate DM retries and TTL with synchronized clocks 2026-10-10 14:14:03 +02:00
evgeny 414d40c00c tests: accelerate JOIN waits and verify timeout boundaries 2026-10-10 13:55:04 +02:00
evgeny cb32aa5aac tests: accelerate connection manager phase and recovery waits 2026-10-10 13:52:27 +02:00
evgeny 069ecc900a tests: accelerate router fault timers with real I/O clock driver 2026-10-10 13:46:50 +02:00
evgeny ec66a7e4f5 tests: detect avatar BUSY ACK repeats with ACK counter 2026-10-10 13:29:47 +02:00
evgeny 2a9a92cc34 tests: finish wrong-key STCP case when handshake closes 2026-10-10 13:27:27 +02:00
evgeny 9b879d6ced tests: wait for avatar backpressure and shorten NCD timeout fixture 2026-10-10 13:25:46 +02:00
evgeny 5805edc3dc tests: shorten mock NTP and DNS shutdown waits 2026-10-10 13:25:04 +02:00
evgeny 3f9edc88c4 Make privilege, port and fd handling reliable in tests 2026-10-10 13:09:45 +02:00
evgeny 5a9e61164b Measure uasync CPU load and display it in desktop and Android GUIs
Sample thread CPU usage without waking idle loops, expose a one-second moving average, and refresh compact GUI indicators at 3 Hz while visible on Android. Add load and bridge regression coverage, streaming notes, and task list cleanup.
2026-10-10 12:31:13 +02:00
evgeny 8bd6e4fb2d Remove obsolete trailing space after channel moc cleanup 2026-10-10 11:18:59 +02:00
evgeny e8a4b8dd4e Translate the group header menu labels 2026-10-10 11:18:34 +02:00
evgeny 61f2ca7a88 Remove group row icons and give titles the freed space 2026-10-10 11:18:17 +02:00
evgeny bfdd9cde15 Remove inline action hit targets from channel rows 2026-10-10 11:17:32 +02:00
evgeny b14bfa95c2 Collect group actions in the conversation header menu 2026-10-10 11:17:00 +02:00
evgeny 42efbbc7da Remove the file manager button from the channel footer 2026-10-10 11:07:02 +02:00
evgeny 8f908c320a Open own group files from the conversation header 2026-10-10 11:06:45 +02:00
evgeny 5ed813fe06 Record recursive removal checks and the receipt cache limit 2026-10-10 11:03:44 +02:00
evgeny 7eefb499f3 Cover recursive confirmation and storage receipt exhaustion 2026-10-10 11:02:31 +02:00
evgeny b4a9339459 Test recursive removal against real disk storage 2026-10-10 10:59:58 +02:00
evgeny afc54eae00 Confirm recursive deletion and localize removal progress 2026-10-10 10:58:45 +02:00
evgeny 7244f22722 Remove directory trees after a complete preflight scan 2026-10-10 10:58:25 +02:00
evgeny 973c734226 Defer closing until batch cancellation and cleanup finish 2026-10-10 09:38:35 +02:00
evgeny 4a6765c49a Wait for the file manager to finish copying every entry 2026-10-10 09:37:44 +02:00
evgeny db91bccda3 Keep deferred drag results owned by their operation 2026-10-10 01:58:13 +02:00
evgeny 11fc932905 Restore keyboard focus and test changed-tree cleanup 2026-10-10 01:57:28 +02:00
evgeny f7ff2df9a7 Place the native drag fixture beside the file manager 2026-10-10 01:43:06 +02:00
evgeny 3a6d075cbb Add an opt-in native file manager drag integration test 2026-10-10 01:42:42 +02:00
evgeny f0e68edad1 Translate batch file and directory operations 2026-10-10 01:41:42 +02:00
evgeny 1ed467d236 Validate batch snapshots and freeze deletion confirmation context 2026-10-10 01:41:42 +02:00
evgeny 8785fc358a Test range selection and recursive file tree transfers 2026-10-10 01:40:39 +02:00
evgeny 400b9fa93c Adapt browser regressions to asynchronous batch completion 2026-10-10 01:39:43 +02:00
evgeny 2e9babe73b Build batch transfers and wait for cancellation on close 2026-10-10 01:38:54 +02:00
evgeny 7bfa75b32c Connect multi-selection and folder transfers to the browser 2026-10-10 01:38:28 +02:00
evgeny 884eb4b412 Add cancellable file tree batch operations 2026-10-10 01:37:14 +02:00
evgeny ac17ffda54 Honor the expected file version before downloading 2026-10-10 01:35:28 +02:00
evgeny 468aeeebc7 Test Android file wire formats and nonempty directory status 2026-10-10 01:27:34 +02:00
evgeny 11b6f78924 Share exported files safely and show background transfer progress 2026-10-10 01:27:34 +02:00
evgeny a630401d10 Localize file operations and refine transfer notifications 2026-10-10 01:27:17 +02:00
evgeny 4abfbc3e9c Open Android file browser from channels and members 2026-10-10 01:27:17 +02:00
evgeny 6a3def3c9a Add a mobile member file browser with safe file actions 2026-10-10 01:27:17 +02:00
evgeny e97885eb22 Expose member file commands through Android JNI 2026-10-10 01:15:32 +02:00
evgeny 51e7f821e9 Own Android file transfers and core activity independently of UI 2026-10-10 01:15:32 +02:00
evgeny 6e5f86b2b5 Share Android file codecs and request IDs with avatars 2026-10-10 01:11:55 +02:00
evgeny e0a287d797 Report nonempty member file directories precisely 2026-10-10 01:11:54 +02:00
evgeny 39946e3cd9 Reactivate offscreen browser after confirmation dialogs 2026-10-10 00:45:49 +02:00
evgeny eb9d379efc Wait for modal focus restoration in file shortcut test 2026-10-10 00:44:42 +02:00
evgeny 93a6277ca4 Keep file navigation focused and open entries with Enter 2026-10-10 00:41:18 +02:00
evgeny 043257cabf Add Delete and Backspace shortcuts to group files 2026-10-10 00:40:25 +02:00
evgeny 6d8c28f228 Verify delayed file MIME across success failure and cancellation 2026-10-10 00:22:22 +02:00
evgeny c61a6da884 Translate file manager export actions and status 2026-10-10 00:20:28 +02:00
evgeny 9640a18b03 Test file manager copy and deferred drag completion 2026-10-10 00:20:27 +02:00
evgeny 4049a3f884 Deliver remote file drags in one gesture using deferred MIME 2026-10-10 00:19:08 +02:00
evgeny e8ca27b590 Export group files through the clipboard and native file drags 2026-10-10 00:16:46 +02:00
evgeny 7d6b45ba5d Accept file drops inside the Group files window 2026-10-10 00:13:33 +02:00
evgeny 04e8a18c9a Translate file drop and upload queue messages 2026-10-09 23:32:47 +02:00
evgeny 17f5c0ecbd Cover footer file drops and upload queue lifecycle 2026-10-09 23:32:29 +02:00
evgeny d4cef49f88 Validate dropped file sources outside the GUI thread 2026-10-09 23:31:27 +02:00
evgeny 49ee66d49c Accept file drops across the channel footer 2026-10-09 23:30:33 +02:00
evgeny b631c4ed72 Add sequential member file uploads with destination locking 2026-10-09 23:29:56 +02:00
evgeny 91a3a621d0 Translate the own files button tooltip 2026-10-09 22:58:05 +02:00
evgeny 2e4ed0347b Add own files button to the channel list footer 2026-10-09 22:58:01 +02:00
evgeny 7f3247bfcc radio: add activation controls, live VAD meters and signal diagnostics 2026-10-09 16:11:11 +02:00
evgeny fddbba217d android: add level-triggered radio and inline activation settings 2026-10-09 00:06:55 +02:00
evgeny 44af542f61 chatgui: add radio activation by signal level with hysteresis 2026-10-08 17:53:16 +02:00
evgeny 2845270016 Add diagnostics for stalled PTT shortcut recording 2026-10-08 15:00:25 +02:00
evgeny d149bad5fc radio: keep drain diagnostics at burst boundaries and test queued PCM 2026-10-08 14:33:09 +02:00
evgeny f054f9c8d9 test: cover radio receive guard timing and playback progress 2026-10-08 14:30:21 +02:00
evgeny a1e8bfe80a android: keep radio VAD blocked until AudioTrack plays the receive tail 2026-10-08 14:28:08 +02:00
evgeny 06c35839ef chatgui: report radio output progress with device timing 2026-10-08 14:27:05 +02:00
evgeny fe7aabb44a radio: gate automatic VAD through receive and playback drain 2026-10-08 14:27:05 +02:00
evgeny c7c47d5ca3 android: use larger adaptive launcher icon with full background 2026-10-08 10:39:40 +02:00
evgeny dff2da0522 test: exercise Android replies through native bridge and media workers 2026-10-08 10:29:30 +02:00
evgeny 1ab51f3da1 android: translate channel reply actions and errors 2026-10-08 10:29:30 +02:00
evgeny 161af93347 android: connect channel reply navigation and camera context 2026-10-08 10:29:30 +02:00
evgeny f72fb177ba android: add reply menu and preserve drafts until submission completes 2026-10-08 10:29:30 +02:00
evgeny 30102f5f42 android: render reply quotes and support message long press 2026-10-08 10:29:30 +02:00
evgeny d28b7062d2 android: track saved replies and navigate original messages 2026-10-08 10:29:30 +02:00
evgeny 22e3c1b0a5 android: model channel reply drafts and native message references 2026-10-08 10:29:29 +02:00
evgeny 368f629948 android: expose reply submission and anchored history in bridge 2026-10-08 10:29:29 +02:00
evgeny 5f79d00f7a android: pass reply context through video preparation 2026-10-08 10:29:29 +02:00
evgeny 7aada92be9 android: preserve reply context while recording voice 2026-10-08 10:29:29 +02:00
evgeny 721160fbd9 android: carry channel reply references through file and photo sends 2026-10-08 10:29:08 +02:00
evgeny 4463f8870a Keep the viewed reply target stable until returning to latest messages 2026-10-08 00:50:17 +02:00
evgeny fde359b4af Set speaking state in the radio button color test fixture 2026-10-08 00:38:04 +02:00
evgeny ba0b21b999 Preserve newer composer edits when a reply finishes sending 2026-10-08 00:36:52 +02:00
evgeny adf1e9507a Fill reply history windows at boundaries and preserve read position 2026-10-08 00:36:52 +02:00
evgeny 3ea830fa00 Verify signed reply references across two peers over real PUSH 2026-10-08 00:32:06 +02:00
evgeny ec0666ca2a Keep quote activation separate from text selection gestures 2026-10-08 00:31:27 +02:00
evgeny 484d6f2fa4 Test signed replies, media completion and old-history lookup 2026-10-08 00:31:13 +02:00
evgeny a9e407a39f Test reply draft acknowledgements and quote clicks across content types 2026-10-08 00:30:08 +02:00
evgeny c6390575ad Translate channel reply actions and unavailable-message states 2026-10-08 00:30:07 +02:00
evgeny 7773a0c648 Keep return-to-latest action available in historical message windows 2026-10-08 00:27:02 +02:00
evgeny 38257a7f54 Connect reply sending, quote resolution and history navigation 2026-10-08 00:27:02 +02:00
evgeny 49e898b6f3 Deliver channel submission acknowledgements to the desktop composer 2026-10-08 00:24:35 +02:00
evgeny 562901c84f Add reply composer with cancellation and correlated send results 2026-10-08 00:24:20 +02:00
evgeny eebef4d14f Render clickable compact replies for text and media messages 2026-10-08 00:23:16 +02:00
evgeny 68df26d4a5 Read reply references and bounded history around message IDs 2026-10-08 00:21:58 +02:00
evgeny e811ac395a Preserve channel reply context through attachment preparation 2026-10-08 00:20:48 +02:00
evgeny 68037b25a5 Store signed replies by message ID and confirm channel submissions 2026-10-08 00:20:48 +02:00
evgeny 99e9b1ae85 Define channel reply references and submission result events 2026-10-08 00:19:35 +02:00
evgeny efebf8c5e8 Fix settings drafts, enlarge avatars, and show desktop version 2026-10-08 00:04:41 +02:00
evgeny f408abcf4e android: move advanced settings into a developer tab 2026-10-07 23:50:36 +02:00
evgeny 3aa0cd4c06 tests: add standalone Merkle sync reconnect and triangle stress test 2026-10-07 23:50:02 +02:00
evgeny 34a151e4cb chatgui: translate PTT latch and release hints 2026-10-07 21:44:14 +02:00
evgeny e4a84fd997 chatgui: verify latched PTT ownership and channel reset 2026-10-07 21:44:13 +02:00
evgeny 37485820b5 chatgui: latch radio PTT on double click and release on next press 2026-10-07 21:42:40 +02:00
evgeny 653853588a logging: preserve system diagnostics and inherit Android debug level 2026-10-07 21:21:33 +02:00
evgeny 1bb885fd11 logging: explain failed probes and RTT persistence errors 2026-10-07 21:15:39 +02:00
evgeny 7eb6d6ee68 logging: move socket mechanics and periodic probe decisions to trace 2026-10-07 21:14:38 +02:00
evgeny 15d460ee7c logging: explain BGP readiness, route selection and peer removal 2026-10-07 21:13:53 +02:00
evgeny a74a62079e logging: include REALITY handshake stage in connection failures 2026-10-07 21:11:34 +02:00
evgeny 6d54a7c102 logging: distinguish STCP probes from data link lifecycle 2026-10-07 21:11:12 +02:00
evgeny adc8b2a670 logging: describe STCP state and endpoint on timeout and close 2026-10-07 21:10:29 +02:00
evgeny b16a4995b7 logging: quiet routine audio and route polling, report audio recovery 2026-10-07 21:09:46 +02:00
evgeny e85a8c8310 logging: scope member file polling and worker traces to media 2026-10-07 21:09:27 +02:00
evgeny f3b1300628 Record Android radio speaker rollout and headless build issue 2026-10-07 19:35:46 +02:00
evgeny 2c407540a0 Test Android radio talk lifecycle and translate speaker status 2026-10-07 19:33:53 +02:00
evgeny e95fc090df Render Android speaker waves independently of local radio mute 2026-10-07 19:33:53 +02:00
evgeny bcda9b82f4 Track Android radio speakers and actual transmission by group 2026-10-07 19:33:53 +02:00
evgeny d5174262d8 Show speaking waves for locally muted radio members 2026-10-07 19:16:15 +02:00
evgeny 61818572cc Verify speaker timeout and record completed radio indication 2026-10-07 19:10:49 +02:00
evgeny 6496448569 Verify radio speaker icons through the bridge and actual PTT/VAD 2026-10-07 19:03:48 +02:00
evgeny 70fd5c077f Translate radio speaker status tooltips 2026-10-07 19:01:53 +02:00
evgeny e61fc8e392 Show silent, speaking and muted radio speakers in member rows 2026-10-07 19:01:53 +02:00
evgeny 5cbab70035 Track radio speakers independently of member list refreshes 2026-10-07 19:01:13 +02:00
evgeny 376695e410 Avoid Windows poll macro collision in activity monitor 2026-10-07 18:35:11 +02:00
evgeny c3468987cb Handle poll questions rendered as a standalone emoji 2026-10-07 17:33:26 +02:00
evgeny b6d8e822b4 Verify poll clicks preserve selection and message controls 2026-10-07 17:18:40 +02:00
evgeny ddcc36aa26 Open desktop chat polls with a single left click 2026-10-07 17:11:43 +02:00
evgeny 7e88d15e1d Validate unmanaged notification overlay independently of EWMH 2026-10-07 16:58:40 +02:00
evgeny 4a7f37b920 Use independent X11 notification overlay across desktops 2026-10-07 16:57:01 +02:00
evgeny bd163fffff Reapply all-desktop scope after notification mapping 2026-10-07 16:50:12 +02:00
evgeny e96e6b7751 Make desktop scope test independent of current workspace changes 2026-10-07 16:43:52 +02:00
evgeny 0d618964a7 Test popup remapping and keep headless input tests isolated 2026-10-07 16:38:33 +02:00
evgeny d62fae9e88 Verify notification desktop scope with a real window manager 2026-10-07 16:38:33 +02:00
evgeny 247479e411 Show Linux message notifications on all X11 desktops 2026-10-07 16:35:29 +02:00
evgeny 1108c773dc Own the independent popup and handle all keyboard mouse input 2026-10-07 16:05:34 +02:00
evgeny bc42df12f5 Keep notification independent of main window minimization 2026-10-07 16:05:33 +02:00
evgeny 2caa653f02 Trace cancellation of pending desktop activity queries 2026-10-07 16:02:10 +02:00
evgeny 16a9094770 Test live message metadata and document notification limits 2026-10-07 16:01:22 +02:00
evgeny c6cb3f0e8e Test notification timing, replacement, and activity fallbacks 2026-10-07 15:58:10 +02:00
evgeny 1da667b09e Complete notification translation and activity watcher cleanup 2026-10-07 15:57:15 +02:00
evgeny f2e35da1fe Wire notification platform dependencies and event diagnostics 2026-10-07 15:55:39 +02:00
evgeny 33c98a014f Notify about live incoming channel and direct messages 2026-10-07 15:54:25 +02:00
evgeny 261198ab47 Expose initial synchronization in channel message events 2026-10-07 15:53:39 +02:00
evgeny 77f2cdc34c Add message popup that expires after first user activity 2026-10-07 15:52:54 +02:00
evgeny 40b8e1a740 Add desktop user activity monitoring with platform fallbacks 2026-10-07 15:51:43 +02:00
evgeny 3279aa86ce Fix Windows system DLL detection in chatgui deployment 2026-10-07 14:07:32 +02:00
evgeny e5d8c34514 Fix desktop uasync readiness for radio member mute requests 2026-10-07 13:49:06 +02:00
evgeny 7e19cbaf3a Add local per-member radio mute controls on desktop and Android 2026-10-07 13:35:25 +02:00
evgeny a069d11ef9 Protect REALITY STCP traffic with authenticated TLS-like records 2026-10-07 12:30:45 +02:00
evgeny a49cf5434e Add randomized ETCP padding and isolate STCP CTR streams per connection 2026-10-07 10:45:42 +02:00
evgeny 1f5b42845b Document horizontal pickers and the signed single-reaction rule 2026-10-07 08:24:25 +02:00
evgeny b1bf094bfd Verify signed multi-reaction rejection and document the single-choice rule 2026-10-07 08:17:52 +02:00
evgeny 066fa8552d Display the reaction picker as one horizontal row in both clients 2026-10-07 08:14:50 +02:00
evgeny 0f14abd6c8 Test single reaction replacement, cancellation and multi-emoji rejection 2026-10-07 08:12:32 +02:00
evgeny a444f28134 Enforce one reaction per author and replace the previous emoji atomically 2026-10-07 08:12:32 +02:00
evgeny 87501a24fe Document reactions inside the timestamp row and device validation state 2026-10-07 02:06:01 +02:00
evgeny 033c24881c Verify reaction rows remain inside bubbles and grow with live updates 2026-10-07 02:00:13 +02:00
evgeny 3dbbe02f69 Keep desktop reactions and timestamp inside the bubble bottom row 2026-10-07 01:53:18 +02:00
evgeny 1f4266f8b1 Move Android reactions inside bubbles beside the timestamp 2026-10-07 01:45:43 +02:00
evgeny 2cf0f9c928 Document transparent reaction controls and verified desktop interactions 2026-10-07 01:35:13 +02:00
evgeny aaa05e761e Check reaction clicks against downloadable media and portable preview paths 2026-10-07 01:30:04 +02:00
evgeny 4f0870b961 Verify transparent reaction footer clicks across text and media layouts 2026-10-07 01:28:56 +02:00
evgeny fc79fa7345 Keep message footer layout and click gesture states consistent 2026-10-07 01:27:04 +02:00
evgeny f71e5531e0 Open reaction picker from desktop heart and toggle existing reactions 2026-10-07 01:22:34 +02:00
evgeny 00ca006d26 Render and hit-test reaction footer for every desktop message type 2026-10-07 01:21:52 +02:00
evgeny 29ee55d751 Enlarge desktop chat and member avatars 2026-10-07 01:18:31 +02:00
evgeny aa58c7fd3d Place transparent outline reaction heart below Android message bubbles 2026-10-07 01:16:33 +02:00
evgeny f8d0acb38d Record root certificate repair and pending SM device verification 2026-10-07 00:56:41 +02:00
evgeny 0218ea9ddd Require channel signatures for root feedback authors and verify profile copies 2026-10-07 00:47:32 +02:00
evgeny 9299c95f10 Verify root formation and invitation certificate replication 2026-10-07 00:46:36 +02:00
evgeny a9a65d53bf Preserve invitation certificates and accept confirmed roots without profile changes 2026-10-07 00:44:50 +02:00
evgeny 7cddb47edc Record verified SM and local history repair 2026-10-07 00:33:52 +02:00
evgeny 8cd54f9802 Verify restored schema constraints and legacy index fixture 2026-10-07 00:27:35 +02:00
evgeny a014d518bf Test atomic history schema repair and preserved message data 2026-10-07 00:26:59 +02:00
evgeny 2f5a49b011 Repair existing chat history tables with stable message IDs 2026-10-07 00:25:11 +02:00
evgeny 918a3cc061 Merge chat polls and reactions into master 2026-10-06 23:08:07 +02:00
evgeny 1f026baf45 Show a circular crop preview in the desktop avatar editor 2026-10-06 23:05:21 +02:00
evgeny a8c81f7e2b Record verified poll implementation and completed checks 2026-10-06 22:56:31 +02:00
evgeny 255853dd76 Document complete poll protocol and verified recovery behavior 2026-10-06 22:47:06 +02:00
evgeny 587a980081 Document poll creation, voting and reaction commands 2026-10-06 22:43:13 +02:00
evgeny bbc8b7693a Restart member and feedback synchronization over existing transports 2026-10-06 22:40:47 +02:00
evgeny 9edc76e5eb Fix Android avatar crop layout and circular preview
Keep the crop actions inside the available dialog window, show a circular mask, and clamp gesture limits to prevent float rounding crashes.
2026-10-06 22:37:04 +02:00
evgeny 7f4a1a66fa Include transport state definitions in poll network regression 2026-10-06 22:33:52 +02:00
evgeny 0c16c2b09a Wait for authenticated bootstrap transport before opening group requests 2026-10-06 22:33:35 +02:00
evgeny 67d1718450 Measure proxy test durations with a monotonic clock 2026-10-06 22:31:53 +02:00
evgeny e86de44259 Test poll synchronization through three nodes and chat restart 2026-10-06 22:30:52 +02:00
evgeny 4966600d52 Localize desktop interaction errors 2026-10-06 22:21:13 +02:00
evgeny 6686dd1815 Translate desktop poll and reaction controls 2026-10-06 22:21:13 +02:00
evgeny 8dc7595e46 Preserve Android feedback state and supplementary Unicode characters 2026-10-06 22:15:44 +02:00
evgeny 1d05ff34c6 Connect Android poll screens and localize interaction controls 2026-10-06 22:15:04 +02:00
evgeny 44e0b7849e Add Android poll voting, results and message reaction components 2026-10-06 22:14:29 +02:00
evgeny 0a615913eb Drive Android feedback commands and live updates through the core thread 2026-10-06 21:45:59 +02:00
evgeny 8f8c8c267f Add typed poll and reaction states to Android message models 2026-10-06 21:45:59 +02:00
evgeny 2c31e3fe03 Expose UTF-8 feedback commands and full message identities on Android 2026-10-06 21:43:21 +02:00
evgeny 39e94fdfd3 Add message reaction menus and distinguish own reactions 2026-10-06 21:39:14 +02:00
evgeny cf869770fb Add desktop poll creation, voting and live result dialogs 2026-10-06 21:39:13 +02:00
evgeny b0933eb282 Keep avatar cache pruning tied to the originating Android account 2026-10-06 21:37:18 +02:00
evgeny ebc96712a2 Track feedback by message identity in desktop UI 2026-10-06 21:36:43 +02:00
evgeny c1048686d1 Route feedback state and command results to the desktop thread 2026-10-06 21:34:38 +02:00
evgeny 0a05a2095e Read stable message identities and decode escaped text in desktop 2026-10-06 21:34:38 +02:00
evgeny 52f4628fe0 Bound avatar manifest sizes before numeric conversion 2026-10-06 21:34:15 +02:00
evgeny e068fe5d3a Test poll codecs, voting rules and delayed source messages 2026-10-06 21:31:20 +02:00
evgeny da2c13dcf1 Add CLI poll creation, voting and emoji reaction commands 2026-10-06 21:26:37 +02:00
evgeny f99a8c1da2 Expose polls and reactions through headless control safely 2026-10-06 21:26:37 +02:00
evgeny ef476bf4e2 Use canonical reaction and answer payloads in feedback tests 2026-10-06 21:24:53 +02:00
evgeny 48d7f2b21b Build poll modules and adapt media insertion callbacks 2026-10-06 21:24:53 +02:00
evgeny 17444b8c8a Evaluate delayed answers after their source message arrives 2026-10-06 21:24:14 +02:00
evgeny 60b4870549 Pass stable identities to insertion callbacks and bound signed bodies 2026-10-06 21:24:13 +02:00
evgeny 865e4cda59 Log delayed answer validation and keep JSON percentages locale independent 2026-10-06 21:23:37 +02:00
evgeny e6db9fef60 Validate feedback data and provide poll command snapshots 2026-10-06 21:23:37 +02:00
evgeny 0af35630d1 Expose full message data and stable identities in listings 2026-10-06 21:17:24 +02:00
evgeny a4ca31f406 Return submitted message identity and encode JSON safely 2026-10-06 21:17:24 +02:00
evgeny d0aa60fe1a Define canonical poll, answer and reaction formats 2026-10-06 21:15:57 +02:00
evgeny d40f58d79a Add message identities and plan the remaining feedback work 2026-10-06 21:12:53 +02:00
evgeny 3e1cc8af85 Map avatar crop frames using full original dimensions on both clients 2026-10-06 21:10:24 +02:00
evgeny 8676893d59 Record feedback implementation milestone and remaining work 2026-10-06 20:17:48 +02:00
evgeny 04ff801531 Document Merkle v3 and signed feedback state contracts 2026-10-06 20:11:25 +02:00
evgeny 6437bf780b Include feedback source in the core library source list 2026-10-06 20:09:48 +02:00
evgeny 4f4e63bb0b Test feedback signatures, versions, cancellation and atomic pages 2026-10-06 20:08:53 +02:00
evgeny f6cc3b6e53 Make feedback roots depend on signed state and validate rebuilt keys 2026-10-06 20:07:44 +02:00
evgeny 982e5a06ab Integrate feedback tables with channel and sync lifecycle 2026-10-06 20:06:39 +02:00
evgeny 8121f93d2e Build feedback model and expose committed state events 2026-10-06 20:06:38 +02:00
evgeny 641a8318a8 Add signed versioned feedback storage and Merkle adapter 2026-10-06 20:05:18 +02:00
evgeny a0fbf51be0 Describe avatar object paths consistently 2026-10-06 20:04:40 +02:00
evgeny 17dd06221a Log Android gallery selections and finish image gestures safely 2026-10-06 20:04:40 +02:00
evgeny cdc5474279 Document avatar bundles, crop selection and original-image galleries 2026-10-06 20:02:45 +02:00
evgeny cee3228e5f Separate each Android gallery swipe from pinch and pan gestures 2026-10-06 20:02:45 +02:00
evgeny 864a250eea Preserve input cursor when member page outputs alias it 2026-10-06 20:02:41 +02:00
evgeny 3f4df4233b Keep member avatar drags separate from viewer clicks 2026-10-06 20:01:38 +02:00
evgeny 1f9dbd9dea Open desktop message avatars only after a completed click 2026-10-06 20:01:38 +02:00
evgeny 7d69404532 Verify five centered previews while navigating six avatar originals 2026-10-06 20:00:58 +02:00
evgeny 49dc837df2 Correct all member dataset arguments in callers 2026-10-06 19:59:43 +02:00
evgeny f0314ab51c Test dataset isolation and colliding cursor prefixes 2026-10-06 19:59:00 +02:00
evgeny 3be5cdd365 Show Android preview loading states and retry failed neighbors 2026-10-06 19:58:37 +02:00
evgeny 96af98f063 Exercise avatar crop gestures, gallery navigation and EXIF originals 2026-10-06 19:58:36 +02:00
evgeny f6bfd36a6c Include dataset identity in Merkle diagnostics 2026-10-06 19:57:58 +02:00
evgeny 1350c95db6 Identify desktop crop controls for interaction checks 2026-10-06 19:57:32 +02:00
evgeny bc7864466c Offer retries for desktop avatar catalog and image failures 2026-10-06 19:57:32 +02:00
evgeny fad96603a7 Display gallery loading and failure placeholders in the image viewer 2026-10-06 19:57:32 +02:00
evgeny 894b24698e Update Merkle integration tests and channel index cleanup 2026-10-06 19:57:25 +02:00
evgeny 5e146f20bc Adapt member sync and protocol tests to dataset cursors 2026-10-06 19:56:52 +02:00
evgeny 539bfd6db9 Extend Merkle sessions with datasets and full record cursors 2026-10-06 19:56:17 +02:00
evgeny c36fd3bfc8 Remove obsolete Android PNG user-avatar preparation 2026-10-06 19:55:20 +02:00
evgeny feccea21a5 Keep desktop crop workers owned until their dialog closes 2026-10-06 19:55:20 +02:00
evgeny 37b80b612b Restore Android gallery selections and validate editor source formats 2026-10-06 19:55:20 +02:00
evgeny 5b4b17ae49 Keep selected Android originals while the catalog finishes loading 2026-10-06 19:51:54 +02:00
evgeny 43c300cba3 Release Android crop sources rejected after account changes 2026-10-06 19:51:53 +02:00
evgeny cbe6da09ee Translate desktop avatar editor and gallery controls 2026-10-06 19:51:53 +02:00
evgeny 27fd9ee62d Share avatar metadata downloads and cancel closed desktop galleries 2026-10-06 19:48:24 +02:00
evgeny 6bd6ea540c Cancel avatar transfers after their last Android viewer closes 2026-10-06 19:48:24 +02:00
evgeny 7e5b60aa10 Include WebP runtime dependencies and avatar test painter 2026-10-06 19:48:24 +02:00
evgeny 7d71b08def Validate avatar object types and distinguish original downloads 2026-10-06 19:46:59 +02:00
evgeny a221452cb6 Correct Kotlin types in avatar catalog and crop coordinates 2026-10-06 19:46:59 +02:00
evgeny 863441c418 Test Android avatar catalog pagination and updated member snapshots 2026-10-06 19:45:19 +02:00
evgeny ab22277e7b Test avatar originals, selected lossy crops and storage limits 2026-10-06 19:44:56 +02:00
evgeny 32eaf9df04 Translate Android avatar crop and empty-gallery states 2026-10-06 19:43:51 +02:00
evgeny af6cd31e71 Open Android avatar galleries with originals and five previews 2026-10-06 19:43:27 +02:00
evgeny 87b8c6bd3a Use exact image coordinates in the desktop crop editor 2026-10-06 19:42:22 +02:00
evgeny 5f3725b72f Add Android avatar crop selection before publication 2026-10-06 19:42:22 +02:00
evgeny d1080129d2 Publish complete Android avatar bundles after crop confirmation 2026-10-06 19:41:02 +02:00
evgeny ea6b895df9 Load Android gallery objects and catalogs through the shared cache 2026-10-06 19:41:01 +02:00
evgeny f27419165d Preserve Android avatar originals and encode selected WebP crops 2026-10-06 19:38:40 +02:00
evgeny 6e9a50f2ec Bridge Android avatar catalogs and per-object transfer limits 2026-10-06 19:37:45 +02:00
evgeny 07aa436380 Decode avatar manifests and paginated catalogs on Android 2026-10-06 19:37:45 +02:00
evgeny c4c11ad813 Build avatar UI and deploy the desktop WebP codec 2026-10-06 19:36:35 +02:00
evgeny fd8054bc7b Open user avatar galleries from desktop member icons 2026-10-06 19:36:35 +02:00
evgeny 3a793e25b3 Open user avatar galleries from desktop messages 2026-10-06 19:36:34 +02:00
evgeny 9970b2235c Show desktop avatar originals with five centered previews 2026-10-06 19:36:00 +02:00
evgeny 4134200b4e Allow avatar galleries to display validated cached images 2026-10-06 19:35:59 +02:00
evgeny ccae89fb60 Add desktop square crop editor before avatar upload 2026-10-06 19:35:59 +02:00
evgeny 1a936f33ef Upload complete avatar bundles and page desktop galleries 2026-10-06 19:34:10 +02:00
evgeny 68fb29e385 Add avatar gallery sources and manifest-aware desktop cache 2026-10-06 19:33:24 +02:00
evgeny f0cc198f72 Prepare avatar originals, WebP crops and hashed manifests 2026-10-06 19:32:22 +02:00
evgeny 17491082d4 Validate signed profile avatar manifest references 2026-10-06 19:29:42 +02:00
evgeny c80e82d104 Define member avatar references as manifest objects 2026-10-06 19:29:37 +02:00
evgeny 50efddd5f0 Allow immutable avatar originals and manifests in storage 2026-10-06 19:29:17 +02:00
evgeny 40eafafe3d chatgui: verify drag highlight cleanup and attachment file picker reuse 2026-10-06 19:11:12 +02:00
evgeny 348cd855b9 chatgui: detach file drop callbacks before composer child destruction 2026-10-06 19:06:37 +02:00
evgeny a175129f93 chatgui: complete audio dependencies for composer integration tests 2026-10-06 19:04:31 +02:00
evgeny 9c7442544b chatgui: add Qt integration tests for file drops and recipient changes 2026-10-06 19:02:04 +02:00
evgeny cc8e47cfc8 chatgui: localize file drop hints and attachment rejection messages 2026-10-06 19:00:18 +02:00
evgeny 0279ed84a9 chatgui: stage dropped files through the attachment composer 2026-10-06 18:58:33 +02:00
evgeny 7b1143b9d4 android: hide download overlay on locally available photos 2026-10-06 17:57:36 +02:00
evgeny 27fb1426d9 android: cover decimal channel IDs from SM in avatar regression tests 2026-10-06 17:34:03 +02:00
evgeny eae3ef5c32 android: parse decimal channel IDs for avatar requests and group keys 2026-10-06 17:32:43 +02:00
evgeny b3c0a2fa7b android: harden avatar cache reads and refresh current download sources 2026-10-06 16:39:26 +02:00
evgeny 15dbb6fe51 android: test avatar event framing, limits and member updates 2026-10-06 16:32:36 +02:00
evgeny 9c70eab075 android: cancel avatar operations before native teardown and bound image preparation 2026-10-06 16:30:54 +02:00
evgeny 15d0772a20 android: preserve avatar references in JSON refresh and live member snapshots 2026-10-06 16:29:06 +02:00
evgeny 57888f8d9e android: display member avatars in conversations and member details 2026-10-06 16:27:35 +02:00
evgeny c77066162a android: show group icons and open properties from chat header 2026-10-06 16:25:51 +02:00
evgeny b9d41d179d android: add avatar editors and localized group properties dialog 2026-10-06 16:24:25 +02:00
evgeny 73019cfd83 android: add shared avatar components and guard picker account identity 2026-10-06 16:22:24 +02:00
evgeny 22aa5d47a5 android: load and cache user and signed group avatars asynchronously 2026-10-06 16:20:45 +02:00
evgeny 8efe075679 android: add asynchronous avatar JNI operations and wire models 2026-10-06 16:15:00 +02:00
evgeny 714370a1a2 Verify direct deployment after a failed Qt deployment tool 2026-10-06 15:53:03 +02:00
evgeny f316554ca3 Retain deployment diagnostics and model executable runtime imports 2026-10-06 15:48:10 +02:00
evgeny e7be94a3d2 Test Qt runtime deployment and missing plugin dependencies 2026-10-06 15:46:46 +02:00
evgeny c4217a8aa6 Verify runtime dependencies and deploy matching Qt plugins every build 2026-10-06 15:44:58 +02:00
evgeny 3d277349be Deploy the selected Qt version and recognize Windows system DLLs 2026-10-06 15:40:54 +02:00
evgeny c81e8e5025 Link Winsock after uasync and include socket headers first 2026-10-06 15:11:16 +02:00
evgeny e9e38ee029 Guard the file protocol against the Windows menu macro 2026-10-06 14:55:31 +02:00
evgeny ed3815a406 Use the portable remove command in the browser and storage tests 2026-10-06 14:54:23 +02:00
evgeny fe03feaf4a Avoid the Win32 MF_REMOVE macro in the file protocol 2026-10-06 14:54:23 +02:00
evgeny e8365fdec6 Upgrade group avatar columns before notifying database readiness 2026-10-06 14:04:49 +02:00
evgeny 5c772aeecf Preserve channel identity in member adapter fixtures 2026-10-06 14:04:49 +02:00
evgeny 2bcba58131 Reject group avatar commands outside a running chat service 2026-10-06 13:59:17 +02:00
evgeny b6a5f98d41 Document 16 KiB signed group avatars and 4 KiB wire blocks 2026-10-06 13:57:12 +02:00
evgeny d0173e1b73 Test atomic schema upgrades and report precise avatar validation errors 2026-10-06 13:57:12 +02:00
evgeny f8d95fcb25 Initialize avatar peers from the current group session epoch 2026-10-06 13:52:19 +02:00
evgeny 35ce2a2b93 Check group avatar encoding, rendering and key-based controls 2026-10-06 13:51:18 +02:00
evgeny 3b47a56076 Trace group avatar assembly resets and block mismatches 2026-10-06 13:49:54 +02:00
evgeny 46f4ce7091 Test real group avatar block delivery, permissions and recovery 2026-10-06 13:48:14 +02:00
evgeny 4062ecdfb1 Display group avatars and restrict editing to group key holders 2026-10-06 13:44:49 +02:00
evgeny 8e3ff6fd81 Deliver group avatar publication results through the GUI bridge 2026-10-06 13:44:49 +02:00
evgeny 2f9dd6a27b Decode and prepare bounded group avatar images outside the GUI thread 2026-10-06 13:43:49 +02:00
evgeny a816b842e5 Read group avatar metadata separately from image bytes 2026-10-06 13:43:49 +02:00
evgeny 56897368b3 Own group avatar synchronization in the chat service lifecycle 2026-10-06 13:41:55 +02:00
evgeny f67f090b2a Synchronize signed group avatars in bounded 4 KiB blocks 2026-10-06 13:41:29 +02:00
evgeny e3745f279e Store signed group avatars without replacing channel keys 2026-10-06 13:37:18 +02:00
evgeny 8173677475 Avoid refreshing avatar controls on unchanged member telemetry 2026-10-06 13:04:57 +02:00
evgeny 74bd4eed7c Keep outdated avatar transfers from delaying new references 2026-10-06 13:03:53 +02:00
evgeny ae461e746f Update Android consumers for extended member snapshots 2026-10-06 13:02:01 +02:00
evgeny 8f83167c0f Forward full join profiles and document canonical profile lengths 2026-10-06 12:58:00 +02:00
evgeny 6d3187db57 Keep profile controls consistent with the saved avatar server 2026-10-06 12:56:20 +02:00
evgeny 5276cd0fa0 Simplify profile updates and report database read failures 2026-10-06 12:54:44 +02:00
evgeny 80c81502fb Protect avatar paths across platforms and initialize join fixture nodes 2026-10-06 12:53:05 +02:00
evgeny 9bfdf3ea41 Allow avatar replacement alongside downloads and correct join fixtures 2026-10-06 12:51:22 +02:00
evgeny dc156c7f2b Test bounded downloads and late completion after avatar replacement 2026-10-06 12:49:26 +02:00
evgeny d946a1b93a Finalize unsigned initial profiles before channel bootstrap 2026-10-06 12:48:20 +02:00
evgeny cebd138190 Expose profile diagnostics in verbose join integration runs 2026-10-06 12:47:20 +02:00
evgeny 8b327a3f27 Bound upload cache growth and reject stale transfer completions 2026-10-06 12:46:32 +02:00
evgeny 972910c763 Verify profile signatures survive avatar and display name updates 2026-10-06 12:45:45 +02:00
evgeny 9842de7ab7 Document avatar storage, profile exchange and client caching 2026-10-06 12:45:13 +02:00
evgeny c04f51d424 Use bounded 16-bit profile lengths in join member records 2026-10-06 12:44:18 +02:00
evgeny 76c02012eb Preserve complete signed profiles in channel bootstrap messages 2026-10-06 12:44:18 +02:00
evgeny c0a4bc7d89 Persist the desired avatar reference atomically 2026-10-06 12:43:11 +02:00
evgeny 9279ded7f3 Read complete signed profiles and escape initial display names 2026-10-06 12:43:11 +02:00
evgeny 3e1641aaf0 Persist avatar removal intent and prioritize profile changes 2026-10-06 12:41:05 +02:00
evgeny f2d2e6be8f Test avatar immutability, hash validation and quota accounting 2026-10-06 12:40:07 +02:00
evgeny 405755f06a Reuse shared channel avatars in direct message conversations 2026-10-06 12:40:07 +02:00
evgeny b8ddf0f52d Translate avatar profile controls and operation statuses 2026-10-06 12:38:51 +02:00
evgeny e642551dfe Expose signed storage roles independently of route priority 2026-10-06 12:38:11 +02:00
evgeny 2fe5f34c5c Trace avatar discovery and profile events for integration checks 2026-10-06 12:37:25 +02:00
evgeny 8fb9129300 Verify large signed profiles alongside full owner records 2026-10-06 12:36:35 +02:00
evgeny eceb69c58e Test avatar publication, channel replication and persistent cache 2026-10-06 12:35:46 +02:00
evgeny 7582509792 Protect pending avatar uploads while bounding disk cache retention 2026-10-06 12:35:45 +02:00
evgeny 1988afa84d Notify profile changes with actual member properties 2026-10-06 12:33:26 +02:00
evgeny 247e3a2efe Add avatar upload, preview and removal to profile settings 2026-10-06 12:33:00 +02:00
evgeny 694ed8e263 Display cached user avatars in member lists and chat messages 2026-10-06 12:32:33 +02:00
evgeny 78bed5efe4 Route avatar events alongside file browser events in ChatGUI 2026-10-06 12:32:02 +02:00
evgeny 8ae45e9e39 Manage avatar replication, background downloads and shared cache 2026-10-06 12:31:16 +02:00
evgeny f91251be14 Add bounded persistent avatar cache and image normalization 2026-10-06 12:28:59 +02:00
evgeny e8a3d5fa16 Bound avatar transfers and enforce immutable hashed uploads 2026-10-06 12:27:32 +02:00
evgeny cbb1a03ccd Define canonical avatar names and size limits 2026-10-06 12:27:32 +02:00
evgeny 61ef23b885 Include avatar references in member snapshots and change events 2026-10-06 12:27:07 +02:00
evgeny 4fbb1dafe1 Preserve profile attributes and publish signed avatar references 2026-10-06 12:26:38 +02:00
evgeny 8029b6e3f5 Extend signed member profiles to 1024 bytes 2026-10-06 12:25:21 +02:00
evgeny 02bd10b005 Document incremental object counts and quota protocol version 4 2026-10-06 11:36:30 +02:00
evgeny 9a06d19984 Test incremental counts, empty replacements and startup-only scans 2026-10-06 11:33:57 +02:00
evgeny e6bf925a94 Translate file and directory statistics in the quota display 2026-10-06 11:29:43 +02:00
evgeny 64d46cdef0 Display file and directory totals in client quota snapshots 2026-10-06 11:29:43 +02:00
evgeny d1034cb4ea Maintain file and directory statistics incrementally per user 2026-10-06 11:25:18 +02:00
evgeny c8236373b1 Extend quota snapshots with file and directory counts 2026-10-06 11:25:18 +02:00
evgeny e76dc6cb82 Report actual directory creation separately from durability errors 2026-10-06 11:25:18 +02:00
evgeny 69f04dec87 Document per-user quotas and the client quota query 2026-10-06 00:29:28 +02:00
evgeny 092e3e3cdc Validate quota scan failures and deny unauthorized quota queries 2026-10-06 00:29:28 +02:00
evgeny fb9e7c3351 Translate quota display and reservation details 2026-10-06 00:25:07 +02:00
evgeny db6acf8662 Test quota display, stale responses and startup directory retries 2026-10-06 00:25:07 +02:00
evgeny 594017ba5f Test user quota isolation, reservations, restarts and wire queries 2026-10-06 00:22:17 +02:00
evgeny 4fdecb697b Show live user storage quota in the member file browser 2026-10-06 00:18:00 +02:00
evgeny 359a327ec4 Enforce per-user reservations and initialize storage at startup 2026-10-06 00:16:20 +02:00
evgeny 3b641a2423 Add the file quota query and per-user quota configuration 2026-10-06 00:14:03 +02:00
evgeny 3354c0b374 Initialize per-user file quota accounts from the storage tree 2026-10-06 00:11:21 +02:00
evgeny 25e2ffeea7 Stream member files with router backpressure and parallel transfer handles 2026-10-05 23:36:49 +02:00
evgeny f7e1752fc2 Document focused member file debug tracing 2026-10-05 21:52:12 +02:00
evgeny 977748b16c Expose fork ownership errors in file debug traces 2026-10-05 21:43:44 +02:00
evgeny c259a7330b Trace file router sessions and crypto decisions in debug 2026-10-05 21:41:46 +02:00
evgeny 0706ccce8a Trace browser state, transfer I/O and daemon PID 2026-10-05 21:36:43 +02:00
evgeny aca10e8268 Trace file bridge callbacks and use scoped VAD test logs 2026-10-05 21:34:39 +02:00
evgeny e913a27f0c Trace file storage and worker lifecycle in debug 2026-10-05 21:33:04 +02:00
evgeny 8982bb034e Move legacy sound and attachment errors to media 2026-10-05 21:30:37 +02:00
evgeny 0773a82342 Keep debug focused on file browser event delivery 2026-10-05 21:30:01 +02:00
evgeny b771e95997 Clear legacy GUI bridge and list messages from debug 2026-10-05 21:28:45 +02:00
evgeny 5b78ec04f6 Move old radio and attachment diagnostics out of debug 2026-10-05 21:26:09 +02:00
evgeny 5dea9394bf Trace member file requests in debug category 2026-10-05 21:25:02 +02:00
evgeny 5d1b8d26e7 Daemonize before creating core workers 2026-10-05 21:19:19 +02:00
evgeny 78a655bb05 Add asynchronous member file storage and desktop browser 2026-10-05 18:44:58 +02:00
evgeny 27d93ecaf9 Add English and Russian ChatGUI interface translations 2026-10-05 16:55:43 +02:00
evgeny 972c6b7f0a Raise rlottie shape budget and log truncated animations 2026-10-05 16:48:00 +02:00
evgeny 7423133229 Build bundled rlottie automatically before Windows ChatGUI 2026-10-05 14:41:03 +02:00
evgeny 5e309c27ca Vendor rlottie sources with CMake and MinGW build adjustments 2026-10-05 14:31:35 +02:00
evgeny 8feb7902b9 Search local rlottie build for its CMake package configuration 2026-10-05 14:15:25 +02:00
evgeny bba44fbe11 Always copy rlottie DLL during ChatGUI runtime deployment 2026-10-05 13:55:43 +02:00
evgeny bc6d372064 Treat imagehlp and bcryptprimitives as Windows system DLLs 2026-10-05 13:43:57 +02:00
evgeny e4c591e1e6 Disable ZXing size optimization for MinGW GCC string link issue 2026-10-05 13:38:16 +02:00
evgeny 05064f7f3d Refresh compiler runtime DLLs before Windows ChatGUI builds 2026-10-05 13:22:16 +02:00
evgeny 3cfa9f8da1 Compare cc1 startup across directories and inspect runtime DLLs 2026-10-05 13:12:11 +02:00
evgeny 586f62fd0a Diagnose Opus bands compilation and replay native Ninja command 2026-10-05 13:07:55 +02:00
evgeny 2c5293bff0 Extend compiler diagnostics with standalone and CMake Opus checks 2026-10-05 13:02:08 +02:00
evgeny a154bacffb Add MSYS2 UCRT64 compiler diagnostic scripts 2026-10-05 12:58:40 +02:00
evgeny 9aaac3cb20 Exclude POSIX tcp_io test scenarios from Windows compilation 2026-10-05 12:27:15 +02:00
evgeny 577d1bd72f Handle delayed TCP delivery in etcpmon regression checks 2026-10-05 12:22:39 +02:00
evgeny d62916edf0 Validate etcpmon response strings and close failed streams 2026-10-05 12:20:25 +02:00
evgeny 45efaf095d Add Windows regression coverage for etcpmon refresh lifecycle 2026-10-05 12:14:04 +02:00
evgeny 24906283fe Refresh etcpmon connections and preserve selection by peer ID 2026-10-05 12:11:43 +02:00
evgeny 9465ed4939 Fix etcpmon response caches and pending request lifecycle 2026-10-05 12:08:05 +02:00
evgeny 0dbdd47130 Fix Windows IPv6 socket option and const link address 2026-10-05 12:00:10 +02:00
evgeny 341da73398 Fix Windows getsockopt argument in TCP error callback 2026-10-05 11:46:54 +02:00
evgeny ba7146f90a Fix HTTP proxy framing, keep-alive and stream lifecycle 2026-10-05 09:27:23 +02:00
evgeny 9005c67b8b Skip Windows DLL deployment when runtimes are present 2026-10-05 09:27:11 +02:00
evgeny 0b72364f10 Recognize DirectWrite and spooler system imports 2026-10-04 23:00:23 +02:00
evgeny be9a953136 Read Windows DLL import dumps through a temporary file 2026-10-04 22:49:17 +02:00
evgeny 6e4b1ae9c4 Trace Windows import parsing and dependency resolution 2026-10-04 22:41:08 +02:00
evgeny ba2bafe56e Diagnose Windows DLL scanning and recognize system imports 2026-10-04 22:25:11 +02:00
evgeny d02c55dbcd Document web link handling, validation and Q8 verification 2026-10-04 19:18:00 +02:00
evgeny 4fcf45d545 Accept browser URL paths on Android and drag within the tested link 2026-10-04 19:12:18 +02:00
evgeny ec856cc73f Verify web link geometry, full addresses, mixed schemes and drag copying 2026-10-04 19:08:39 +02:00
evgeny 4ce7e29a37 Share URL boundary fixtures between Android and Qt tests 2026-10-04 19:08:39 +02:00
evgeny dc2b6c3d68 Keep invitation and gesture regressions on the generic link API 2026-10-04 19:07:11 +02:00
evgeny 2e41610693 Open Android web links from shared channel and PM message UI 2026-10-04 19:06:44 +02:00
evgeny e2472cb5c3 Localize failures opening external message links 2026-10-04 19:06:44 +02:00
evgeny c191b777b7 Annotate Android web links without altering addresses containing emoji 2026-10-04 19:06:44 +02:00
evgeny 0a060d04ca Parse Android message links with matching punctuation rules 2026-10-04 19:05:09 +02:00
evgeny a76ba87501 Open HTTP and HTTPS messages through the desktop URL handler 2026-10-04 19:05:08 +02:00
evgeny 01fa1bc552 Route message link activation through the Qt conversation view 2026-10-04 19:05:08 +02:00
evgeny 5b321c6304 Generalize Qt link gestures while preserving drag selection 2026-10-04 19:04:11 +02:00
evgeny 3482e4a327 Share Qt text layout for web links and invitations 2026-10-04 19:04:11 +02:00
evgeny 394ac93dc4 Extract message links with explicit schemes and punctuation boundaries 2026-10-04 19:03:11 +02:00
evgeny 93c0c5bdfc Show channel member inviter in Android details 2026-10-04 18:34:17 +02:00
evgeny b6fd489b8e Record compact invitation validation and Q8 installation 2026-10-04 18:21:08 +02:00
evgeny 35266d2a31 Verify copy through Qt keyboard action and log Android invite presentation 2026-10-04 18:12:58 +02:00
evgeny d14babea33 Create signed test channel with its final invitation title 2026-10-04 18:12:23 +02:00
evgeny af27eaa33b Size invitation JSON buffers to the escaping contract 2026-10-04 18:11:54 +02:00
evgeny ec7ec2a229 Document localized invitation titles and compact link presentation 2026-10-04 18:11:08 +02:00
evgeny f70d5c7460 Verify invitation title delivery and one-line full-link actions 2026-10-04 18:09:41 +02:00
evgeny 6ad89a4f6a Elide Qt invitation links with consistent layout and full-address selection 2026-10-04 18:08:43 +02:00
evgeny 12e09cb0ed Render one-line Android invitations and retain full links for copy 2026-10-04 18:07:46 +02:00
evgeny 0ce6e9e83a Expose invitation group tags and localized Android titles 2026-10-04 18:06:55 +02:00
evgeny 306aaa94ba Carry encrypted group name metadata in PM invitations 2026-10-04 18:06:26 +02:00
evgeny be65962e96 Record final history recovery validation 2026-10-04 17:57:04 +02:00
evgeny a6aea51ff3 Document invitation history recovery and device verification 2026-10-04 17:46:20 +02:00
evgeny 607bd5d475 Report history wait reasons and registration failures precisely 2026-10-04 17:20:07 +02:00
evgeny 387f95208d Keep message content type alive through JSON serialization 2026-10-04 17:16:58 +02:00
evgeny fb92f1a8c9 Verify history recovery when a group session loses readiness 2026-10-04 17:10:49 +02:00
evgeny 4cd52c27d5 Include transport definition in history fault injection test 2026-10-04 16:57:06 +02:00
evgeny a5c616ba0f Exercise history retry after a lost initial exchange 2026-10-04 16:56:24 +02:00
evgeny 65264137f8 Apply JOIN metadata through the transactional member model 2026-10-04 16:55:00 +02:00
evgeny b390f3c28f Cover pre-invitation history with both synchronization roles 2026-10-04 16:48:01 +02:00
evgeny 274f4f2d63 Prepare explicit group sessions in history protocol fixtures 2026-10-04 16:47:01 +02:00
evgeny eaac611465 Gate history on member synchronization and group readiness 2026-10-04 16:45:47 +02:00
evgeny 6a67cc2038 Prepare channel history before enabling network synchronization 2026-10-04 16:40:32 +02:00
evgeny 865657b3bd Expose group peer readiness transitions to subscribers 2026-10-04 16:39:19 +02:00
evgeny 5caa1131c8 Test Android member radio flags and update preservation 2026-10-04 16:10:41 +02:00
evgeny cf77fffaeb Keep the stroke import used by radio controls 2026-10-04 16:07:08 +02:00
evgeny 51467296c3 Test radio member snapshots, updates and route withdrawal 2026-10-04 16:05:40 +02:00
evgeny 334521ce3f Display active radios in Android member rows 2026-10-04 16:02:57 +02:00
evgeny 47082c4e2e Preserve Android radio presence across member refreshes 2026-10-04 16:02:57 +02:00
evgeny 658c248867 Request Android member snapshots on the core thread 2026-10-04 16:00:41 +02:00
evgeny 6c5c1ef0a0 Show enabled radios in desktop member rows 2026-10-04 15:59:45 +02:00
evgeny 0f9210e491 Share the desktop portable radio icon painter 2026-10-04 15:59:45 +02:00
evgeny 9dd2dffc80 Expose live radio subscriptions in member updates 2026-10-04 15:58:54 +02:00
evgeny 81d71690c2 Document PM invitation flow, lifetime and verification 2026-10-04 15:55:42 +02:00
evgeny 38c4c0772b Use explicit cursor index type for Qt 5 and Qt 6 2026-10-04 15:54:06 +02:00
evgeny 69442dcbb8 Start wrapped-link selection test inside the first link line 2026-10-04 15:52:18 +02:00
evgeny 17179c9c4a Use invite text layout for selection and wrapped emoji positions 2026-10-04 15:51:51 +02:00
evgeny 0e5778c013 Reuse PM automatic route resolution and check persistence after successful send 2026-10-04 15:50:38 +02:00
evgeny f02894f164 Release pending invitation dialogs when Android chat stops 2026-10-04 15:46:35 +02:00
evgeny 2afb7268a1 Restore PM group route and verify messaging after invite JOIN 2026-10-04 15:46:14 +02:00
evgeny efcc9a1761 Schedule member sync when joining over an existing transport 2026-10-04 15:43:15 +02:00
evgeny c800d9253d Trace group connection and membership after opening a received invitation 2026-10-04 15:39:06 +02:00
evgeny 92b41ec300 Test wrapped message links and per-member invitation button hit targets 2026-10-04 15:38:10 +02:00
evgeny 8e25ffb392 Wait for member replication after JOIN and include desktop diagnostics 2026-10-04 15:24:06 +02:00
evgeny 570e637795 Verify desktop invitation clicks preserve drag selection 2026-10-04 15:12:26 +02:00
evgeny b9af72e71d Test PM invitation cancellation, commit errors and joining another group 2026-10-04 15:12:26 +02:00
evgeny f07a5b7a83 Keep invite hit testing in the message presenter and show link cursor 2026-10-04 15:10:02 +02:00
evgeny 149c397b7b Include desktop diagnostic macros for invitation actions 2026-10-04 15:08:16 +02:00
evgeny 3c3f3896f7 Handle message invitation links in Android channel and PM screens 2026-10-04 15:04:14 +02:00
evgeny 68103d0aca Make invite links clickable in Android text with inline animated emoji 2026-10-04 15:03:54 +02:00
evgeny 442a2a7b04 Complete Android invitation dialog imports and show share link lifetime 2026-10-04 15:03:53 +02:00
evgeny d13663eb23 Add Android PM and member invitation buttons with group picker 2026-10-04 15:02:42 +02:00
evgeny e5a87bd043 Track Android PM invitations and joining from message links in ViewModel 2026-10-04 15:02:27 +02:00
evgeny f841c8eb8a Localize PM invitation actions, lifetime and error messages 2026-10-04 15:00:45 +02:00
evgeny 81f8f6d4d0 Use asynchronous post API for invitation requests 2026-10-04 15:00:44 +02:00
evgeny 9bdfb93b50 Expose PM invitation operations and membership checks through JNI 2026-10-04 14:59:08 +02:00
evgeny ef62a80eb8 Filter desktop invitation targets by local membership 2026-10-04 14:59:08 +02:00
evgeny 55d9e79983 Expose read-only channel membership for invitation UI 2026-10-04 14:59:07 +02:00
evgeny fb21f9c1af Show invitation lifetime and correlate desktop join results by group 2026-10-04 14:57:12 +02:00
evgeny d369ff968d Wire desktop invitation sending and joining from message links 2026-10-04 14:57:12 +02:00
evgeny b7821121c3 Activate desktop invite links on clicks while preserving drag selection 2026-10-04 14:56:11 +02:00
evgeny 56fe1cab95 Render and hit-test wrapped invitation links in desktop messages 2026-10-04 14:56:10 +02:00
evgeny 01aa692a56 Add group invitation button to desktop PM header 2026-10-04 14:54:59 +02:00
evgeny 44e75e661e Forward member invitation actions to main window 2026-10-04 14:54:58 +02:00
evgeny fe23f4ca56 Add invitation action to desktop member rows 2026-10-04 14:54:58 +02:00
evgeny 6bc6d28e9e Add desktop group picker for PM invitations with ten minute notice 2026-10-04 14:53:59 +02:00
evgeny fb68e66ca4 Dispatch PM invitation results to desktop UI 2026-10-04 14:53:58 +02:00
evgeny 6f99ad3103 Add cancellable PM invitation operation with correlated results 2026-10-04 14:51:33 +02:00
evgeny 123b47c612 Improve Android VAD lifecycle, audio settings and diagnostics 2026-10-04 14:36:52 +02:00
evgeny 20a1211e09 Add GPLv3 licensing and bundle third-party notices 2026-10-04 14:18:18 +02:00
evgeny a97e8bcc1f docs: update file inventory and module header contracts
Refresh both AGENTS guides and clarify module purpose, usage, ownership, threading, return values and wire formats in headers. Remove the unused timeout_heap freed_count field and unimplemented getter.
2026-10-04 13:10:01 +02:00
evgeny c19eff67f6 android chat: refine message bubble colors and shadows 2026-10-03 14:22:21 +03:00
evgeny 5c74b7cd3e android radio: add plus and minus to volume PTT icon 2026-10-03 14:21:20 +03:00
evgeny 8358210241 docs: record Android volume PTT panel verification 2026-10-03 14:13:48 +03:00
evgeny de54c2d059 android radio: add volume PTT toggle to radio panel 2026-10-03 14:08:17 +03:00
evgeny 40e6bd36c3 android radio: release volume PTT on focus loss and option disable 2026-10-03 14:08:17 +03:00
evgeny 1805b3dd27 android radio: track independent manual PTT owners 2026-10-03 14:08:17 +03:00
evgeny 163621ddfa Document verified desktop radio panel and VAD controls 2026-10-03 13:34:50 +03:00
evgeny c1bec9aca5 Wait for queued radio packets and verify persistent VAD settings 2026-10-03 13:29:33 +03:00
evgeny 33f276a69e Cover radio panel shutdown and microphone recovery states 2026-10-03 13:19:45 +03:00
evgeny a2bafb61c7 Own pending radio settings in cancellable uasync tasks 2026-10-03 13:18:09 +03:00
evgeny 312c2dd386 Test desktop VAD panel through Qt buttons and real audio workers 2026-10-03 13:16:15 +03:00
evgeny 8820e9a3c9 Build desktop radio panel and retain audio recovery coverage 2026-10-03 13:13:28 +03:00
evgeny 625846fa33 Apply shared audio changes without resetting saved preferences 2026-10-03 13:13:28 +03:00
evgeny afbce0efeb Load radio settings after core startup and apply from dialog 2026-10-03 13:12:17 +03:00
evgeny 9f41415dc7 Expose radio VAD thresholds and independent compressor gains 2026-10-03 13:11:54 +03:00
evgeny d1e2220e54 Place radio controls above desktop chat messages 2026-10-03 13:10:38 +03:00
evgeny fa70093866 Add desktop radio panel with device menus and VAD switch 2026-10-03 13:09:38 +03:00
evgeny dcb07c9124 Add live desktop radio VAD settings and transmission state 2026-10-03 13:08:04 +03:00
evgeny ffd26c56b4 Reduce radio VAD prefetch to 600 milliseconds 2026-10-03 11:32:34 +03:00
evgeny 6985a2cf18 Document Android radio toolbar VAD control 2026-10-03 11:28:23 +03:00
evgeny 9ff1f5645f Move Android VAD activation to the radio toolbar 2026-10-03 11:18:04 +03:00
evgeny 099c9b7eac Record successful VAD validation and full test results 2026-10-03 01:53:03 +03:00
evgeny 13b5136b92 Document radio VAD compressor behavior and validation 2026-10-03 01:50:31 +03:00
evgeny c9131fff39 Ship deterministic speech fixture for VAD regressions 2026-10-03 01:48:37 +03:00
evgeny 50e276cb94 Include previous audio context in streaming Silero inference 2026-10-03 01:48:37 +03:00
evgeny 9c8f635e63 Test VAD compression, quiet speech and independent prefetch audio 2026-10-03 01:34:27 +03:00
evgeny cd57597975 Expose radio VAD gain limit in Android settings 2026-10-03 01:30:31 +03:00
evgeny cbee3da2c9 Add independent compressor branch before radio VAD 2026-10-03 01:30:31 +03:00
evgeny 389fca1de0 Expose compressor gain and register separate radio VAD limit 2026-10-03 01:27:42 +03:00
evgeny 1e9264735b Fix desktop hover and emoji playback; share Android chat background 2026-10-02 23:19:13 +03:00
evgeny 35648a19ce Avoid Windows poll macro collision in PTT hotkey manager 2026-10-02 17:32:46 +03:00
evgeny 23368a5bbe Add Android boot startup, lazy settings, emoji and DM calls
Add battery optimization controls and configurable boot startup. Keep message sounds in the application and render general settings lazily to avoid ANRs. Add animated emoji controls and live BGP availability for direct-message calls on Android and desktop.
2026-10-02 17:18:24 +03:00
evgeny 66afcc8625 Add extensible Russian and English interface languages on Android 2026-10-02 16:05:33 +03:00
evgeny df346fc097 Log standby retry transitions and await the actual wake phase 2026-10-02 12:04:34 +03:00
evgeny 7ec5f931bb Defer automatic retries until Android standby wakes 2026-10-02 11:56:44 +03:00
evgeny f12bab8676 Include concrete transport definitions for UP checks 2026-10-02 11:52:36 +03:00
evgeny f4b273d0be Cover UP-based chat peer selection and fallback transitions 2026-10-02 11:50:28 +03:00
evgeny 1d189195f9 Advertise desktop clients and log unknown automatic candidates 2026-10-02 11:47:24 +03:00
evgeny dc34faaada Route chat retries through automatic peer selection 2026-10-02 11:44:46 +03:00
evgeny a7632cfee1 Select chat auto peers by device class and transport UP 2026-10-02 11:43:10 +03:00
evgeny 1c499dc230 Classify chat supernodes independently of address type 2026-10-02 11:37:09 +03:00
evgeny 01bc6e4cf0 Опиши контракты аудиотракта и результаты проверок 2026-10-02 10:22:47 +03:00
evgeny bd082a26bf Раздели аудиопотоки и привяжи AEC к фактическому общему выходу 2026-10-02 10:22:34 +03:00
evgeny d7fb0c83f9 Fix bounds and unsigned decoding in ETCP packet dumps 2026-10-02 02:52:32 +03:00
evgeny ac45554439 Fix ETCP INIT collision scope and backpressure regression tests 2026-10-02 02:25:40 +03:00
evgeny 217e5795bf Раздели PCM-сессии и исправь частоту и перемотку звука 2026-10-02 02:00:50 +03:00
evgeny 40b974b29c Исправь потоковый AGC и применяй mute после AEC 2026-10-02 01:56:10 +03:00
evgeny bdcf8b5634 Сохрани накопленные исправления аудио, async и диагностики 2026-10-02 01:49:32 +03:00
evgeny b1e3193899 Document typed PM media, shared preparation and completed validation 2026-10-01 19:22:06 +03:00
evgeny 7ae56f03b3 Keep Android video metadata truthful and remove obsolete composer imports 2026-10-01 19:16:27 +03:00
evgeny 203ae61eda Share bounded thumbnail workers and keep PM downloads in DM transport 2026-10-01 19:12:25 +03:00
evgeny c863b80917 Validate Android voice streams and keep playback cancellation off UI waits 2026-10-01 19:09:07 +03:00
evgeny dd8041b30f Retain picker cache ownership on native PM rejection 2026-10-01 19:06:19 +03:00
evgeny fe9441d63b Acknowledge Android PM file ownership only after native enqueue 2026-10-01 19:06:18 +03:00
evgeny 05476fb102 Test Qt voice file validation and decoder cancellation 2026-10-01 18:56:57 +03:00
evgeny 6ccf71b20d Exercise separate conversations and codec format boundaries 2026-10-01 18:56:57 +03:00
evgeny 418329962d Test pinned preparation targets, worker ownership and typed offline media 2026-10-01 18:55:34 +03:00
evgeny f2ff3ee5ab Report preparation rejection and release abandoned owned sources 2026-10-01 18:53:08 +03:00
evgeny 3b0609fe76 Reject corrupt voice containers and remove unused synchronous playback 2026-10-01 18:51:44 +03:00
evgeny 8e108425ae Track Android video probing and clean rejected cache files 2026-10-01 18:50:32 +03:00
evgeny 20322e7ecb Show PM voice and video delivery status in both shared renderers 2026-10-01 18:49:59 +03:00
evgeny 5092bdf9fe Validate capture format and diagnose capture thread failures 2026-10-01 18:48:57 +03:00
evgeny be8dbcf25e Remove duplicate GUI voice encoding and keep shared worker codec 2026-10-01 18:48:09 +03:00
evgeny 72233d2365 Quiesce Android capture before detaching PCM and consume PM cache files 2026-10-01 18:41:17 +03:00
evgeny 79f0f2a07f Keep Android media preparation status scoped to its conversation 2026-10-01 18:41:17 +03:00
evgeny 225e11265f Connect Qt preparation notifications to the conversation view 2026-10-01 18:39:31 +03:00
evgeny 6da6471204 Apply task-scoped media preparation events to the shared Qt composer 2026-10-01 18:39:31 +03:00
evgeny e4a57970f0 Show preparation state for the current Qt conversation only 2026-10-01 18:39:31 +03:00
evgeny 55b21fdcda Forward pinned attachment preparation events to Qt 2026-10-01 18:37:49 +03:00
evgeny 51a820ac82 Track PM encryption preparation and clean cancelled source files 2026-10-01 18:37:23 +03:00
evgeny c900adbc69 Report pinned preparation results and clean abandoned media outputs 2026-10-01 18:36:59 +03:00
evgeny b1ee7857f7 Identify media preparation events by conversation and task UUID 2026-10-01 18:36:59 +03:00
evgeny 318d72708c Preserve voice autoplay ordering while decoding in a worker 2026-10-01 18:29:43 +03:00
evgeny 172cc3a649 Update shared Qt playback controls after background decoding 2026-10-01 18:29:07 +03:00
evgeny d4a27d1975 Decode voice files asynchronously before starting Qt playback 2026-10-01 18:27:44 +03:00
evgeny c77cb9bb0a Adopt decoded voice buffers without large copies on the Qt thread 2026-10-01 18:27:44 +03:00
evgeny add016894b Use bounded independent Android cache files for each attachment 2026-10-01 18:24:12 +03:00
evgeny fd3fc971fa Return Android video recording to its original channel or PM 2026-10-01 18:19:37 +03:00
evgeny fef800ef7a Use the same Android media interface for channels and PM 2026-10-01 18:19:12 +03:00
evgeny 1cd366c0e1 Share Android conversation composition attachments and message lists 2026-10-01 18:16:23 +03:00
evgeny dbec6e75e8 Pin Android recording targets and stop capture outside the main thread 2026-10-01 18:13:35 +03:00
evgeny 80b4e6f3e9 Normalize PM voice and video into the Android message model 2026-10-01 18:12:14 +03:00
evgeny 6185869c6d Use fixed conversation targets for Android media preparation 2026-10-01 18:11:54 +03:00
evgeny 038fd173c2 Detach Android recordings for shared background preparation 2026-10-01 18:01:45 +03:00
evgeny 14935378bb Check queued worker completion cancellation and attachment codec tests 2026-10-01 18:00:02 +03:00
evgeny 0dc2b0135a Test typed PM delivery and strict voice attachment metadata 2026-10-01 17:59:16 +03:00
evgeny c58b92ecd7 Render typed PM media using shared Qt message roles 2026-10-01 17:57:43 +03:00
evgeny a281165450 Keep channel download commands out of the shared message delegate 2026-10-01 17:56:29 +03:00
evgeny 4f76b4d765 Dispatch Qt attachments using the target captured by the composer 2026-10-01 17:54:48 +03:00
evgeny d1f03f2f80 Enable shared Qt voice and video composition for PM 2026-10-01 17:53:41 +03:00
evgeny 0d3f2952c1 Move Qt recording buffers to background preparation without copies 2026-10-01 17:52:38 +03:00
evgeny ba409427c6 Bound media worker concurrency and reject incomplete file copies 2026-10-01 17:51:09 +03:00
evgeny 1b3794e9f9 Include common attachment preparation in both core targets 2026-10-01 17:50:46 +03:00
evgeny 555b6fcf86 Prepare voice and video before dispatching to a fixed conversation 2026-10-01 17:49:52 +03:00
evgeny 1bd310c859 Detach recorder compressors while preserving capture settings 2026-10-01 17:47:39 +03:00
evgeny 20acf975d9 Return final video metadata from the preparation worker 2026-10-01 17:46:55 +03:00
evgeny 984b9e8123 Keep PM attachment kinds scoped to files voice and video 2026-10-01 17:43:53 +03:00
evgeny c7310e1fad Register shared voice encoding in core build targets 2026-10-01 17:43:10 +03:00
evgeny a5f90ec207 Share voice file encoding and final PCM metadata across clients 2026-10-01 17:42:44 +03:00
evgeny 72d1d3f582 Validate PM attachment metadata before committing messages 2026-10-01 17:41:19 +03:00
evgeny 7b8e2cd96c Transfer temporary source ownership before launching PM workers 2026-10-01 17:40:49 +03:00
evgeny c1185b62c1 Carry typed encrypted metadata with PM file transfers 2026-10-01 17:39:48 +03:00
evgeny 7357a63fdf Define bounded typed attachment metadata for PM and GUI 2026-10-01 17:38:25 +03:00
evgeny adff64e483 Deliver media completions without polling or timer failure joins 2026-10-01 17:36:43 +03:00
evgeny 00e890e0c3 Reserve worker completions before starting background tasks 2026-10-01 17:36:12 +03:00
evgeny 142eefaf86 Record completed PM media integration and validation results 2026-10-01 17:00:55 +03:00
evgeny 7e780bb6a7 Document PM file custody, worker ownership and client integration 2026-10-01 16:44:38 +03:00
evgeny ef5cd63d9f Set injected control packet lengths so replay and quota checks execute 2026-10-01 16:40:53 +03:00
evgeny 7bd3e0d4de Test PM media rollback, quota, snapshots and transfer cancellation 2026-10-01 16:35:55 +03:00
evgeny 18ee21cf93 Allow receipt-authorized custody cleanup after role revocation 2026-10-01 16:34:00 +03:00
evgeny 934f1ccd1f Use the existing diagnostic macros for desktop PM errors 2026-10-01 16:32:14 +03:00
evgeny a90274f24c Test event-loop heartbeat during sustained PM file encryption 2026-10-01 16:31:51 +03:00
evgeny dc1110af4d Add Android PM attachment selection, saving and delivery labels 2026-10-01 16:29:12 +03:00
evgeny 1457275139 Update Android PM screens from asynchronous core events 2026-10-01 16:27:58 +03:00
evgeny 52e38ff5d6 Parse PM snapshots and delivery states in the Android repository 2026-10-01 16:27:58 +03:00
evgeny 8403697013 Post Android PM reads and file sends to the core event loop 2026-10-01 16:27:01 +03:00
evgeny d9eee4e520 Bound and safely serialize PM conversation snapshots 2026-10-01 16:25:52 +03:00
evgeny e562896d63 Define asynchronous conversation snapshots for PM clients 2026-10-01 16:25:52 +03:00
evgeny 7fee3ec551 Render asynchronous PM history and submit desktop file sends 2026-10-01 16:24:09 +03:00
evgeny 1ceb09062f Show PM file delivery state without invoking group downloads 2026-10-01 16:22:54 +03:00
evgeny 570f3dfab3 Enable original file attachments in the desktop PM composer 2026-10-01 16:22:32 +03:00
evgeny 4cabbc32f5 Fit file chunks within the signed router packet limit 2026-10-01 16:22:07 +03:00
evgeny 3d6f9c1be4 Sign media test fixtures with the derived Ed25519 seeds 2026-10-01 16:14:39 +03:00
evgeny 76497481f7 Test direct and offline DM media with separate supernode and storage nodes 2026-10-01 16:04:35 +03:00
evgeny 139356bd18 Deliver DM history snapshots asynchronously as JSON events 2026-10-01 16:00:39 +03:00
evgeny 1cdead18de Expose bounded DM history snapshots with text and media delivery state 2026-10-01 15:59:38 +03:00
evgeny b47918fad5 Keep media publication and durable deletion off the event loop 2026-10-01 15:57:30 +03:00
evgeny 55c9d79a01 Integrate atomic media registration and orderly transfer/worker shutdown 2026-10-01 15:53:53 +03:00
evgeny d7a12b24bf Coordinate offline DM media custody and durable deletion on supernodes 2026-10-01 15:52:05 +03:00
evgeny 41fc5902be Implement durable DM downloads, media-node custody, quota and expiry 2026-10-01 15:47:05 +03:00
evgeny e47c7fb2ac Allow file owners to revoke active transfers on custody expiry or deletion 2026-10-01 15:42:54 +03:00
evgeny 84c148acff Prepare and publish target-encrypted DM files with exact media receipts 2026-10-01 15:42:02 +03:00
evgeny d559cbf473 Add configurable seven-day DM custody TTL and reserved storage quota 2026-10-01 15:36:31 +03:00
evgeny 5e9a9059c8 Bind signed file descriptors to atomic DM enqueue and receive 2026-10-01 15:28:40 +03:00
evgeny 27fa3a4812 Define independent DM media metadata, receipts and custody contracts 2026-10-01 15:27:44 +03:00
evgeny 3d0ab35efe Register file transfer and DM media services in the common core 2026-10-01 15:22:10 +03:00
evgeny f39ba840b8 Add bounded signed file transfers with per-operation CM ownership 2026-10-01 15:21:44 +03:00
evgeny 6c86e380a8 Record DM stage validation and pending media delivery work 2026-10-01 15:06:08 +03:00
evgeny c63343cf2b Stop DM test event loops promptly on scenario failure 2026-10-01 14:51:37 +03:00
evgeny bd550159f0 Verify DM queues drain beyond one delivery batch after restart 2026-10-01 14:48:37 +03:00
evgeny 43c89ca1ed Document implemented DM guarantees and remaining media integration 2026-10-01 14:46:24 +03:00
evgeny 1740bec94f Test mailbox admission, exact deletion and late PUT replay 2026-10-01 14:43:50 +03:00
evgeny 0e0f611d3c Check atomic DM enqueue rollback and explain sequence conflicts 2026-10-01 14:41:33 +03:00
evgeny bd05ab6d3a Verify DM media record boundaries and corrupted artifacts 2026-10-01 14:35:43 +03:00
evgeny 467d2e7605 Prepare DM media as authenticated records using target key 2026-10-01 14:33:45 +03:00
evgeny 10de4f1c71 Commit DM activity timestamps together with message state 2026-10-01 14:28:47 +03:00
evgeny 794d7b70f3 Authorize DM custody by signed group role instead of NAT type 2026-10-01 14:27:54 +03:00
evgeny e6c78c17b7 Keep DM test diagnostics focused on delivery state 2026-10-01 14:24:28 +03:00
evgeny 3cdb3e6580 Create DM router streams before checking queue capacity 2026-10-01 14:24:18 +03:00
evgeny febc3419b0 Exercise DM reordering, rollback and signed acknowledgments 2026-10-01 14:22:46 +03:00
evgeny 937ef9fcef Test CCM limits and offline DM custody across restart 2026-10-01 14:20:19 +03:00
evgeny 5800902a42 Deliver offline DM through durable supernode custody 2026-10-01 14:18:50 +03:00
evgeny cc072cceb2 Persist DM outbox and verify exact delivery receipts 2026-10-01 14:14:46 +03:00
evgeny 67bbf80769 Fix DM CCM length setup and separate media nonces 2026-10-01 14:04:25 +03:00
evgeny ce5f5bb7d0 Improve Android onboarding, member UI and PTT permissions 2026-10-01 13:55:28 +03:00
evgeny 577bfb4e6f Record independent audio startup checks and phone updates 2026-10-01 03:05:00 +03:00
evgeny fe1d29f007 Check refill below the 60 ms reserve including SoundTouch 2026-10-01 02:59:50 +03:00
evgeny fbf50b093d Document independent call audio startup and delivery reserve 2026-10-01 02:58:45 +03:00
evgeny 5b0262d53f Diagnose refill expectation with the corrected reserve 2026-10-01 02:57:18 +03:00
evgeny 4a2efe21c7 Start Android capture and playback independently 2026-10-01 02:56:29 +03:00
evgeny 7d9f13b8bc Test startup backlog independently of delivery jitter 2026-10-01 02:51:04 +03:00
evgeny 50e3db81ea Separate playback backlog from adaptive call reserve 2026-10-01 02:49:35 +03:00
evgeny d8745fd303 Measure call jitter from media delivery timestamps 2026-10-01 02:48:53 +03:00
evgeny bc797550d9 docs: record SM startup timings after phone update 2026-10-01 02:25:49 +03:00
evgeny 5ebab7072c docs: record call startup validation results 2026-10-01 02:19:58 +03:00
evgeny 9ff4e68246 docs: describe call audio startup and loss tone gating 2026-10-01 02:14:15 +03:00
evgeny 00666499fe android: start call capture before playback setup 2026-10-01 02:12:44 +03:00
evgeny 5487bc8c20 call: suppress loss tone until first received PCM 2026-10-01 02:08:17 +03:00
evgeny a3f6b6e86e docs: outline simple Android call startup improvements 2026-10-01 02:00:56 +03:00
evgeny 6f66df61f7 docs: record false audio loss tone during call startup 2026-10-01 01:58:20 +03:00
evgeny 72b5a9887f chatgui: measure duplex audio stalls and record SM call evidence 2026-10-01 01:36:34 +03:00
evgeny 076cc3c617 docs: record full recovery checks and root socket test 2026-10-01 01:19:37 +03:00
evgeny 91a69b939c docs: record conn_mgr recovery validation and check timeout limitation 2026-10-01 01:15:15 +03:00
evgeny 969c085085 tests: resend withdrawn relay advertisements during recovery 2026-10-01 01:09:15 +03:00
evgeny 426fc2f4e8 conn_mgr: verify route loss and document handle lifetime recovery 2026-10-01 01:06:56 +03:00
evgeny 464575ef44 conn_mgr: exercise persistent retries with valid signed node updates 2026-10-01 01:01:49 +03:00
evgeny a65ef30d78 conn_mgr: resume recovery when local sockets change 2026-10-01 00:51:17 +03:00
evgeny 8f5f2b089c conn_mgr: keep demand alive across bounded recovery attempts 2026-10-01 00:50:44 +03:00
evgeny 111432aa64 conn_mgr: race connection probes and report call path with correct RTT 2026-10-01 00:42:47 +03:00
evgeny 1c51df039f Fix call gap tone to follow 500ms of missing jitter PCM 2026-09-30 21:14:37 +03:00
evgeny 9e3a417059 Synchronize standby transitions and immediate keepalive phase announcements 2026-09-30 20:42:40 +03:00
evgeny e914a1bdc1 Improve Android member details contrast 2026-09-30 20:13:25 +03:00
evgeny b36c685cfa Show ongoing Android calls on the lock screen 2026-09-30 19:42:12 +03:00
evgeny 3c5c9604b3 Keep Android calls active independently of screen and UI 2026-09-30 19:12:04 +03:00
evgeny fb37ca747a 1 2026-09-30 17:37:13 +03:00
evgeny 2d21c53b4b Improve call buffering, CM handle lifecycle and radio PTT controls 2026-09-30 17:31:53 +03:00
evgeny 752c1f140c Add unified Android dark theme and improve UI contrast 2026-09-30 13:41:45 +03:00
evgeny db3cacd33e Rename Android app to vibechat 2026-09-30 13:10:09 +03:00
evgeny 7ebc282eb2 Prioritize supernodes in invites and update chat background and ringtone 2026-09-30 12:56:59 +03:00
evgeny 0d44ada922 Improve Android radio capture lifecycle, VAD and PTT controls 2026-09-30 12:25:42 +03:00
evgeny 52ffb91101 Fix TCP proxy use-after-free during synchronous close 2026-09-30 10:43:02 +03:00
evgeny 745ec3dbd2 android radio: preserve output route on reconfigure, link input/output settings 2026-09-29 22:35:08 +03:00
evgeny 15958a078e android: self-managed Telecom ConnectionService for call audio routing 2026-09-29 21:26:24 +03:00
evgeny 61bf235ef4 radio: PTT double-tap latch + single-tap off (in-chat & overlay); compressor initial gain = mid-range 2026-09-29 20:00:28 +03:00
evgeny 556b0bc7b7 call/radio: mic AGC compressor + selectable input device; proximity lock 2026-09-29 19:34:23 +03:00
evgeny f12f61d7ee call: add SpeexDSP AEC (acoustic echo cancellation) for desktop + Android 2026-09-29 17:01:30 +03:00
evgeny 1176411b79 radio: Silero VAD + auto-PTT; chatgui/android: app icon and background 2026-09-29 16:19:25 +03:00
evgeny 74ca98b38f proxy: rename pcb guard, dedupe abort; tasks: FIN_WAIT_2 leak 2026-09-29 13:52:35 +03:00
evgeny 961a234d7d proxy: detach lwIP pcb on local close, guard abort against freed pcb
При half-close (exit закрылся первым, браузер держит соединение) pcb уходил в
TIME_WAIT, а conn_finish ждал flow.fin_sent (доставку FIN в exit) — при backpressure
это не наступало, pc->pcb оставался висячим. Таймер TIME_WAIT освобождал pcb без
err_cb, и CLOSE_ALL давал double free. Теперь pcb отсоединяется по завершении
локальной стороны, а tcp_abort/tcp_close защищены проверкой memory_pool_is_freed.
2026-09-29 13:37:00 +03:00
evgeny a73ae13df4 lwip/proxy: trace PCB lifetime in debug category, drop old noisy debug 2026-09-29 13:18:18 +03:00
evgeny d8cbe2e589 Add proxy bug list and lightsout game (desktop + Android) 2026-09-29 12:19:23 +03:00
evgeny e1946b208d Preserve proxy TCP streams across connect, backpressure and FIN 2026-09-29 04:00:34 +03:00
evgeny 09bc8c35c5 Release TUN proxy connections when lwIP aborts their PCB 2026-09-29 03:59:30 +03:00
evgeny f19ff11b36 Scope TCP proxy streams and restart messages to their peer 2026-09-29 03:58:53 +03:00
evgeny 268243feeb Isolate datagram proxy state and replies between instances and peers 2026-09-29 03:58:14 +03:00
evgeny 4be1e62251 Parse IPv4 options and reject malformed TUN proxy packets 2026-09-29 03:57:37 +03:00
evgeny 10b66e2ad0 Validate SOCKS5 authentication and destination addresses 2026-09-29 03:57:21 +03:00
evgeny 7a41fcf512 Notify TCP writers when the final synchronous send drains 2026-09-29 03:57:15 +03:00
evgeny 518df33b0e Fix UDP and ICMP packet construction at the TUN boundary 2026-09-29 03:56:55 +03:00
evgeny fdec5b65c5 Add independent radio compressor and Android audio controls 2026-09-29 03:37:47 +03:00
evgeny ce1f23fe46 Radio: stereo capture/playback with mono fallback; CAMCORDER source; bump RADIO_MAX_OPUS
- stereo capture: Android AudioRecord stereo (CAMCORDER source for stereo,
  voice sources fall back to CAMCORDER) + desktop separate miniaudio
  capture/playback devices (no asymmetric duplex)
- Opus stereo encode/decode, voice_jitter stereo (per-channel frame_size,
  slot-based SoundTouch)
- radio_audio_start takes explicit capture_channels (stereo->mono fallback)
- RADIO_MAX_OPUS 256->512 (128kbps stereo frames up to 320B)
- radio_capture_stereo setting + desktop radio settings UI (opus mode/bitrate,
  capture stereo)
- radio_send_to send stats logging (etcp_send ok/fail per second)
2026-09-29 02:31:41 +03:00
evgeny 331970a121 Radio: Opus mode/bitrate + mic source + PTT release tail; drop online status from member JSON 2026-09-29 00:40:10 +03:00
evgeny e3caf15f32 Guard routing loops and reconcile blocked transit against current paths
Track visited nodes outside the signed body for all routed packet types. Recheck queued transit routes without losing FIFO priority, reject unknown-group transit, and suppress BGP announcements caused only by RTT jitter.

Document routing rules and wire format. Validate graph churn, REINIT, encrypted transit, malformed paths, loop drops and queue cleanup; full suite: 106 passed, 1 skipped, plus proxy integration and sanitizer checks.
2026-09-28 15:36:47 +03:00
evgeny fa9e102271 Preserve config string terminator when preparing key updates 2026-09-28 15:16:57 +03:00
evgeny 39ce249361 Prune unconfirmed BGP paths and restart stalled exchanges 2026-09-28 14:58:13 +03:00
evgeny b76e6553bc Reconcile BGP paths and advertise selected routes per neighbor 2026-09-28 14:37:44 +03:00
evgeny 546e0c185d Fix WITHDRAW removing nodes with surviving paths 2026-09-28 14:20:55 +03:00
evgeny 4503bdfa7e diagnostics: explain and rate-limit rejected ETCP session data 2026-09-28 13:16:39 +03:00
evgeny c63e93715f fix: accept invite public keys with a leading zero byte 2026-09-28 12:26:13 +03:00
evgeny 904a9fbda1 docs: refresh development guides for current architecture 2026-09-28 12:10:39 +03:00
evgeny 01fcefdec5 docs: clarify core, group and chat header contracts 2026-09-28 12:01:08 +03:00
evgeny 72fee9e245 Separate core, UTUN and chat lifecycles with owned asynchronous work 2026-09-28 11:39:08 +03:00
evgeny 5838e63cde Use shared group requests for config and chat connection planners 2026-09-28 11:07:25 +03:00
evgeny 0756a6e020 Serialize group topology lazily through a cancellable backpressure sender 2026-09-28 02:45:00 +03:00
evgeny 792776a1f6 Negotiate group sessions and scope topology messages to peer epochs 2026-09-28 02:15:54 +03:00
evgeny 52ef7a4b3c Own recovery attempts in group sessions and require restored routes 2026-09-27 23:06:48 +03:00
evgeny 66c59501fc Track topology exchange readiness per group and peer 2026-09-27 22:14:03 +03:00
evgeny bce17d9cfc Route UTUN configuration and group ownership through NCD 2026-09-27 21:59:32 +03:00
evgeny 9745a987ac Define join contract and require committed CHAT membership for BGP 2026-09-27 20:34:24 +03:00
evgeny 1a8072e9b2 Release group NCD ownership when destroying peers 2026-09-27 20:08:17 +03:00
evgeny 26e6c05313 Apply fresh BGP addresses to shared NCD connections 2026-09-27 19:18:40 +03:00
evgeny ec51b2d61f Version signed node snapshots with persistent timestamps 2026-09-27 19:11:11 +03:00
evgeny 7374d1648c Support 8 KiB member owner tags without signature or serialization truncation 2026-09-27 17:12:13 +03:00
evgeny de714f5023 Add bounded Merkle pull lifecycle and terminal error handoff 2026-09-27 16:17:04 +03:00
evgeny 3e25bcef02 Refactor Merkle sync into bounded bidirectional rounds with atomic member updates 2026-09-27 14:44:39 +03:00
evgeny 1b92b4885b Restrict dynamic auto socket test to Linux 2026-09-27 12:48:40 +03:00
evgeny 10d3ecfda4 Record final root transport and full suite validation 2026-09-27 12:19:52 +03:00
evgeny 03f71fd94e Bound ETCP receive window and fix timestamp and allocator UB 2026-09-27 12:16:15 +03:00
evgeny 59f4802904 Fix autosocket ownership and exercise interface churn under load 2026-09-27 11:57:29 +03:00
evgeny 3131dcbb7c Fix ETCP session recovery and router lifecycle; confirm chat invites before publishing 2026-09-27 02:51:47 +03:00
evgeny 018c8e795d android: фикс UAF AudioTrack при переключении группы рации (start → releaseLocked) 2026-09-26 19:18:42 +03:00
evgeny 0209bcf5d3 android: рация — единый режим per-канал, не гаснет при выходе/заходе в группу 2026-09-26 19:12:07 +03:00
evgeny 44d4265084 ll_queue: queue_waiter_cancel отменяет отложенный call_soon при internal==NULL (фикс UAF waiter_defer) 2026-09-26 18:58:27 +03:00
evgeny f6e6e2c73f stcp: write-error закрывает conn отложенно (фикс UAF keepalive/etcp_tcp_send)
Синхронный send-путь (stcp_link_send → queue_data_put → tx_queue_cb →
flush_pending → try_send) при ошибке сокета вызывал stcp_conn_do_close →
on_close → tcp_link_close_cb → etcp_link_close внутри стека вызывающего,
освобождая ETCP_LINK под keepalive_timer_cb/etcp_tcp_send (use-after-free,
SIGSEGV в keepalive_timer_cb:210).

Теперь write-error планирует отложенный close (uasync_call_soon) с отменой
handle в stcp_conn_do_close; первый err сохраняется; flush_pending не работает
вне STCP_STATE_DATA. Мёртвый hook on_write_error удалён.
2026-09-26 16:42:51 +03:00
evgeny 8aaac45020 ll_queue: отложенный первый запуск автозабора (callback_defer) + тест
queue_set_callback_defer(q, 1) переносит первый запуск callback из
queue_data_put* в uasync_call_soon (очистка стека производителя).
Повторные запуски (queue_resume_callback) и так всегда deferred.
2026-09-26 16:42:46 +03:00
evgeny cc2d2b5f78 etcp: фикс UAF teardown (двойное закрытие линка) + вытеснение down-линка при коллизии адреса
- tcp_link_close_cb (серверная ветка): убрать преждевременный огонь link_status/on_link_down,
  etcp_link_close сам снимает линк из списка и стреляет коллбэки в безопасном порядке
  (устраняет двойное закрытие линка — UAF из callring 45502)
- etcp_connection_close: при callbacks_running откладывать закрытие через uasync_call_soon
  вместо *(int*)0=0
- callbacks_running: uint8_t-флаг -> int-счётчик вложенности (++/--)
- etcp_fire_conn_status / etcp_fire_link_status_cbk: поднимать callbacks_running вокруг огня
- insert_link_queue: при коллизии адреса с чужим conn — ERROR при одинаковом pubkey,
  отказ при up-линке, вытеснение (etcp_link_close) при down-линке

Попутно: доки/комментарии в etcp_router, ACK-интервал 100мс->10мс (ROUTER_ACK_INTERVAL_TB).
2026-09-26 13:40:33 +03:00
evgeny 265421eb96 tun proxy: фикс TCP-суммы (нечётный pbuf), FIN до дренажа to_lwip, tcp_bind/rexmit_fast/active_pcbs_changed + нагрузочный burst-тест 2026-09-25 23:36:45 +03:00
evgeny a20d73d2be proxy/tun: добавить DEBUG-логи диагностики застревания keep-alive (lwIP IN/OUT/RECVED/RTO/PERSIST + per-conn DIAG) 2026-09-25 22:12:47 +03:00
evgeny 4353daf913 freebsd: уникальный dst-адрес per-interface (tunN -> 192.0.2.(N+1)) 2026-09-25 20:16:39 +03:00
evgeny 3a4daf6e77 etcp: строгая последовательность коллбэков при закрытии (NEW→UP↔DOWN→DELETE)
- close: финальный per-connection DOWN только если был UP, до state=2
- etcp_cbk_fire / etcp_fire_conn_status / etcp_link_ready_callback / etcp_on_up: guard state==2
- документация контракта в etcp_api.h / etcp.h
2026-09-25 19:13:38 +03:00
evgeny ab5d1c5f91 topo_group: диагностика senders_list (new_conn/remove_conn) для локализации UAF
- new_conn: DEBUG_INFO ADD с conn/peer/state/ch
- remove_conn: state в enter-логе, детали в !found_in_list WARN, REMOVED-лог
2026-09-25 18:18:39 +03:00
evgeny 08e8b2f1cc route_connectivity: фикс UAF при удалении узла с влётными пробами; отмена TCP-пингов при teardown
- topo_nodeq_remove_node: единый хелпер удаления nq (cancel проб + free paths/subnets + remove + free), заменяет все free-сайты
- route_connectivity_cancel_all: итерация по всем группам + зануление ctx->nq + отмена standby-defer
- etcp_send_tcp_ping: out_cli — хендл stcp_client для досрочной отмены; conn_probe_ctx.tcp_cli
- conn_mgr: cand_tcp_pings[] — отмена влётных candidate TCP-пингов в conn_mgr_destroy
2026-09-25 16:01:19 +03:00
evgeny 3f7b4351cd stcp: фикс UAF log_dump, дренаж очередей при закрытии, защита pre-closed серверной ветки, логирование тихих сбросов rx 2026-09-25 13:49:43 +03:00
evgeny ceaf466b1c chatgui-android: входящий звонок поверх блокировки (inline-оверлей) + вибрация всегда кроме silent 2026-09-24 12:08:49 +03:00
evgeny fb4343fca6 transport: reality-релей idle-timeout (конфиг, дефолт 5с) + warn при >64 релеях; убрать лимит FD_SETSIZE; проверка socket_id в stcp_server 2026-09-24 12:08:45 +03:00
evgeny cc269e810c Звонки: надёжность отмены (INVITE-кодограмма + watchdog) и вибрация входящего по ringer-режиму 2026-09-24 10:15:19 +03:00
evgeny b90dc752d2 call: две фазы исходящего вызова — индикация + разные гудки (desktop/Android) 2026-09-23 19:16:53 +03:00
evgeny 7198154106 chatgui: клик по медиа с отсутствующим файлом — сброс пометки и перекачка 2026-09-22 21:59:59 +03:00
evgeny d237dfec7c diag: кольцевой трейс вызовов teardown + дамп при SIGSEGV (размотка UAF link close) 2026-09-22 20:52:02 +03:00
evgeny 4f29839de5 chat: инвайтер шлёт CONNECT_RESULT(0) после приёма джойнера
- chat_join_process_request: постить CHAT_EVT_CONNECT_RESULT(result=0) в GUI инвайтера
  (иначе диалог «Пригласить в канал» висел на «ожидание ответа»)
- cs_handle_join_request: снять pending_invite_* и погасить invite_send_timer после join
2026-09-22 18:26:44 +03:00
evgeny b46fd599fa chat: push-invite join key + запрет флагов на плейсхолдере
- CHANNEL_INVITE теперь несёт join_key (регистрируется инвайтером локально),
  джойнер шлёт JOIN_INFO_REQ с валидным ключом — чинит автоjoin chatserver-узлов
- ask-путь сохраняет join_key для chat_sync_accept_invite
- chat_member_tags_commit отвергает установку флагов на source!=0 (плейсхолдер)
- member_sync_apply_record: defensive ERROR при adm_tags на плейсхолдере
2026-09-22 17:58:50 +03:00
evgeny 0e625da109 call: ретрансмит INVITE каждые 2с + fail-fast при отсутствии маршрута (6с) 2026-09-22 16:06:52 +03:00
evgeny 93becd36e6 keepalive: TCP-линки без ретраев/ACK; не пинговать спящих пиров, bg-ping цикл ≤ 1/мин 2026-09-22 03:47:50 +03:00
evgeny a3e890b711 keepalive: раздельный sleep-код (announce+ACK-ретраи в окне, dead-link) + без flagless в спячке 2026-09-22 03:29:42 +03:00
evgeny a3df1720b4 keepalive: симметричный sleep-протокол (без is_server) + отдельный ACK-бит 2026-09-22 02:41:15 +03:00
evgeny 331c7849f0 keepalive: убрать периодический keepalive в standby (обе стороны молчат, только фазовые анонсы + один ack) 2026-09-22 02:19:28 +03:00
evgeny 945c6a82b0 keepalive: гвард нулевого тайминга фазы (busy-loop) + разделение echo/announce на сервере 2026-09-22 01:35:25 +03:00
evgeny cc0dcb071e chat: UI настройки звука канала (Android toggle + JNI, desktop через C API) + trampoline 2026-09-22 01:25:55 +03:00
evgeny 3d9c79e752 db_sync: троттлинг push к спящему пиру в тихом чате 2026-09-22 01:21:51 +03:00
evgeny b39b85db13 chat: троттлинг по фазе спячки (member_sync send-only, db_sync sound-aware) + настройка звука 2026-09-22 01:20:32 +03:00
evgeny 9f89fd0f7a transport: keepalive duty-cycle тайминг (active_remaining+sleep_duration) + серверная фаза троттлинга 2026-09-22 01:20:26 +03:00
evgeny 0a1ba79d6f chat: member_sync per-peer throttling on peer sleep (cancel_peer + recv/start gating) 2026-09-22 00:42:24 +03:00
evgeny e4e9fca243 transport: per-peer sleep callback (peer_sleep_cbk) fired from keepalive protocol 2026-09-22 00:40:32 +03:00
evgeny 5628138d16 etcp: keepalive sleep-протокол на флагах (KA_FLAG_SLEEP + sleep_time, announce-retry, ack/echo) 2026-09-21 23:30:39 +03:00
evgeny 8a9d8d0097 fix: утечки памяти в router-сервисах и при shutdown
- conn_mgr_router_recv_handler / md_etcp_recv не освобождали entry (рантайм-утечка на каждое сообщение)
- reality_relay: registry + shutdown hook (отложенный free не отрабатывал при остановке)
- etcp_router: uasync_drain_immediate после destroy (router_close_finalize)
- мелкие callback-списки: activity_cbks, node_cbks, props_cbks/apply_cbks
2026-09-21 21:55:11 +03:00
evgeny 2f3001458f android: standby keepalive sync + rx logging during sleep 2026-09-21 21:17:35 +03:00
evgeny 845af9200d Stop call ring sound immediately on accept 2026-09-21 19:41:21 +03:00
evgeny 328d36376c chat: пишем local_attrs атомарно при вставке своего медиа (гонка с MSG_RECEIVED) 2026-09-21 19:36:07 +03:00
evgeny 6e2a2f0011 media_download: сброс таймера простоя при CONN→REQ + правки диагностики
- md_dl_conn_cb: обнулять last_progress_tb при переходе CONN→REQ, иначе время
  установки indirect-соединения (~2с) засчитывается как простой блока и
  вызывал преждевременный фейловер с держателя, реально имеющего блок
- STALL-лог: печатать миллисекунды корректно (значение в единицах 0.1мс /10)
- media_index: INSERT OR IGNORE — повторная регистрация блока как servable
  больше не шумит как ERROR (UNIQUE constraint)
2026-09-21 19:16:27 +03:00
evgeny 9793733cd5 chatgui: диагностика воспроизведения голосовых сообщений (Windows)
- sound_manager: ma_engine_init/playRawPcm логируют через DEBUG_* (qWarning/qDebug терялись в GUI-приложении без консоли); детальный результат старта PCM (rc/is_playing/cursor)
- main: ранняя инициализация лога в GUI-потоке до SoundManager::init, чтобы диагностика инициализации попала в файл
- messagedelegate: лог клика по Play/timeline (filePath/dlState/существует ли файл)
- voiceplayback: лог заголовка opus (sr/ch/frameSamples)

Попутные правки:
- u_async: фикс двойного close wakeup_pipe (eventfd/Windows)
- chat_core: NULL-guard в get_db/is_initialized
- android: sqlite открывается в utun_instance_init, cleanup stale-потока без утечки fd
- android SettingsScreen: компактная раскладка имени интерфейса
2026-09-21 18:59:04 +03:00
evgeny 19dc076528 chatgui: инициализация call/dm/media backfill при старте узла (fix звонков) 2026-09-21 17:20:41 +03:00
evgeny 6a0373ee73 call: диагностика ставок TX/RX и детект сталла аудио-девайса с хард-ресинком 2026-09-21 16:54:14 +03:00
evgeny 570b421ba0 conn_mgr: parallel reverse+indirect, retransmit, and call teardown fix
- call_on_conn_status: only end call when its own data conn drops
  (INDIRECT call was killed by unrelated topo_group_connect conn DELETE)
- conn_mgr: run REVERSE and INDIRECT in parallel after DIRECT fails;
  reverse is preferred (indirect defers via indirect_ready until reverse
  resolves). REVERSE timeout 15s -> 3s.
- retransmit DIRECT_REQ / EXCHANGE_REQ every 1s (idempotent, same request_id)
  to survive packet loss (router RST dropping stale-channel requests)
- replace main.phase with reverse_active/indirect_active/indirect_ready flags
- add diagnostics to trace the parallel flow
2026-09-21 15:22:16 +03:00
evgeny ee54bd52b3 conn_mgr: fix reverse-phase timer leak causing use-after-free crash
cm_ncd_callback re-entered cm_start_phase_reverse on every NCD timeout
during REVERSE phase (shared NCD revived by other modules), leaking reverse
timers: only the last one was tracked in entry->main.timer, so cm_entry_cleanup
cancelled only that one and the earlier timers fired on the freed entry.

- ignore repeated NCD timeouts while already in REVERSE (phase==2)
- cancel entry->main.timer before overwriting in reverse/indirect phases
- remove entry from reverse_pending in cm_entry_cleanup
2026-09-21 13:57:32 +03:00
evgeny e885fe30f5 chatgui: remove universal black background from video window (fix black boxes around controls) 2026-09-21 12:50:06 +03:00
evgeny cd0757e272 chatgui: declare metatype for shared_ptr<vector<int16_t>> (Qt5 build fix) 2026-09-21 12:42:21 +03:00
evgeny f5fece4959 chatgui: fix video playback on Windows (metatype registration + swscale YUVJ range) 2026-09-21 12:39:18 +03:00
evgeny 01987a75d0 chatgui: verify FFmpeg DLL deployment + fix dependency resolution order 2026-09-21 11:52:21 +03:00
evgeny 9d4538935d chatgui: restore FFmpeg build script (build_ffmpeg.sh + .gitignore)
Скрипт был случайно удалён в d773f172 вместе с .gitignore/.opencode.
Без него на Windows не собирается ffmpeg/ffmpeg_build/install -> HAVE_FFMPEG
не определяется -> воспроизведение и транскодирование видео отключены.
2026-09-21 11:29:47 +03:00
evgeny 6ee9bad17d chat: defer media auto-download to per-channel backfill after sync
- db_sync insert-cb gets initial_sync flag (SEND_DATA=1, PUSH/local=0)
- on_msg_inserted skips media download during initial sync
- new chat_media_autodownload_backfill_channel (newest-first) triggered
  on SYNC_DONE per channel (2s debounce)
2026-09-21 02:07:24 +03:00
evgeny a5047c29f7 chatgui: always deploy non-Qt DLLs (FFmpeg, mingw-runtime) next to vibechat.exe 2026-09-21 00:58:56 +03:00
evgeny 2f1545de38 chatgui: fix Qt5 build — QMouseEvent/QWheelEvent position() -> localPos()/posF() 2026-09-21 00:51:48 +03:00
evgeny de850db548 chat: remove join_password check from per-channel JOIN_INFO_REQ (server-link only) 2026-09-20 22:23:42 +03:00
evgeny 39a1da4c28 chat: fix use-after-free in invite_candidates trampoline + accept JOIN_INFO_RESP/READY/ERROR for unknown channels 2026-09-20 22:03:31 +03:00
evgeny d1432b7d19 tests: fix Windows build — SKIP fork-based e2e, cross-platform mkdtemp/setenv 2026-09-20 20:21:50 +03:00
evgeny a7a3666d85 radio: AGC release-rate (без кликов), jitter dead-zone, TX/RX-статистика, FGS-гонка и кэш настроек 2026-09-20 20:07:48 +03:00
evgeny ba1c2d85d3 reality_relay: async DNS вместо getaddrinfo + AGC-компрессор по умолчанию (-12 dBFS) 2026-09-20 15:28:46 +03:00
evgeny 738cfe7835 radio: адаптивный джиттер-буфер (pre-roll, симметричный темп, reorder) + AGC-компрессор микрофона
- voice_jitter: vj_create_cfg (target_ms/min_tempo/max_tempo/pre_roll), pre-roll
  с таймаутом, vj_end для дренажа остатка, глубина учитывает внутренний буфер SoundTouch
- radio_audio: reorder по seq (mesh, out-of-order через соседей), force-flush pending
  по таймауту и FIN, компрессор микрофона (AGC, конфиг [chatserver]), статистика буфера
- audio_compressor: clear_output для потокового слива без потери gain_smoothed
- chatgui messagelist: fromUtf8 для emoji-строк
- тесты: pre-roll/симметричный темп, reorder, late-drop
2026-09-20 14:39:25 +03:00
evgeny 27e66f1092 log: move socket diagnostics SYS->SOCKET and conn lifecycle ETCP->CONNECTION (avoid poll() flood) 2026-09-20 01:51:40 +03:00
evgeny 1bde2eca35 diag: log conn add/remove/free pointers to pinpoint BGP senders_list UAF 2026-09-20 01:42:05 +03:00
evgeny 16237b0df8 etcp_send: fix contract (caller owns entry on error) + fix 7 leak sites 2026-09-20 01:35:30 +03:00
evgeny fae0a7c069 uasync: cast function pointers in socket_add log to void* 2026-09-20 01:18:33 +03:00
evgeny b33caa56b8 uasync: pass socket name to all add_socket call sites 2026-09-20 01:13:38 +03:00
evgeny c36cebe357 uasync: add socket name diagnostics and HUP/ERR auto-unregister safety net 2026-09-20 01:11:30 +03:00
evgeny 90520a83ff topo_group_connect: не подключаться к самому себе в авто-цикле
Phase 1/2/3 и on_up/on_down не фильтровали self: узел попадал в список
connected-пиров (get_connected_peers не исключает self) и разворачивал
node_conn_direct_open на собственные адреса (12 линков на 5C31->5C31).
Ручной путь (connect_node_once) фильтр уже имел.

Добавлен skip self в Phase1 init/cycle_restart, Phase2, Phase3 и в
on_up/on_down (self не помечается connected и не считается в active_conn_count).
2026-09-19 23:40:06 +03:00
evgeny 6b92964794 radio/android: PTT FGS microphone permission + детект потери микрофона
- FOREGROUND_SERVICE_MICROPHONE запрос + foregroundServiceType microphone|specialUse (Android 14+)
- RadioOverlayService.start: не стартовать без mic-разрешения, ServiceCompat.startForeground с TYPE_MICROPHONE
- RadioAudioEngine: micDead-флаг и лог при обрыве captureLoop/read
- radio.c: лог первого кадра burst на транзитных узлах
- SettingsScreen: категория radio в списке debug-категорий
2026-09-19 23:13:04 +03:00
evgeny 688785fc6e etcp: фикс зависшего reinit_pending после реинита готового соединения
etcp_conn_queue_set_ready() делает ранний выход по state!=0, поэтому при
реините уже готового соединения (peer restart / duplicate INIT / collision)
reinit_pending оставался в 1 навсегда. Добавленные в 841b3042 guard'ы
etcp_conn_input/etcp_output_try_assembly дропали все входящие пакеты.

Снятие reinit_pending + REINIT перенесено в etcp_conn_ready.
2026-09-19 23:13:04 +03:00
evgeny 5e955168d2 chatgui-android: PTT-оверлей — talking flow, цвет idle/talking, drag-lock
- RadioAudioEngine: StateFlow talking для реактивной подсветки кнопки
- RadioOverlayService: градиент idle/talking, drag-lock, сброс isShown при destroy
- ChatScreen: скрывать PTT-кнопку при активном оверлее, подсветка по talking
2026-09-19 21:17:38 +03:00
evgeny 841b3042cf etcp: фикс double-free буферов паковщика и повторных free после неудачного queue_data_put
- pn_send_to_etcp/pn_unpacker_cb: занулять pn->data/pn->recvpart ДО queue_data_put
  (ре-энтрантный close/reinit в синхронной цепочке освобождал буфер повторно)
- etcp_output_try_assembly: гард state==2/reinit_pending + убрать re-put освобождённого entry
- etcp_conn_input: гард ре-энтрантного reinit/close в цикле секций, локальный etcp вместо pkt->link
- убраны повторные free после queue_data_put (он сам освобождает entry+dgram) в 6 местах
- диагностика: obj=%p в DOUBLE FREE, логи free dgram в pn_deinit/ack_recv
2026-09-19 21:17:33 +03:00
evgeny 4a785772be chatgui-android: PTT по volume-кнопкам (вверх/вниз) + синхронизация оверлея
- volume up/down обе держат PTT (holdCount), настройка radio_volume_ptt
- syncRadioOverlay при включении/выключении рации
2026-09-19 18:16:46 +03:00
evgeny 8a9184675a radio: дедупликация дублей в mesh + фикс OOB в hop_list
- radio_recv: кэш (src,stream,seq) с TTL 2с гасит кадры, пришедшие по второму пути (diamond/mesh)
- фикс записи за границу new_hops[hop_count] при hop_count==RADIO_MAX_HOPS
- счётчик c_dup_dropped + radio_dup_dropped() для диагностики
- тест test_radio_mesh (4 узла, diamond): C получает ровно N кадров, dup>0
2026-09-19 18:16:42 +03:00
evgeny b96ed8bd00 radio: PTT-радио в каналах (src/radio + chatgui + chatgui-android)
Новый модуль src/radio (radio/radio_audio/radio_headless) — push-to-talk
радио поверх topo-групп, доставка аудио в канал, headless-режим и тесты.
Интеграция в chatgui (ptt_hotkey_manager, radio_audio_engine, радионастройки,
вызовы) и chatgui-android (RadioAudioEngine, RadioOverlayService, UI).
2026-09-19 17:09:00 +03:00
evgeny 398078a28f headless/call: закрывать клиента и логировать при ошибке send() (EPIPE/ECONNRESET) 2026-09-19 10:43:04 +03:00
evgeny 6c42390fc2 utun: игнорировать SIGPIPE, чтобы send() в закрытый TCP-сокет не убивал демон 2026-09-19 10:32:57 +03:00
evgeny 1de1409699 conn_mgr: handle-based API (conn_mgr_open/close), фазы DIRECT/REVERSE/INDIRECT, тесты phases+handles 2026-09-19 09:31:08 +03:00
evgeny 734a414186 etcp: единый реестр svc_id + убрать коллизию CHAT/CONN_MGR (0x11)
Все ETCP_ID_*/ETCP_RT_ID_* сведены в etcp_api.h (единственное место
определения) с реестром и предупреждением про коллизии в пределах одного
пространства имён. Убраны локальные define из topo_group.h,
topo_group_invite.h, db_sync.h, chat_sync.h, chat_join.h; хардкод 0x31 в
member_sync.c заменён на ETCP_RT_ID_MEMBER_SYNC.

Коллизия: chatgui-десктоп биндил мёртвый ETCP_RT_ID_CHAT 0x11 поверх
ETCP_RT_ID_CONN_MGR 0x11 — затирался conn_mgr_router_recv_handler, из-за
чего INDIRECT-обмен conn_mgr не работал и голос звонка не устанавливался.
Удалён мёртвый код recvCallback/messageReceived/send в utun_node.{cpp,h}.
2026-09-19 00:18:39 +03:00
evgeny 6d3ad965ee topo: remember REQUEST_TABLE for not-yet-created groups, fulfill on create
При REQUEST_TABLE для ещё не созданной группы (CHAT-канал не загружен) пир
дропал запрос, и проситель навсегда не узнавал таблицу — односторонняя
видимость (звонок android→linux падал «peer offline»). Теперь запрос
запоминается в pending_table_reqs[32] и выполняется при создании группы
(topo_group_fulfill_table_reqs → handle_request_table).

Попутно: test_chat_join_e2e — критерий успеха через CHAT_EVT_CONNECT_RESULT
(JOIN_READY) вместо senders_list; doxygen-комментарии в etcp_api.h.
2026-09-18 23:36:45 +03:00
evgeny 8bfb456161 etcp: fix OOB/DoS in packet parsing and ping path
- FILLER: drop (uint16_t) truncation in fill_len bound (OOB pointer)
- ACK: require len >= ETCP_ACK_BASE_SIZE (8); wrap-aware clamp of till
  to next_tx_id + 16MB cumulative-ACK iteration limit (DoS)
- ping: bound len before sc_encrypt (stack overflow), ulen bound,
  allocate ETCP_DGRAM + PACKET_DATA_SIZE (heap overflow)

Signed-off-by: uTun <dev@utun>
2026-09-18 17:58:40 +03:00
evgeny 1340ab123b reality/stcp: fix OOB read in key_share parse, padding clamp, shbuf leak
- reality_parse_client_hello: bound shares_len by extension data length
  (len-2) to prevent stack-buffer-overflow from malformed ClientHello
- stcp handshake: reject padding_size > STCP_HS_PADDING_MAX (512)
- reality_ch_body_cb: free shbuf on stcp_try_send error (leak)
- tests: fuzz key_share parse; raw-socket handshake control/clamp/garbage

Signed-off-by: uTun <dev@utun>
2026-09-18 17:20:19 +03:00
evgeny e7fc69bb1d topo: комментарии к функциям topo_node/topo_group
conn_mgr: src node_id вместо conn->peer_node_id, препенд ETCP_RT_ID_CONN_MGR к dgram, bind CONN_MGR после etcp_router_init

call: guard CALL_ST_CONNECTING при accept
2026-09-17 17:30:05 +03:00
evgeny 1fce405204 debug: вычистить логи из категории DEBUG_CATEGORY_DEBUG (удалить дампы, перекатегоризовать ERROR-проверки в ETCP) 2026-09-17 00:17:32 +03:00
evgeny e6a4ffd418 reality: fix leak of ClientHello init buffer in reality_server_start_relay 2026-09-17 00:05:42 +03:00
evgeny 1f1df4f437 mem: детект аномально больших аллокаций (LARGE ALLOC log + calloc overflow guard) 2026-09-16 21:12:46 +03:00
evgeny 51a5c5bcbf router: fix UAF on rconn->recv_conn after ETCP_CONN destroy
recv_conn хранил заимствованный указатель на физический conn без очистки
при его освобождении. Добавлен etcp_router_conn_destroyed(), вызываемый из
etcp_connection_close фазы 1; RST теперь отправляется через живой conn,
доставивший пакет, а не через устаревший recv_conn.
2026-09-16 20:27:03 +03:00
evgeny 51d6d22a27 sock_match: классификация сокета по адресу, а не по type из конфига (fix Q8/Android→LAN)
ip_is_public() вынесен в lib/platform_compat (единый классификатор IPv4 с ntohl).
auto_socket_reconcile_config: тип сокета выставляется по фактическому адресу.
sock_collect_views: has_priv/has_inet/is_eim — по классификации адреса и NAT-детекции.
is_local_subnet упрощён до !ip_is_public(). Тест test_sock_match обновлён.
2026-09-16 20:26:55 +03:00
evgeny 21d9a4e5b7 dm: заполнять peer_name при автосоздании беседы (иначе в списке 0x...) 2026-09-16 20:26:55 +03:00
evgeny 5293fdd3a9 call(android): звать call_audio_init перед start; логировать feed_pcm без instance
Без call_audio_init(inst) g_inst оставался NULL и call_set_audio_ops не
регистрировался: TX (feed_pcm) падал на !ua, RX (audio_ops->on_media) не
вызывался — тишина и спам 'callAudioFeed returned false'. Зеркалим desktop:
utun_bridge_call_audio_start теперь делает call_audio_init + call_audio_start.
2026-09-16 20:26:55 +03:00
evgeny 2de554f8b9 call: единый голосовой стек (libutun_voice) + SoundTouch на Android
- voice_jitter (кодированные Opus-кадры + decode-on-pull + SoundTouch time-stretch)
  вместо PCM-буфера audio_jitter; излишек держится до декодирования
- call_audio.c/.h — единая реализация для desktop/Android/headless (C-интерфейс)
- libutun_voice (voice_sources.cmake): SoundTouch + voice_jitter + call_audio + call_tones
- Android: pull-модель (nativeCallAudioPull), убран Kotlin FIFO, ANDROID_STL=c++_static
- headless audio-сокет: PCM-протокол (decode-only, без тактирования), daemon остаётся C-only
- CALL_STATS расширен dropped/underruns; удалены audio_jitter.cpp и libutun_lite/call_audio.c
- тест test_voice_jitter (ёмкость/догон/стационар), test_call_headless переведён на PCM
2026-09-16 20:26:55 +03:00
evgeny de930b21a2 ncd: fix UAF on deferred UP delivery after handle close
ncd_deliver_up_cb (uasync_call_soon) держал сырой указатель handle h,
который мог быть освобождён node_conn_direct_close/force_close раньше
выполнения отложенного callback'а. Сохраняем токен call_soon в handle
и отменяем его при close через uasync_call_soon_cancel.
2026-09-16 20:26:55 +03:00
evgeny e4557b0473 dm: маршрутизация через общую группу (group_id), JSON-эскейпинг; звонки: иконки/UI в desktop и android 2026-09-16 15:05:48 +03:00
evgeny b483335c06 bgp/ncd: владение conn через NCD-handle в senders_list; call audio route persist + incoming call sound 2026-09-16 13:29:28 +03:00
evgeny b11e77e4c0 call: P2P аудиозвонки — сигналинг, релей медиа, аудио-контур, headless/desktop/android
- src/call/: сигналинг (call.c, call_proto.h, ETCP_RT_ID_CALL 0x36) + релей медиа
  + headless аудио-сокет (call_headless.c, call_audio_bind) + Opus/jitter/тоны
- chat: события CHAT_EVT_CALL_* (34–40), CHAT_EVT_DM_MESSAGES (41), DM-трамплины
- chatgui: call window, miniaudio I/O, audio_frame_ring, test_audio_jitter
- chatgui-android: CallScreen/IncomingCallDialog, аудио-роутинг, JNI, рингтон
- tests: test_call, test_call_headless
2026-09-16 10:17:57 +03:00
evgeny 0157f7f96b build: fix --asan to build lib/libopus dependency 2026-09-15 22:44:27 +03:00
evgeny 21ec740d82 socks_proxy: defer conn free to fix UAF from synchronous write_queue flush 2026-09-15 22:39:27 +03:00
evgeny b085715654 tun/nat: fix UAF при teardown и use-after-free в tun_read_packet 2026-09-15 22:07:55 +03:00
evgeny 6dc017337d chat: fix UAF в member list trampoline (req->inst после u_free) 2026-09-15 21:52:46 +03:00
evgeny 73fe99b33f dm: прямой обмен сообщениями (DM) + soundtouch + архитектура звонков
- src/dm/: dm_core/dm_crypto/dm_mailbox — прямой обмен сообщениями (DM)
- lib/soundtouch-master/: vendored SoundTouch (изменение темпа/высоты аудио)
- doc/dm_arch.md, doc/chat_call_arch.md: архитектура DM и звонков
- tests: test_dm.c, test_dm_e2e.c
- chat: рефакторинг chat_* модулей, member_sync, invite_build
- chatgui/chatgui-android: синхронизация с новым API чата
- lib/u_async, debug_config, utun_instance: сопутствующие изменения
2026-09-15 21:09:17 +03:00
evgeny 761f64cf76 chatgui-android: отображение REALITY-линков (TCP+REALITY) в статусе узлов и сокетов 2026-09-15 12:18:22 +03:00
evgeny 27b591daee reality: использовать NTP-время вместо time(NULL) в анти-реплей проверке timestamp
Часы colo2 отстают на ~52с (ntpd выключен), что ломало reality auth
(diff < -30с = time_window). Теперь клиент и сервер передают
ntp_time_get_seconds() в reality_client_hello_build_at /
reality_server_hello_build_at вместо сырого системного времени.
2026-09-15 05:39:28 +03:00
evgeny 481872d5db chat: invite-ссылки с несколькими адресами + ncd-адреса узлов (join) 2026-09-15 05:16:04 +03:00
evgeny d88dccfa19 etcp/stcp: обрыв etcp_link->tcp_link при закрытии stcp_link (fix UAF) 2026-09-15 00:25:42 +03:00
evgeny af382403ec tun_freebsd: reuse existing TUN interface on SIOCIFCREATE EEXIST 2026-09-14 23:17:59 +03:00
evgeny 952504d8eb etcp: убрать ложный reinit по gop (got_initial_pkt)
reinit должен происходить только при смене reset_id пира
(etcp_conn_apply_peer_reset_id). gop-проверка 'dirty vs clean' на догоняющем
TCP-линке давала ложный reinit и рвала соединение (JOIN_INFO_RESP терялся).
Удалён peer_got_initial_pkt из stcp_link (стал не нужен).
2026-09-14 22:46:41 +03:00
evgeny d58d4d39c4 chat join: запуск JOIN_INFO_REQ при уже поднятом соединении + добавление джойнера в группу
- cm_invite_trampoline: если уже подключены к connection-узлу — cs_on_conn_up
  повторно не сработает, запускаем join-хендшейк напрямую (cs_start_channel_join)
- chat_join_process_request: после верификации ключа сразу добавляем джойнера
  в CHAT-группу (topo_group_new_conn), чтобы BGP подключился
2026-09-14 22:46:41 +03:00
evgeny 910cfeac20 chatgui: настройка звука уведомления на канал (sound_on_message) 2026-09-14 21:12:53 +03:00
evgeny fc2262912a chat_join: верификация join-ключа на инвайтере (REQUEST_FWD)
Инвайтер добавлял мембера без проверки join-ключа — любой известный узел
мог самопригласиться в канал через REQUEST_FWD напрямую владельцу.
register_key теперь всегда хранит выданный ключ локально,
process_request отклоняет запрос с неизвестным/чужим ключом.
Тесты: unknown join_key rejected + регистрация ключа в t_process_request.
2026-09-14 21:12:53 +03:00
evgeny 75e39d2fee chat_join: модуль join-протокола, invite-ссылки v2, синхронизация мемберов 2026-09-14 19:57:27 +03:00
evgeny a01a4ddba4 fix: UAF in _on_member_sync_done (channel_cache freed by refresh)
cs_flush_sync passed &cs->channels[i] as member_sync cb_arg, but the 30s
cs_refresh_channels frees/reallocs cs->channels[], leaving the arg
dangling when _session_done later fires the callback. Re-find the channel
by the (stable) ns id instead of trusting arg.
2026-09-14 15:19:07 +03:00
evgeny 9ee61dce6c fix: ncd handle UAF in topo_recovery and topo_group_invite callbacks
topo_recovery_callback: detach cb on UP handoff and close handle on
DOWN/TIMEOUT so no handle with cb_arg==ctx outlives ctx (ASAN
heap-buffer-overflow in topo_recovery_callback).

tgi_to_channel_cb: force_close handle on NCD_EVENT_TIMEOUT so a late UP
cannot fire into the freed tgi_conn_ctx.

Also: android chatgui reverseLayout scroll + msg order/stable pagination.
2026-09-14 15:10:04 +03:00
evgeny fc3295ce12 nat_detection: гасить NAT-чек при закрытии линка через link-status коллбэк (UAF na->link) 2026-09-14 14:37:34 +03:00
evgeny e0f5afbff8 stcp: гасить on_close_cb/close_arg в stcp_link_close; keepalive: снапшот+ревалидация link до/после каскада (UAF) 2026-09-14 14:27:38 +03:00
evgeny da329d0479 merkle_sync: ms_outq_drain_cb — сохранять e->len до etcp_send (UAF); документировать ownership etcp_send 2026-09-14 13:49:12 +03:00
evgeny 1f8ccabdbf etcp: tcp_link_close_cb — сохранять link поля до каскада (UAF local_link_id) 2026-09-14 13:39:07 +03:00
evgeny 187bcd80b9 uasync: ре-валидация node перед последующими коллбэками (счётчик cb_called) вместо снапшота 2026-09-14 12:56:52 +03:00
evgeny 836c715299 uasync: снапшот полей socket_node в poll()/select() — защита от realloc во время коллбэков (UAF) 2026-09-14 12:47:35 +03:00
evgeny 369b253bdb chatgui-android: автоскролл чата вниз при открытии клавиатуры и своих сообщениях 2026-09-14 12:14:44 +03:00
evgeny d78d652bf5 shutdown: закрывать shared sqlite (topo_sqlite_db) в самом конце utun_instance_destroy (UAF при teardown) 2026-09-14 11:47:45 +03:00
evgeny 884f36c2d4 ncd: cancel connect/fin_wait таймеры в ncd_deferred_close (UAF fin_wait_timeout_cb) 2026-09-14 11:34:36 +03:00
evgeny f2f4a37344 routing: fix use-after-free в route_ping_cancel_for_conn (чтение request_id после u_free) + формат %08x 2026-09-14 11:29:31 +03:00
evgeny 11991e688c build: ASAN-сборка включает lib/libopus (зависимость src) 2026-09-14 11:20:12 +03:00
evgeny d730b06436 build: доработка build_bsd_full.sh (ASAN/hardened через clang, gmake fallback, -jN, FFmpeg) 2026-09-14 11:18:05 +03:00
evgeny d0dfa97a47 transport: SOCKS5-прокси для ETCP-линков (CONNECT + UDP ASSOCIATE + пинги) 2026-09-14 00:24:11 +03:00
evgeny 7c8d65db78 chat: локальное удаление канала (Android) + db_sync инстансы в куче 2026-09-13 22:08:33 +03:00
evgeny ddfffad1ed conn: сопоставление сокетов по ролям (direct/nat/local) + hairpin; NTP-детект оффлайна
- sock_match: единая классификация сокетов (DIRECT/NAT/LOCAL) и адресов (PUB/PRIV/UNREACH)
- eim приравнен к direct; на приватный адрес — только с приватного интерфейса;
  оба EIM за одним NAT (одинаковый публичный IP) — только через LAN
- topo_node: LOCAL-сокеты рекламируются (INTERFACE), private скрыт,
  NAT-адрес не рекламируется для strict и при оффлайне
- ntp: флаг reachable по циклам + пере-анонс nodeinfo при смене доступности
- cm_direct_add_links / ncd_create_links / conn_match_candidate_sockets на sock_match
- test_sock_match: unit-тесты классификации и сопоставления
2026-09-13 16:48:00 +03:00
evgeny ff53c735dd chat: превью медиасообщений в списке каналов (Android + desktop) 2026-09-13 15:55:33 +03:00
evgeny 07ef7d9a01 chat: живой счётчик медиакеша + сверка + DESC-проход докачки/вычистки 2026-09-13 14:49:57 +03:00
evgeny 1eb0566cb5 chat: enforce storage_total_size + media orphan cleanup; async_dns: fix use-after-free
- media cache: удаление сирот на старте (сверка ФС с сообщениями) + лимит
  storage_total_size (удаление самых старых файлов, только старше backfill_days)
- ma_dir_walk(): кроссплатформенный рекурсивный обход каталога (POSIX/Windows)
- async_dns: первый драйв slow-пути отложен через call_soon (исключён
  double-free при синхронном завершении запроса); cancel унифицирован
- chat: видимый WARN 'media too large' + честный счётчик started/skipped
2026-09-13 03:11:10 +03:00
evgeny 7bba39ddcb nodeinfo: 2 адреса на сокет (до/после NAT) + типы адресов local/public/nat/nat_eim/nat_strict
- topo_node: сокет рекламирует INTERFACE (до NAT) + NAT (после NAT, EIM);
  strict/direct отдельным адресом не передаются
- topo_node_sqlite: addr_type_classify различает NAT_VERIFIED_*; добавлен
  ADDR_TYPE_NAT (тип неизвестен), fallback больше не даёт DIRECT по IP
- chat_member: бейдж NAT-типа по приоритету (public>nat_eim>nat>nat_strict>local)
  вместо MIN(addr_type)
- GUI (desktop+android): адреса мембера с socket_id и ролью (local/public/nat/nat_eim/nat_strict)
2026-09-13 01:05:25 +03:00
evgeny a823898379 chat/media: докачка недостающего медиа после стартовой синхронизации + HAVE_BLOCK анонс локальных блоков 2026-09-12 21:28:08 +03:00
evgeny e636c3a420 docs: доступ на узел colo2 2026-09-12 21:24:04 +03:00
evgeny 5ee6bf53d8 conn_mgr: ping активных кандидатов с noop-колбэком вместо NULL (bad args) 2026-09-12 21:07:24 +03:00
evgeny c6118280bd conn_mgr: не удалять как stale кандидатов с активным соединением 2026-09-12 20:00:48 +03:00
evgeny 265008961a conn_mgr: кандидаты-посредники из topo_node-списка (активные соединения); гейт md_super_connect по is_supernode; откат ping→best_candidates 2026-09-12 19:38:48 +03:00
evgeny 2045d4a169 media: восстановить conn_mgr_open (DIR/REV/IND) + популяция best_candidates из RTT + watchdog не трогает CONN-блоки 2026-09-12 18:47:01 +03:00
evgeny 905fde5a27 media: BLOCK_REQ транзитом — убрать требование прямого соединения (NAT-узлы качают через роутер) 2026-09-12 18:27:23 +03:00
evgeny 0055c2c6fe media: фикс детекта суперузла (json_flat_get), backfill осиротевших медиа, fallback на автора при пустом QUERY_RESP 2026-09-12 18:19:47 +03:00
evgeny 5a83aaa786 opencode: подключить aiterm-mcp (интерактивный терминал для отладки) 2026-09-12 16:19:09 +03:00
evgeny ce12f058d9 tcp_io: fix error_cb busy-spin on graceful EPOLLHUP; socks_proxy: close local socket on both FINs
- error_cb (tcp_io): при graceful HUP (SO_ERROR==0) после уже прочитанного FIN
  сокет оставался в epoll и level-triggered EPOLLHUP крутил цикл на 100% CPU.
  Теперь сокет закрывается через tcp_conn_push_close.
- socks_proxy on_fin_cb: при обоих обменянных FIN локальный сокет закрывается
  (send_close + push_close), симметрично exit-стороне (tcp_proxy_server).
2026-09-12 16:08:14 +03:00
evgeny 56d0928632 etcp: wire-MTU кап 2048→1500 (согласовано с TUN_MAX_PACKET_SIZE), enc_buf привязан к MTU; тест multilink: per-link проверка трафика с ретраем через keepalive-тики 2026-09-12 13:53:55 +03:00
evgeny 13e2697f80 test_etcp_link_stress: переписан — 5 вариантов добавления линков (V1/V2, коллизии V3a/b/c), kill/failover и инвариант «нет реинита при живом линке»; фикс stack buffer overflow в etcp_encrypt_send (enc_buf 1600→2048) 2026-09-12 13:31:37 +03:00
evgeny 42a94aa916 BGP: фикс владения адресами в реестре (мульти-группа), проброс group_id в сервис (conn_mgr per-group), membership-фильтр create_group; тест media через chat-группу 2026-09-12 02:13:34 +03:00
evgeny 6c9a47b201 media_delivery: диагностика маршрутизации md_send/md_dl_send/router_deliver (conn, subcmd, no-route) + тест test_media_delivery_chat 2026-09-11 23:48:31 +03:00
evgeny d7bb58c6fb chat: автоподключение к узлам группы (вкл/выкл) + ручной connect к узлу (desktop+android); фикс reinit при QR-join 2026-09-11 19:46:02 +03:00
evgeny 9ef501a2fb etcp_connections: собрать forward-объявления в блок и добавить пояснения к функциям 2026-09-11 18:13:48 +03:00
evgeny d95048160d etcp: закрыть UAF в клиентской ветке tcp_link_close_cb
etcp_on_link_down может каскадно закрыть conn и освободить link;
использовать сохранённый etcp и проверять state!=2 перед reconnect
2026-09-11 17:46:38 +03:00
evgeny be1f08b43e etcp: защита от UAF в start_keepalive_timer/enter_ready_tcp + диагностика указателей
- start_keepalive_timer: guard !link->etcp->instance->ua (SIGSEGV на 80 строке)
- etcp_link_enter_ready_tcp: return при etcp->state==2 после reinit/apply_reset/conn_ready
- tcp_server_on_link/enter_ready_tcp: DEBUG_CATEGORY_DEBUG с адресами link/etcp/inst/ua
- topo_node: убрать лишние DEBUG_CATEGORY_DEBUG (chain_rtt)
2026-09-11 17:13:35 +03:00
evgeny 7de636be6e topo: убрать ALTER-миграции, backfill deleted из adm_tags 2026-09-11 16:48:15 +03:00
evgeny b7da6547c1 connection: логировать только connect/reconnect/disconnect; фиксы конфига чатгуи
- категория connection теперь только жизненный цикл соединения; INIT/PING/keepalive
  переклассифицированы в ETCP/SOCKET/KEEPALIVE/REALITY/ROUTING, удалены дубли
- settingsdialog: устранён дубль my_node_name в конфиге (двухпроходная запись + только при изменении)
- storagesettingspage: канонические ключи storage_total_size/storage_unit_size с суффиксом M/G
- audiodevicesettingspage: compressor_max_gain_db min 0→1
- etcp_connections: %s-баг в логе TCP link (addr → sockaddr_storage_to_str)
- docs/sample: [chatserver], актуализированы ключи хранилища
2026-09-11 15:24:56 +03:00
evgeny b7669eea36 Fix use-after-free of ETCP_LINK in keepalive_timer_cb 2026-09-11 13:21:35 +03:00
evgeny 70a18c8be8 etcp: чистка — убрать мёртвый peer_id/guard reset_id, поправить комментарий STCP_HS 2026-09-11 11:59:48 +03:00
evgeny f36ad8903a etcp: убрать session_id из INIT/STCP handshake, симметричная эпоха reset_id 2026-09-11 11:57:29 +03:00
evgeny ab6773a313 etcp: перепривязка/своп remote_link_id при INIT со сменённым адресом (NAT) 2026-09-10 21:20:26 +03:00
evgeny 311cee0ee1 Fix group node scan stall and UAF on server link teardown
- node_conn_direct_close: immediate conn cleanup when links are down
  (graceful fin_wait on a down conn left a lingering entry, so re-open
  in Phase3 stuck in 'pending' with no timer and scan never continued)
- tcp_link_close_cb: skip etcp_link_close if conn was already closed
  synchronously during etcp_on_link_down cascade (fixes use-after-free)
2026-09-10 19:01:21 +03:00
evgeny 5633903be0 stcp: закрывать принятые коннекты в stcp_server_destroy (UAF server_tlink->conn после tcp_socket_remove) 2026-09-10 17:53:14 +03:00
evgeny 6a674b3422 etcp: etcp_inflight_nullify_link читал entry->data вместо entry — last_link не обнулялся (UAF в bbr/retrans) 2026-09-10 17:53:14 +03:00
evgeny fdad786744 etcp: сбрасывать last_rr_link при закрытии линка в обеих ветках (use-after-free в loadbalancer) 2026-09-10 17:20:19 +03:00
evgeny ee8b2a4a45 chat: deleted-флаг выводить из adm_tags — member_block_put вместо деструктивного member_put, удалить member_put, фильтр deleted=0 в списках 2026-09-10 17:20:18 +03:00
evgeny 94136564ee platform_compat: выбирать глобальный IPv6 (2000::/3) вместо ULA при выборе адреса интерфейса 2026-09-10 17:20:17 +03:00
evgeny 327aac1c12 ncd: убрать временные DEBUG_CATEGORY_DEBUG логи (scan_conns, open/open_node dedup) 2026-09-10 16:01:57 +03:00
evgeny e75e3750aa ncd: синхронный close timed_out-conn чтобы не было коллизии линков при cycle_restart 2026-09-10 15:57:07 +03:00
evgeny d886a2ff84 Диагностика коллизии линков + детектор дубликата node_id + reality-конфиг autogen 2026-09-10 14:40:45 +03:00
evgeny c2882c533e reality: конфиг [client] для REALITY-клиентов (server_name/short_id/public_key/version/fingerprint) + TCP-линки через NCD 2026-09-10 12:33:16 +03:00
evgeny 898a0a4a66 reality: реклама reality-параметров сокетов через BGP и применение к исходящим TCP-линкам 2026-09-10 11:24:17 +03:00
evgeny edb3bb36e1 reality: TLS-камуфляж для STCP (ClientHello/ServerHello + релей на реальный сайт) 2026-09-10 01:11:26 +03:00
evgeny 0f308fdbd7 chatgui/chatgui-android: уведомления со звуком, флаги и прочие улучшения UI 2026-09-10 00:23:14 +03:00
evgeny 6c857ceab8 chat: обрабатывать CHANNEL_INVITE/INFO_REQ для ещё не созданного канала + GUI-результат invite-by-link
- chat_sync: не дропать CHANNEL_INVITE/CHANNEL_INFO_REQ при отсутствии CHAT-группы (group_id == channel_id, выводим ch_id напрямую) — приглашение по ссылке на сервер теперь доходит до autojoin
- inviteby: регистрация connect_result_cb, показ результата, автозакрытие при успехе
- joindialog: исправлен маппинг кодов результата (2=timeout, -1=not found, -7=not init)
2026-09-09 22:13:52 +03:00
evgeny 22da6bd832 chatgui: открывать SQLite-БД до auto_socket_init (переиспользование портов) 2026-09-09 21:29:27 +03:00
evgeny 738ac8928b tests: fix mkdtemp cross-platform in test_media_delivery_download 2026-09-09 20:32:48 +03:00
evgeny e3168d1e55 Удалить обмен метриками ETCP (ETCP_SECTION_METRICS)
Полностью убрана фича периодического обмена CSV-метриками с Ed25519-подписью:
- ETCP_SECTION_METRICS и парсинг/верификация секции
- struct etcp_metrics, etcp_metrics_add_*/start/stop и снапшот-таймер
- мёртвый API on_metrics_rcvd/metrics_user_ptr (никто не подписан)
- link->remote_ed25519_pubkey (заполнялся только на клиенте → верификация на сервере всегда падала)
- stats_dir в UTUN_INSTANCE
Общий sc_stream_sign_* (деривация Ed25519 из X25519) и INIT-передача обоих ключей сохранены.
2026-09-09 19:59:29 +03:00
evgeny 5b31a765c2 chatgui-android: auto_sockets=android — сокеты из конфига, лёгкое обновление без полного рестарта
- auto_sockets три-режимный: off/system/android; android берёт сокеты из [server] конфига
- auto_socket_reload_config: reconcile сокетов (remove→create) по имени+addr:port, сохранение портов
- etcp_create_server_socket вынесен из init_sockets; stcp_server_list_find_by_sock
- instance_lite_update_sockets + JNI nativeUpdateSockets + NativeLib.updateSockets
- NetworkChangeMonitor: updateSockets вместо restart; порты сохраняются при смене сети
2026-09-09 17:34:46 +03:00
evgeny c474c37284 chatgui/chatgui-android: просмотр изображений с зумом + инфо о медиа
- chatgui: просмотрщик изображений (ImageViewerWindow) с зумом/поворотом
- chatgui: отправка картинок как image с определением разрешения и типа
- chatgui: у видео-пузыря строка info (res·size·bitrate) и бейдж только длительность
- chatgui: контекстное меню медиа (сохранить в файл/скачать)
- chatgui-android: большие emoji-only сообщения, битрейт/разрешение у видео
- chatgui-android: сохранение неактивных интерфейсов как закомментированные секции
- chatgui-android: отправка картинок по расширению (jpg/png/gif/bmp/webp)
- photo_sender: content-type и расширение по типу файла
2026-09-09 16:18:26 +03:00
evgeny ca16b46404 chatgui-android: съёмка фото — JPEG, выбор разрешения, отправка аттачем, просмотр с зумом 2026-09-08 20:02:40 +03:00
evgeny c94acd892e chatgui: автозакрытие видеоплеера по завершении воспроизведения 2026-09-08 20:02:37 +03:00
evgeny 73e81032bc chatgui-android: видеосообщения — запись, выбор камеры, качество, подсветка
- C-слой: video_sender (content_type video/mp4, media_video=0 без FFmpeg)
- chat_msg_submit: duration/width/height для метаданных видео в теле сообщения
- JNI: nativeVideoSend + NativeLib/Repository/ViewModel
- VideoRecordScreen: CameraX VideoCapture, 4 пресета качества (разрешение+битрейт),
  выбор камеры (селфи/широкая/обычная/макро/теле по фокусу), тумблер подсветки,
  запись/пауза/отправить, лимит 60с
- InputBar: единая кнопка — короткий тап переключает voice/video, удержание — запись
- режим камеры/микрофона сохраняется в ViewModel
2026-09-08 18:38:18 +03:00
evgeny fe1726ebea chatgui-android: видеоплеер — зум, поворот, полноэкранный режим 2026-09-08 16:39:59 +03:00
evgeny aa166a2030 topo_group: RESYNC — пассивная сторона перезапускает BGP-обмен после флэпа
Сервер (пассивная сторона) на UP шлёт общий RESYNC, клиент по нему
заново анонсирует группы (JOIN_GROUP), которые ему положены по конфигу.
Чинит потерю маршрутов при одностороннем флэпе, когда клиент не замечает
разрыв и не перешлёт JOIN_GROUP повторно.
2026-09-08 16:39:55 +03:00
evgeny f28d3e91f7 media: рефакторинг загрузки блоков — конечный автомат, фейловер, watchdog простоя
- media_download: состояние блока (IDLE/CONN/REQ/RECV/WAIT/DONE/FAILED),
  кандидаты-держатели (MD_MAX_HOLDERS_PER_BLOCK), фейловер + ретраи (MEDIA_DL_MAX_ATTEMPTS),
  watchdog простоя (MEDIA_DL_STALL_TIMEOUT_TB) вместо фейкового времени
- media_delivery: настраиваемые dl_stall_timeout_tb / dl_max_attempts
- chat_msg: пометка ошибки скачивания в local_attrs ("st":"er") для UI
- tests: новый test_media_download_timeout (watchdog/фейловер), рефакторинг
  test_media_delivery_download/full
- chatgui-android: состояние 'error' у вложений/видео (иконка + tap to retry)
2026-09-08 06:53:48 +03:00
evgeny 089bbe5189 chatgui: запуск на XWayland (xcb) + ручной move/resize с пропорцией
- main.cpp: на Linux принудительно QT_QPA_PLATFORM=xcb (если не задан) — на Wayland
  композитор владеет позицией окна, на XWayland ручной move/resize работает полностью
- videoplayer_window: вернуть ручной move + ручной resize с пропорцией (один путь,
  без платформозависимого нативного startSystemMove/Resize)
2026-09-08 06:48:23 +03:00
evgeny 0dfb39cfcb chatgui: гибридный resize — нативный на Wayland (край тянется), ручной с пропорцией на X11/Windows 2026-09-08 02:47:59 +03:00
evgeny 3adf9871fb chatgui: вернуть ручной resize с пропорцией (двигает окно, край под мышью) + фикс перемотки
- resize: ручной setGeometry с живой пропорцией (якорь = противоположный край);
  drag — нативный startSystemMove
- перемотка: QProxyStyle SH_Slider_AbsoluteSetButtons=LeftButton вместо сломанного
  SeekSlider (sliderReleased не эмитился — seekMs не вызывался)
2026-09-08 02:39:06 +03:00
evgeny 645591bb64 chatgui: нативный move/resize (Wayland) + фикс замирания видео при перемотке
- окно: startSystemMove/startSystemResize вместо ручного move/setGeometry
  (Wayland игнорирует позицию окна) — драг и resize за левый/верхний край работают
- пропорции: снэп к videoAspect после нативного resize (дебаунс 150мс)
- перемотка: backpressure по m_buffered (декод не убегает вперёд и воркер живёт
  для seek), seek через av_seek_frame(-1, us), пропуск кадров с AV_NOPTS_VALUE
- SeekSlider: клик по шкале = прыжок к позиции
- крестик закрытия справа вверху; курсор контролов — стрелка
- test_video_engine: проверка seek
2026-09-08 02:04:18 +03:00
evgeny f2b34459c3 chatgui: баббл видео — размер + время m:ss (напр. 1.5MB · 0:45) 2026-09-08 01:47:32 +03:00
evgeny d09a6af52a chatgui: плеер — фикс drag, центр/fullscreen при открытии, resize за края с сохранением пропорций
- m_render WA_TransparentForMouseEvents — события мыши напрямую в окно (drag работает)
- открытие: видео влезает — натуральный размер по центру; больше экрана — fullscreen
- resize мышью за 4 края и 4 угла, пропорции сохраняются (якорь = противоположный край)
- нижний край доступен через eventFilter на панели контролов (пустые поля пропагируют)
- сброс fullscreen при повторном playFile
2026-09-08 01:31:12 +03:00
evgeny 76182d018f chatgui: QPointer-защита при генерации превью в фоне 2026-09-08 01:01:28 +03:00
evgeny 74b17685f7 chatgui: фикс синхронизации кадров (часы не убегают вперёд при быстром декоде) + smoke-тест движка
- presentTick: кадры всегда по мастер-часам, завершение только когда очередь пуста
- clockMs: по окончании аудио часы в конец (хвостовые кадры не теряются)
- fallback на системные часы, если аудио-устройство недоступно
- tests/test_video_engine: smoke-тест FFmpeg-декодера (opened/frames/seek)
2026-09-08 01:00:38 +03:00
evgeny 6542618927 chatgui: воспроизведение видео — FFmpeg-плеер + frameless окно
- videoplayer_engine: декод в worker-потоке (FFmpeg), A/V sync по аудио-курсору,
  seek с epoch-защитой от устаревших кадров
- videoplayer_window: окно без рамки, drag-перенос, dblclick/Alt+Enter fullscreen,
  Esc закрытие, timeline + play/pause/close, авто-скрытие контролов
- видео-баббл в списке: превью-кадр (jpg), play-оверлей, длительность, прогресс
- превью извлекается после скачивания (video_extract_thumbnail) в фоне
- mainwindow: синглтон-плеер, openVideoRequested → openVideo
- CMake: HAVE_FFMPEG для vibechat
2026-09-08 00:55:06 +03:00
evgeny e264b8aaa7 video: video_extract_thumbnail — превью первого кадра в JPEG (MJPEG) 2026-09-08 00:44:01 +03:00
evgeny 7f3b4f61f4 chat: dur/w/h в тело видео-сообщения (после транскода) 2026-09-08 00:41:43 +03:00
evgeny 1855c61275 video: отправка видео-сообщений (FFmpeg пробинг+транскод H.264/AAC MP4)
- src/video/: video_probe/video_get_info/video_transcode_start (FFmpeg, HAVE_FFMPEG)
- chat_core/chat_msg: media_video флаг, chat_core_submit_media_message, транскод перед index
- headless: команда attach с --video
- chatgui: диалог 'Как видео' + прогресс транскода + sendVideoAttachment
- FFmpeg в сборке (configure.ac, Makefile.am, chatgui CMake, build_nix.sh)
- debug: категория DEBUG_CATEGORY_VIDEO
2026-09-08 00:41:04 +03:00
evgeny c8bd5975ae chatgui-android: invite-ссылки с выбором узла входа в GUI + desktop dialog layout 2026-09-07 22:36:27 +03:00
evgeny ba557e2b9d chat: фикс 0:00 у длинных голосовых — b64_decode терял последний байт без padding + formatTime в мс 2026-09-07 22:36:23 +03:00
evgeny 1345f44e10 chat: invite-ссылки с выбором достижимого узла + tcp_io фикс half-close FIN
- Общий модуль invite_build: сборка utun://-ссылок, авто-выбор лучшего
  достижимого узла, список кандидатов (вынесено из android jni_bridge)
- chatgui: диалог invite с выбором узла (комбобокс), события INVITE_CANDIDATES /
  INVITE_LINK_READY через gui_bridge
- headless/chatcli: invite <ch_id> [node_id], команда invite_nodes
- tcp_io: error_cb отличает грациозный EPOLLHUP (SO_ERROR==0) от реальной ошибки —
  при приостановленном чтении FIN не роняет недослейные данные (фикс flaky half-close)
- run_test.sh: half-close echo на выделенном порту 19190 + отдельный DNAT (был недостижим)
2026-09-07 13:59:15 +03:00
evgeny a17a85a383 chat: deleted-аккаунты — колонка deleted, скрытие/зачёркивание, не подключаемся 2026-09-07 01:40:53 +03:00
evgeny 631a176944 chat: права админа + member tags (verified/deleted) + ncd revive
- chat_admin: передача канальных privkey другому узлу (encrypted+signed,
  ETCP_RT_ID_ADMIN_KEY), событие ADMIN_KEY_RECEIVED
- member_sync/bridge: tags verified/deleted, is_channel_owner, transfer_admin
  (android JNI + chatgui memberpropsdialog)
- node_conn_direct: оживление timed_out entry — обновление линков в существующем
  conn (убрать старые, добавить новые) + немедленная очистка не поднявшегося
  conn без fin_wait — фикс invite/reconnect
2026-09-07 00:53:41 +03:00
evgeny 6163f34ce1 etcp_router: эпоха reset_id на wire + master/slave — фикс потери пакетов при рестарте пира 2026-09-06 22:20:08 +03:00
evgeny 61c572cae4 etcp: reset_id эпоха ресета — фикс потери NODEINFO при флапе
Добавлен 64-битный reset_id (эпоха ресета) в INIT-фреймы и STCP-handshake:
- генерится один раз при создании conn, меняется только при фатальном ресете
  (normalizer desync / bad fragment size);
- при приёме чужого id slave принимает эпоху пира и ресетится, master держит свою;
- первый id принимается без ресета (свежие при init);
- «tcp peer clean» реинит гейтится по смене id — нет повторного сброса seq.

Это устраняет коллизию seq=1 (RX dup) из-за двойного реинита нескольких TCP-линков,
из-за которой NODEINFO пира терялся и member не возвращался online.

Также: chat member online теперь из topo_group (без БД/merkle), тест test_etcp_seq_collision.
2026-09-06 21:50:06 +03:00
evgeny f7c08f8f5e nat_detection: применять NAT-тип через topo_node_update_my_addresses с переподписью 2026-09-06 01:29:07 +03:00
evgeny 53bce51201 etcp_router: корректный handshake/restart без таймаут-петли
- START на первом пакете сеанса (seq=0), гейт отправки до первого ACK
- дедуп рестарта по payload init-пакета (ретрансмит START не триггерит рестарт)
- RST переинициализирует только отправку (без CLOSE_ALL), дедуп повторных RST
2026-09-06 00:33:45 +03:00
evgeny 232ad2de6b etcp_router: разделить служебные функции и API в хедере + чистка мёртвого кода
- etcp_router.h: 4 секции (протокол / внутренние структуры / API / служебные)
- переименование router_set_max_inflight -> etcp_router_set_max_inflight
- удалены неиспользуемые: input_q_count, waiter_register/cancel, conn_close_async
  (+ поля close_callback/close_callback_arg)
- docs: актуализировать backpressure (on_send_ready на send_q) вместо удалённого waiter API
2026-09-05 22:34:00 +03:00
evgeny 4b9b0009d8 chat: гейт авто-синхронизации db_sync до member_sync/pubkeys + member tags (verified/deleted) в chatgui 2026-09-05 20:38:22 +03:00
evgeny 01ea6913ae etcp_router: sess_id как локальный счётчик переустановок (не в протоколе) 2026-09-05 20:38:17 +03:00
evgeny 2d04bf941b etcp_router: вынести подпись/шифрование в route_crypto, одна функция отправки с флагами
- route_crypto_encode/decode: автономный пред/пост-обработчик пакета
  (encrypt-then-sign, стандартный Ed25519 вместо sc_stream_sign)
- одна функция отправки etcp_route_send/etcp_router_conn_send с bitmask mode
- формат доставки: [svc_id][src][dst][rx_flags][payload] (статус подписи/шифрования)
- ROUTER_INFLIGHT хранит финальный wire-пакет (чинит потерю подписи при ретрансмите)
- дедуп инициализации: router_conn_reset/free_queues
- учёт inflight через inflight_q count (seq присваивается на enqueue)
2026-09-05 17:40:43 +03:00
evgeny 7c95ddb09b chatgui: значок владельца канала (🔑) в шапке блока мемберов при наличии private key 2026-09-05 15:23:45 +03:00
evgeny 7817bb96aa etcp_router: START/RST handshake для переживания рестарта пира
Раньше START ставился один раз при первой отправке и терялся в ретрансмите,
а принимающая сторона молча авто-синкала rx_seq на любой первый пакет —
после рестарта пира/среднего узла seq рассинхронизировались (тихий data loss
или no-ACK close). Теперь:

- Клиент: флаг start_sent «новый» до первого ACK; пока он стоит, первый
  seq (0) шлётся со START, включая ретрансмит. Первый принятый ACK
  (tx_acked>0) сбрасывает флаг. При рестарте conn флаг снова «новый».
- Сервер: после рестарта peer_sync_done=0 («ждёт START»). Не-START пакет →
  отправка RST (через recv_conn). START → синк rx_seq + ACK → обычный режим.
- RST на принимающей стороне теперь сбрасывает локальное состояние
  (etcp_router_conn_restart), а не закрывает conn.

Тесты unit обновлены: первый data-пакет сессии инжектится со START.
2026-09-05 15:03:44 +03:00
evgeny 84015176b3 etcp_router: досылать потерянный ACK при дубле (рестарт среднего узла)
При рестарте транзитного узла ACK приёмника терялся и не пересылался:
router_schedule_ack выходит рано при rx_seq==last_sent_ack_seq, поэтому
пир ретранслил 17 циклов (~5с), роутер-conn закрывался и цикл повторялся
бесконечно — лечилось только рестартом клиента. Теперь при дубле
(ретрансмите пира) ACK досылается с троттлингом ROUTER_ACK_INTERVAL_TB.
2026-09-05 01:17:37 +03:00
evgeny 32725657dc chatgui-android: обработка скачивания голосовых (download state) + config: секция [chat] алиас для chatserver 2026-09-04 22:46:07 +03:00
evgeny 23fbdf4584 chat: запуск BGP-обмена при подключении узла к группе (invite + auto-connect) 2026-09-04 21:04:18 +03:00
evgeny 5b8c725c01 etcp_router: round-robin send + контроль заполненности send_q через waiter на send_input_q 2026-09-04 19:30:08 +03:00
evgeny 1a88ea88a8 topo_group: узлы меняются только по BGP (NODEINFO/WITHDRAW), удаление по link down через BGP-коллбэк 2026-09-04 17:57:37 +03:00
evgeny b0f149092f BGP: log node add/remove per group at info level 2026-09-04 16:45:33 +03:00
evgeny c049788c08 etcp_router: при no route выводить список узлов группы (<10) 2026-09-04 16:39:23 +03:00
evgeny ed2df9fe88 etcp_router: добавить кол-во узлов группы в лог no route 2026-09-04 16:25:21 +03:00
evgeny d092f649ac chatgui: пути db и лога относительные в конфиге, резолв к бинарнику в рантайме 2026-09-04 14:34:39 +03:00
evgeny 654202b0cb chatgui: debug-категории в секции [debug] построчно (как в utun) 2026-09-04 13:42:59 +03:00
evgeny 20eef8ad16 chatgui: лог по умолчанию в файл chatgui.log (debug_file) 2026-09-04 13:25:06 +03:00
evgeny 656da48e35 topo_group: асимметричный старт BGP (JOIN_GROUP) + вынос group_id/flags из TOPO_NODE
- UTUN: BGP инициирует только сторона-клиент ([client]) и явные etcp_connect;
  сервер добавляет пира по JOIN_GROUP (фикс обратного маршрута: сервер не
  создавал узел клиента из-за дедупа senders_list)
- topo_group_new_conn: conn_add_path до дедупа; handle_request_table без add_to_senders
- TOPO_NODE: убраны group_id/flags (shared-реестр), передаются в topo_node_serialize
- routing/control_server: явный TOPO_GROUP_UTUN
- CHAT: гейт по peers_* (get_member_channels) + пассивное add/remove в member_sync
- tests: тесты адаптированы (route_ping ждёт registry+v4_addrs)
- chatgui-android: voice_recorder_init в utun_bridge_voice_start (idempotent)
2026-09-04 13:21:24 +03:00
evgeny 7b73d99b5b chat: передавать group_id в бинарном виде в протоколе синхронизации
db_sync/merkle_sync/chat_sync шлют group_id (uint64 BE) вместо текстового
channel_id/namespace. Приёмная сторона находит группу/таблицу через
topo_groups_find(group_id) и требует существующую CHAT-группу. db_sync:
instance key заменён с SHA256(ch_id) на strtoull(ch_id)=group_id.
2026-09-04 09:02:46 +03:00
evgeny 326dfd7e2c media_delivery: маршрутизация всегда через CHAT-группу
media_download/медиа-ответы и суперузловая репликация теперь берут группу
из контекста (dl->group_id, group_id из пакета, peer->group_id) вместо
хардкода TOPO_GROUP_UTUN. node_props_changed получает channel_id.
SUPER_HELLO несёт group_id.
2026-09-03 23:24:50 +03:00
evgeny 55da8fd146 media_delivery: логирование отправки QUERY_RESP и провалов md_send 2026-09-03 21:59:57 +03:00
evgeny 01b61a457a etcp_router: fix ACK no-route — слать ACK через recv_conn
При асимметричной топологии topo_group_find_conn_for_node возвращает NULL,
ACK не уходит → ретрансмит-шторм → no-ACK close → inflight сбрасывается в 0.
Теперь ACK уходит через тот же коннект, с которого пришли данные (recv_conn),
с фолбэком на топологию. Довершает фикс из e08956e (deliver), который
аналогично не трогал router_send_ack.

Заодно удалён неиспользуемый ETCP_ID_ROUTE_ENTRY.
2026-09-03 20:05:59 +03:00
evgeny fbea9c3869 socks_proxy: fix client stall + non-blocking DNS via async_dns
- fix HTTP_STATE_RELAY == SOCKS_STATE_CONNECTING enum collision (relay data dropped)
- fix wrong waiter cancel (etcp_router_waiter_cancel -> etcp_router_cancel_send_ready)
- add backpressure retry timer, restore lost queue_resume_callback, defer FIN on pending tx_buf
- async_dns: /etc/hosts fast-path + hosts loading (avoid 4s DNS timeout for localhost)
2026-09-03 18:13:00 +03:00
evgeny f47ad7d87f tcp_proxy: fix throughput (tx_queue backpressure) + connect timeout + integration test
- Router: unified service delivery format [svc_id][src][dst][payload] (end-to-end src/dst from SVC_ROUTE header)
- tcp_proxy_client: replace single tx_buf drop-on-backpressure with bounded tx_queue + send_q waiter + retry timer (no data loss, bulk window reopen — removes 62.5ms delayed-ACK stall)
- tcp_io: configurable connect timeout (tcp_conn_set_connect_timeout)
- tcp_proxy_server: connect_timeout config option (ms, default 2000, 0=off)
- check.sh: run tcp_proxy_full integration test with sudo (skip if passwordless sudo unavailable)
- test_tcp_io: connect timeout unit test; docs (lwip_nuances.md)
2026-09-03 13:49:09 +03:00
evgeny acfaae2801 dns: заменить K&R-объявления указателей на функции на типизированные
gcc 15 (C23) трактует () как 'без аргументов', из-за чего lib/dns.c не собирался
(65 ошибок). struct dns_rr_i.sort и таблица dns_rrtypes[] переведены на явные
прототипы с typedef'ами и кастами указателей.
2026-09-02 23:59:46 +03:00
evgeny e08956e5dc etcp_router: fix NULL conn в deliver при отсутствии пути к узлу
router_handle_data_packet выбрасывал физический conn, а router_incoming_q_cb
восстанавливал его через topo_group_find_conn_for_node, который возвращает NULL,
когда у узла-источника нет пути в топологии. В итоге callback сервиса (routing,
conn_mgr) получал conn=0x0. Теперь физический conn сохраняется в rconn->recv_conn
и прокидывается в deliver.
2026-09-02 23:59:41 +03:00
evgeny 1db4fe1f01 build_linux_full.sh: автоустановка зависимостей и корректная обработка ошибок 2026-09-02 21:29:48 +03:00
evgeny 5d39083a6d auto_socket: пропускать интерфейсы без default-маршрута (опция auto_socket_skip_no_default_route, default=on) 2026-08-17 23:43:15 +03:00
evgeny f841a964aa chat: debug-логи на джойнер-стороне (channel_put, JOIN sent, WELCOME self-add) 2026-08-17 22:53:03 +03:00
jeka 10306f5613 chat: fix CHANNEL_INVITE invalid ch_sig — передавать владельца канала из БД, а не node_id отправителя 2026-08-17 21:16:44 +03:00
evgeny 7dee49ce63 android: мониторинг смены сети — NetworkChangeMonitor, перезапуск ядра при смене интерфейса 2026-08-17 21:03:05 +03:00
1658 changed files with 486931 additions and 33947 deletions

No files matched your search

+7
View File
@@ -2,7 +2,9 @@
/build/
build-win/
tools/chatgui/build/
tools/chatgui/rlottie_build/
tools/chatgui/ffmpeg/ffmpeg_build/
tools/lightsout/build/
# Build outputs
*.o
@@ -11,6 +13,8 @@ tools/chatgui/ffmpeg/ffmpeg_build/
*.a
*.so
*.so.*
# Prebuilt native runtime для Android VAD (onnxruntime) — коммитим несмотря на *.so
!tools/chatgui-android/app/src/main/jniLibs/*/libonnxruntime.so
/utun
/src/utun
test_serialize
@@ -28,6 +32,7 @@ index.html
*.exe
# Autotools
/.utun-build-state*
Makefile
!tools/etcpmon/Makefile
Makefile.in
@@ -80,6 +85,7 @@ logs/
tests/test_*
!tests/test_*.c
!tests/test_*.h
!tests/test_*.py
!tests/test_*.conf
!tests/test_*.json
tests/bench_*
@@ -108,6 +114,7 @@ tools/proxy/udp_proxy
# Reference projects (not part of utun)
tools/tdesktop-dev/
/hardware/
# Debug/temp artifacts
*.bak
+501 -132
View File
@@ -25,12 +25,24 @@ This file contains essential information for AI coding agents working in the uTu
При поиске багов всегда проверяй достаточно ли отладочной информации. Если не достаточно - фокусируйся на том чтобы добавить нужную информацию в вывод.
Помни что отладка управляется фильтрами по категормяи, которые нужно проверить и при необходимости правильно настроить (в конфиге или в коде).
Для поиска выстараивай информативные логи. По которым максимально чётко понятно что происходит. Если какие-то места которые могут иметь отношение к ошибке в логах пропущены - это повод доработать логи и прогнать еще раз.
Всегда отладку выстраивай через доработку логов, пока точно и однозначно не будет видно в каком точно месте проблема.
По хорошей диагностике сразу должно быть понятно что происходит, в том числе точную причину проблемы. Если приходится догадываться или плохо понятно - это значит что надо улучшать диагностику.
Доствп на узел colo2: ssh -p44322 logs@colo2
Структура .h файлов:
- в начале краткое и понятное описание: что делает модуль, как пользоваться.
- поля структур также кратко описываем - что, для его, если есть нюансы/значения
- для функций: описываем назначение, как пользоваться, описание аргументов, возврата. кратко но понятно.
## Quick Reference
**Repository:** uTun - Secure VPN tunnel with ETCP protocol
**Repository:** uTun — общее сетевое ядро ETCP, независимо запускаемые UTUN и P2P-чат
**Language:** C (C99)
**Build System:** GNU Autotools (autoconf/automake)
**Cryptography:** OpenSSL (AES-CCM, X25519, SHA256)
**Cryptography:** OpenSSL (AES-CCM, X25519, Ed25519, SHA256)
## Build Commands
@@ -44,9 +56,19 @@ This file contains essential information for AI coding agents working in the uTu
powershell build_full.bat # запускает bash build.sh --full через MSYS2 UCRT64
```
### Build (FreeBSD)
```sh
# Первичная установка инструментов от root:
pkg install -y bash gmake autoconf automake libtool pkgconf openssl python3
./build.sh -j4 # общий скрипт; автоматически выбирает gmake и Clang
./build_bsd_full.sh -j4 # полная сборка через общий скрипт
```
Python 3 нужен для проверок скрипта сборки. SHA-256 берётся из базовой системы FreeBSD.
### Incremental Build
```bash
./build.sh -j4 # make с авто-конфигурацией если надо
./build.sh -j4 # инкрементальная сборка, конфигурация и очистка при необходимости
./build.sh --no-gui -j4 # только ядро и тесты, без отдельной сборки GUI
```
```powershell
powershell -Command ".\build.bat" 2>&1 # Windows, логи: build_win.log
@@ -61,9 +83,10 @@ make distclean # Clean everything including configure files
### Partial Builds
```bash
cd lib && make # Build only the library (libuasync.a)
cd src && make # Build only the main program
cd tests && make # Build only tests
make -C lib # базовые библиотеки
make -C lib/libopus # встроенный Opus
make -C src # основной бинарник src/utun (библиотеки уже должны быть собраны)
make -C tests # тесты (используют объекты src/)
```
### Direct Build (Windows, без autotools)
@@ -79,7 +102,7 @@ python3 tools/chatcli members <ch_id> # мемберы с полн
python3 tools/chatcli messages <ch_id> [count] # последние сообщения
python3 tools/chatcli send <ch_id> "text" # отправить сообщение
python3 tools/chatcli invite <ch_id> # создать invite-ссылку
python3 tools/chatcli connect <utun://...> # join по ссылке
python3 tools/chatcli connect 'utun://...' # join по ссылке
python3 tools/chatcli create "Name" # создать канал
python3 tools/chatcli listen # слушать события (Ctrl+C выход)
```
@@ -87,13 +110,14 @@ python3 tools/chatcli listen # слушать событи
### ASAN Build (AddressSanitizer, Linux)
```bash
./build.sh --asan -j4 # lib/ + src/ only (tests not ASAN-compatible)
./build.sh --asan -j4 # собирает lib/ + src/; тесты этот режим скрипта пропускает
```
Запуск с ASAN (detect_leaks отключены чтобы не спамить при shutdown):
```bash
ASAN_OPTIONS=detect_leaks=0:halt_on_error=0 ./src/utun -f -p /tmp/utun.pid -l /tmp/utun.log
ASAN_OPTIONS=detect_leaks=0:halt_on_error=0:log_path=/tmp/utun_asan ./src/utun -f -p /tmp/utun.pid -l /tmp/utun.log
```
Краш-логи ASAN пишутся в `/tmp/utun_asan.<pid>`.
В этом примере отчёты ASAN пишутся в `/tmp/utun_asan.<pid>` благодаря `log_path`.
Для отдельного теста нужно собирать его зависимости с теми же sanitizer-флагами; обычные и ASAN-объекты не смешивать.
## Test Commands
@@ -111,13 +135,14 @@ cd tests/
./test_etcp_two_instances
```
### Run Single Test with Debug Info
### Build and Run a Single Test
```bash
cd tests/
gcc -I../src -I../lib -I../tinycrypt/lib/include \
-o my_test test_file.c ../src/*.c ../lib/*.c ../tinycrypt/lib/source/*.c
./my_test
make -C tests test_services
cd tests && ./test_services
```
Использовать цель из `tests/Makefile.am`: ручная компиляция `src/*.c` пропускает подкаталоги и добавляет лишний `main`.
Уровни и категории диагностики настраиваются в самом тесте через `debug_set_level` / `debug_set_category_level`.
Проверки самого скрипта сборки запускаются отдельно: `make -C tests check-build-script` (нужен Python 3); в `./check.sh` они не входят.
## Code Style Guidelines
@@ -130,7 +155,7 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
### Formatting
- **Indentation:** 4 spaces, no tabs
- **Braces:** Same line for functions, new line for control structures:
- **Braces:** Открывающая скобка на строке функции или условия:
```c
int function(void) {
if (condition) {
@@ -138,7 +163,7 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
}
}
```
- **Comments:** Primary language is Russian for business logic, English for API docs
- **Comments:** Краткие описания на русском; сохранять принятые имена API и протокольных полей.
- **Line Length:** Aim for 80-100 characters, but can go up to 150 if logically coherent
### Include Order
@@ -194,7 +219,7 @@ sc_context_t ctx;
sc_init_ctx(&ctx, &my_keys);
// 2. Set peer public key (for key exchange)
sc_set_peer_public_key(&ctx, peer_public_key, SC_PEER_PUBKEY_HEX); // 0=bin, 1=hex
sc_set_peer_public_key(&ctx, peer_public_key_bin, SC_PEER_PUBKEY_BIN); // 32 бинарных байта; HEX — для hex-строки
// 3. Ready for encrypt/decrypt
sc_encrypt(&ctx, plaintext, plaintext_len, ciphertext, &ciphertext_len);
@@ -216,134 +241,254 @@ sc_obfuscate_pubkey(salt, peer_pubkey_bin, my_pubkey_bin, obfuscated_output);
### Debug Levels
`none < error < warn < info < debug < trace`
trace - логируем заходы в функции и важные ветвления внутри функций
debug - всё что нужно для понимание сути происходящего (состояний, важных переменных) алгоритма. тоесть по уровню debug мы должны видеть все важные внутреннии нюансы работы функции включая состояния и переменные.
debug - отладка: всё что нужно для понимание сути происходящего (состояний, важных переменных) алгоритма. тоесть по уровню debug мы должны видеть все важные внутреннии нюансы работы функции включая состояния и переменные.
info - сообщения для пользователя о работе. мы должны видеть в читаемом виде понятные и осмысленные сообщения о каких-либо значимых действиях с точки зрения логики работы модуля или приложения
warn / error - ошибки и предупреждения (аномалии). должны быть во всех ошибочных ветках. Молча вываливаться с ошибкой нельзя, все ошибки и предупреждения должны логироваться.
### Debug Categories (28 категорий)
```
NONE=0, UASYNC=1, LL_QUEUE=2, CONNECTION=3, ETCP=4, CRYPTO=5, MEMORY=6,
TIMING=7, CONFIG=8, TUN=9, ROUTING=10, TIMERS=11, NORMALIZER=12, BGP=13,
SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20,
KEEPALIVE=21, ETCPROUTE=22, BBR=23, ETCP_DUMP=24, CONNECTIVITY=25, DB_SYNC=26,
MEMBER_SYNC=27
```
### Debug Categories
Актуальные имена и константы — в `lib/debug_config.h`, текстовые имена — в `lib/debug_config.c`.
Основные: `sys`, `connection`, `etcp`, `crypto`, `config`, `tun`, `routing`, `timers`, `bgp`,
`socket`, `control`, `dump`, `traffic`, `debug`, `general`, `nat`, `keepalive`, `etcp_route`,
`media`, `etcp_dump`, `chat`, `chat_sync`, `member_sync`, `proxy`, `video`, `reality`, `dm`, `call`, `radio`, `vad`, `aec`.
UASYNC/LL_QUEUE/MEMORY/TIMING объединены в SYS, NORMALIZER/BBR — в ETCP; старые числовые списки не использовать.
### Настройка отладки
- В конфиге: `debug = etcp=trace,config=info` (формат: `категория=уровень,...`)
- В INI-конфиге: секция `[debug]`, каждая категория отдельной строкой (`etcp=trace`, `config=info`).
- Строку `etcp=trace,config=info` принимает `debug_parse_config()`; GUI хранит её в `[gui] debug_categories`.
- В коде: глобальный уровень и per-category уровни из `debug_config_t g_debug_config`
- Макросы: `DEBUG_ERROR(cat,fmt,...)` `DEBUG_WARN` `DEBUG_INFO` `DEBUG_DEBUG` `DEBUG_TRACE`
- `log_dump(prefix, data, len)` — hex dump в лог
- `log_dump(level, category, prefix, data, len)` — hex dump в лог
### Dual Output
- Консоль и файл настраиваются раздельно (`debug_set_console_level`, `debug_set_file_level`)
- Фильтр уровней общий для выходов; `debug_enable_console()` включает/выключает консоль.
- `debug_enable_file_output(path, truncate)` / `debug_disable_file_output()`
## Architecture
### Ядро, сервисы и владение ресурсами
- `utun_instance_create*()` создаёт ядро; `utun_core_start()` запускает общие сетевые службы.
- `utun_service_start/stop()` управляет UTUN-группой, TUN/NAT и запросами к пирам из конфига.
- `chat_service_start/stop()` управляет чатом и CHAT-группами. Чат не требует UTUN; desktop и Android запускают ядро и чат явно.
- Ядро владеет общей SQLite (`db_path/chats.db`, без пути — `:memory:`), идентичностью, транспортом и маршрутизатором.
Остановка сервиса сохраняет БД и ресурсы другого сервиса. `utun_instance_destroy()` освобождает ядро, но не переданный `UASYNC`.
- Группа владеет NCD handle с начала CONNECTING. Инициатор хранит отменяемый `TOPO_PEER_REQUEST`;
закрытие своего запроса не разрывает транспорт других владельцев. Готовая сессия сохраняется у группы.
- UP означает работоспособный транспорт. READY означает завершённое согласование и обмен таблицей конкретной группы.
Recovery ждёт нужных маршрутов через READY-пиров; наличие `ETCP_CONN*` само по себе не гарантирует UP.
- JOIN добавляет подписанного мембера, Merkle распространяет запись. Доступ к CHAT-группе каждый узел даёт по своей таблице участников;
invite-ключ не даёт временного членства. Протокол описан в `src/chat/chat_join.h`, групповое согласование — в `src/routing_layer/topo_group.h`.
- `TOPO_NODE` — общая подписанная запись узла. Более свежий timestamp заменяет запись целиком, с полями и подписью;
адреса не версионируются отдельно. BGP обновляет запись и передаёт изменения NCD (см. `doc/node_snapshot.md`).
- BGP хранит один путь к назначению от каждого соседа. WITHDRAW удаляет только путь отправителя;
NODEINFO с прежним timestamp также обновляет путь. Выбор: живой путь с минимумом хопов, затем меньший ID соседа.
REINIT сохраняет пути до проверки нового снимка; TABLE_COMPLETE удаляет неподтверждённые, зависший обмен перезапускается.
- ETCP-router дописывает ID при транзите и отбрасывает повторное посещение для DATA/ACK/control.
Список переходов находится вне подписанного тела; формат описан в `src/routing_layer/etcp_router.h`.
Все операции с ядром выполняются в его uasync-потоке; stop/destroy — вне callbacks останавливаемых сервисов.
Порядок освобождения ресурсов и нюансы media workers: `doc/service_lifecycle.md`.
### Directory Structure
```
├── lib/ # Core libraries (18 .c + 20 .h + libopus/liblmdb)
├── lib/ # uasync, очереди, SQLite, Opus, SpeexDSP, Silero VAD, SoundTouch, LMDB
├── src/ # Main source code
│ ├── transport_layer/ # ETCP stack, crypto, STCP, normalizer, BBR (~30 файлов)
│ ├── routing_layer/ # Routing, BGP, conn_mgr, etcp_router, NAT detection (~20 файлов)
│ ├── chat/ # P2P чат, db_sync, merkle_sync, member_sync (17 файлов)
│ ├── proxy/ # SOCKS5, TCP/UDP/ICMP прокси (10 файлов)
│ ├── lwip_tcp/ # Встроенный lwIP TCP стек (7 файлов)
│ ├── media_delivery/ # Доставка медиафайлов (6 файлов)
│ ├── media_async/ # Асинхронный движок медиа (2 файла)
│ └── *.c/h # Корневые модули: utun, config, TUN, firewall, NAT, NTP, control (~14 файлов)
├── tests/ # ~70 тестов, 64 в check_PROGRAMS
│ ├── transport_layer/ # ETCP, сессии, crypto, STCP/REALITY, SOCKS-клиент, NCD, BBR
│ ├── routing_layer/ # Routing, группы/BGP, recovery, conn_mgr, etcp_router
│ ├── chat/ # P2P чат, join, db_sync, merkle_sync, member_sync
│ ├── dm/ # Личные сообщения, E2E-вложения и offline-mailbox
│ ├── call/ # Звонки и общий голосовой стек
│ ├── radio/ # Групповая PTT-рация
│ ├── stream/ # Прямые трансляции: подписанные RS-блоки, выбор поставщиков, аудио/видео
│ ├── member_files/ # Файловое хранилище участников CHAT-группы и асинхронные передачи
│ ├── local_transport/ # Обнаружение устройств и доставка приглашений в локальной сети по UDP
│ ├── proxy/ # SOCKS5, TCP/UDP/ICMP прокси
│ ├── lwip_tcp/ # Встроенный lwIP TCP стек
│ ├── media_delivery/ # Блоки медиа каналов и передача неизменяемых файлов
│ ├── media_async/ # Фоновые задачи, метаданные вложений, подготовка голоса/видео
│ ├── video/ # Пробинг и транскодирование видео через FFmpeg
│ ├── dnsmasq/ # Архив исходников для встроенной сборки dnsmasq
│ └── *.c/h # utun, instance, config, TUN, firewall, NAT, NTP, control, broadcast
├── tests/ # Состав тестов и цели запуска: tests/Makefile.am
├── doc/ # Technical Specifications
├── tools/ # Auxiliary tools
│ ├── etcpmon/ # GUI монитор ETCP
│ ├── chatgui/ # GUI чат (Qt 6 с Qt 5 fallback, сборка CMake)
│ ├── chatgui-android/ # Android P2P чат (Jetpack Compose + libutun_lite)
│ ├── logreceiver/ # Приём UDP-логов Android
│ ├── chat_tcp_test/ # Сценарий проверки чата через TCP
│ ├── lightsout/ # Отдельная игра Lights Out на Qt
│ ├── lightsout-android/ # Android-версия Lights Out
│ ├── tdesktop-dev/ # Референс: Telegram Desktop (только для изучения)
│ ├── proxy/ # UDP прокси для тестов
│ └── bping/ # BPing (bandwidth ping)
│ ├── bping/ # BPing (bandwidth ping)
│ └── chatcli # Headless CLI; описание команд — chatcli_commands.txt
├── tinycrypt/ # TinyCrypt crypto library (external)
├── net_emulator/ # Network emulator (delays, loss, reordering)
└── c2/ # Test instance 2 (конфиг и бинарник для тестов)
└── net_emulator/ # Network emulator (delays, loss, reordering)
```
### File Overview
Пути ниже относительны к каталогу секции; `name.c/h` означает пару `.c` и `.h`.
Перечислены модули проекта; внутренние файлы сторонних библиотек сгруппированы по каталогам.
**Core (src/)**
- `utun.c` - Main program entry point, CLI parsing, daemon mode
- `utun_instance.c/h` - Root instance lifecycle, config loading, all submodule init
- `utun_instance.c/h` - Создание ядра и независимый жизненный цикл UTUN/чата, владение общими ресурсами
- `tun_if.c/h` - TUN interface API (init/write/close, cross-platform)
- `tun_linux.c` `tun_freebsd.c` `tun_windows.c` - Platform-specific TUN implementations
- `tun_route.c/h` - TUN routing table sync
- `config_parser.c/h` - INI-style config file parsing
- `config_updater.c/h` - Config file modification utilities
- `config_updater.c/h` - Проверка и сохранение согласованных X25519-ключей и node_id в INI-конфиге
- `control_server.c/h` - Control/monitoring server (etcpmon backend API)
- `firewall.c/h` - Firewall rules (per-interface filtering)
- `eim_nat.c/h` - Endpoint-Independent Mapping NAT engine
- `nat_transport.c/h` - NAT transport layer (packet relay)
- `ntp_time.c/h` - NTP time synchronization
- `ntp_node_time.c/h` - Inter-node time synchronization
- `nat_transport.c/h` - Передача IPv4 между отдельным TUN и удалённым NAT-провайдером через UTUN-router; локальный провайдер использует eim_nat
- `ntp_time.c/h` - Асинхронная NTP-синхронизация: поправка времени instance без изменения часов ОС
- `ntp_node_time.c/h` - Получение временной поправки от синхронизированных ETCP-пиров и диагностика расхождения часов
- `broadcast.c/h` - Широковещательная рассылка данных по topo-группе (UUID-дедупликация, TTL)
- `video/video.c/h` - Пробинг и транскодирование видео (FFmpeg, опционально; H.264/MP4 ≤720p)
- `dnsmasq/` - Архив dnsmasq-2.93.tar.xz для встроенной сборки
**Transport Layer (src/transport_layer/)**
- `etcp.c/h` - ETCP protocol implementation (inflight queues, retrans, ACK, RTT/jitter)
- `etcp_api.c/h` - ETCP public API (send/recv/bind callbacks)
- `etcp_connections.c/h` - Socket and link management, INIT handshake, keepalive, PING/PONG
- `etcp_session.c/h` - Согласование эпох ETCP-сессии через HELLO/CHALLENGE/CONFIRM и проверка принадлежности DATA сессии
- `etcp_loadbalancer.c/h` - Multi-link load balancing with traffic shaper
- `etcp_debug.c/h` - ETCP packet dump/formatting
- `etcp_debug.c/h` - Диагностический разбор секций ETCP_DGRAM и форматирование IPv4-адресов
- `etcp_dump.c/h` - ETCP дамп/декодирование пакетов
- `etcp_bbr.c/h` - BBR congestion control for ETCP
- `etcp_connect.c/h` - ETCP outbound connections
- `etcp_keepalive.c/h` - Keepalive-пакеты, адаптивный период, смена режима standby/normal (мобильные узлы)
- `auto_socket.c/h` - Автосоздание UDP+TCP сокетов на всех интерфейсах и линков к ETCP-соединениям (`auto_sockets=yes`)
- `pkt_normalizer.c/h` - Packet fragmentation/reassembly (packer/unpacker)
- `packet_dump.c/h` - Packet hex dump utility
- `dummynet.c/h` - Network emulator integrated into utun (for testing)
- `secure_channel.c/h` - AES-CCM encryption with X25519 key exchange, pubkey obfuscation
- `packet_dump.c/h` - Текстовая сводка IPv4-пакета: адреса, поля транспорта и короткий hex-фрагмент
- `dummynet.c/h` - Двунаправленный UDP-эмулятор между заданными endpoint: задержка, ограничение скорости и случайные потери
- `secure_channel.c/h` - X25519, пакетное AES-128-CCM, потоковое AES-CTR, подписи Ed25519 и обфускация публичного ключа
- `etcp_padding.c/h` - Выбор длины случайного padding и периодическое обновление диапазона для instance/соединения
- `etcp_tx_stats.c/h` - Общая статистика исходящих очередей, inflight и подтверждённых за секунду байтов ядра
- `crc32.c/h` - CRC32 checksums
- `stcp.c/h` - STCP протокол (Secure TCP)
- `stcp_server.c/h` - STCP сервер
- `stcp_client.c/h` - STCP клиент
- `stcp_link.c/h` - STCP линк-уровень
- `node_conn_direct.c/h` - Direct config-based connections
- `socket_monitor.c/h` - Socket state monitoring
- `stcp.c/h` - Зашифрованные кадры поверх TCP: согласование ключей/эпох, AES-CTR, CRC, очереди и закрытие соединения
- `stcp_server.c/h` - Приём входящих STCP-соединений, handshake и передача готового соединения владельцу
- `stcp_client.c/h` - Исходящее STCP-подключение, в том числе через SOCKS5 и REALITY
- `stcp_link.c/h` - Подключение STCP к ETCP_LINK: передача пакетов, backpressure и жизненный цикл TCP-линка
- `socks_client.c/h` - Исходящий SOCKS5-транспорт: TCP CONNECT и UDP ASSOCIATE с переподключением control-сокета
- `node_conn_direct.c/h` - Общий прямой транспорт к пиру с отдельным handle каждого владельца; обновление линков по адресам узла
- `socket_monitor.c/h` - Наблюдение за адресами интерфейсов и маршрутами ОС, обновление сетевого состояния ETCP-сокетов
- `reality.c/h` - REALITY-style TLS ClientHello/ServerHello камуфляж для STCP
- `reality_fingerprint.c/h` - Статические отпечатки (cipher suites, groups, ALPN) для REALITY-камуфляжа
- `reality_io.c/h` - Внешний REALITY-handshake и поток защищённых записей STCP, Finished, replay-защита и выбор relay
- `reality_record.c/h` - TLS-подобные AES-128-GCM записи: вывод ключей, счётчики направлений, padding и KeyUpdate
- `reality_relay.c/h` - Релей неавторизованных клиентов на реальный HTTPS-сайт
- `stun_nat.c/h` - Проверка mapping/filtering NAT через внешние STUN-серверы, RFC 5780 и кеш результатов по IP/интерфейсу
- `call_ring.c/h` - Кольцевой трейс контрольных точек освобождения ETCP/STCP-ресурсов для диагностики teardown/UAF
**BBR (src/transport_layer/BBR/)**
- `bbr_v3.c` - BBR congestion control algorithm v3
**Routing Layer (src/routing_layer/)**
- `routing.c/h` - Routing table (local)
- `route_lib.c/h` - Routing library
- `route6_lib.c/h` - IPv6 routing
- `route_ping.c/h` - Route ping probing (NAT check, liveness)
- `route_connectivity.c/h` - Route connectivity checks
- `topo_group.c/h` - BGP-like route exchange between nodes (NODEINFO/WITHDRAW, изолированные группы UTUN/CHAT)
- `topo_group_connect.c/h` - Авто-подключение к узлам CHAT-группы (3 фазы, бесконечный цикл)
- `topo_group_invite.c/h` - Invite/join канала через прямое ncd-подключение (INVITE_INFO_REQ/RESP)
- `topo_node.c/h` - Node management (модель узла: TOPO_NODE/TOPO_GROUP_NODE, сериализация, node_registry)
- `topo_node_sqlite.c/h` - SQLite persistence for nodes
- `topo_recovery.c/h` - Восстановление каскадно отвалившихся узлов после разрыва соединения
- `routing.c/h` - Обмен IPv4-пакетами UTUN между локальным TUN и сервисом DATA ETCP-router
- `route_lib.c/h` - Локальная таблица IPv4-подсетей UTUN: добавление/удаление маршрутов и поиск узла назначения
- `route6_lib.c/h` - Локальная таблица IPv6-подсетей на radix tree с поиском по самому длинному префиксу
- `route_ping.c/h` - Пинг цели через явно выбранного BGP-посредника для NAT-проверки
- `route_connectivity.c/h` - Прямые UDP/TCP-пробы адресов узла для оценки достижимости и RTT при выборе пиров
- `topo_group.c/h` - Сессии UTUN/CHAT (JOIN/ACCEPT, эпохи, READY), групповые запросы, NODEINFO/WITHDRAW и владение NCD
- `topo_group_connect.c/h` - Подбор пиров CHAT-группы по роли: суперузлы → desktop → mobile; история/RTT внутри класса, backoff и standby burst
- `topo_group_invite.c/h` - Получение описания канала через INVITE_INFO_REQ/RESP; добавление мембера реализовано в chat_join/chat_sync
- `topo_node.c/h` - Модель TOPO_NODE/TOPO_GROUP_NODE, подписанные снимки узлов, сериализация и общий node_registry
- `topo_node_sqlite.c/h` - Хранение подписанных снимков узлов, адресов, каналов и блоков мемберов в общей SQLite
- `topo_recovery.c/h` - Восстановление потерянных маршрутов группы через READY-сессии, отмена попыток при stop
- `conn_mgr.h` + `conn_mgr_core.c`/`conn_mgr_indirect.c`/`conn_mgr_monitor.c` + `conn_mgr_priv.h` — Connection Manager (handle-based API, 3-phase DIR/REV/IND)
- `etcp_router.c/h` - ETCP router/multiplexer (мультиплексирование каналов, transit forwarding)
- `nat_detection.c/h` - NAT type detection
- `etcp_router.c/h` - Надёжные логические каналы сервисов поверх BGP-маршрутов: ACK, окна, транзит и защита от циклов
- `route_tx_stats.c/h` - Скользящее окно подтверждённых payload-байтов логического канала для оценки скорости доставки
- `multicast.c/h` - Рассылка списку узлов группы с разделением маршрута на ветви, локальными подписками и отчётами о потерях
- `nat_detection.c/h` - Проверка mapping и доступности входящих рабочих сокетов через ETCP-пиров с учётом предварительных STUN-измерений
- `address_profile.h` - Профиль endpoint: изменение адреса, доступность входящих и источник измерения ETCP/STUN
- `sock_match.c/h` - Классификация локальных сокетов и адресов пира, отбор совместимых пар для прямого подключения
- `route_crypto.c/h` - Пред/пост-обработка SVC_ROUTE пакетов (encrypt-then-sign)
**Chat (src/chat/)** — децентрализованный P2P чат: сообщения и участники автоматически синхронизируются между всеми узлами канала без центрального сервера.
- `chat_core.c/h` - Главный модуль чата. Единственная точка входа из GUI: принять сообщение, создать канал, изменить настройку. Управляет БД и жизненным циклом всех chat-модулей
- `chat_core.c/h` - API сообщений, каналов, профиля и настроек; chat_service_start/stop управляют чат-сервисом поверх общей БД ядра
- `chat_core_priv.h` - Внутренний API для частей chat_core: общий контекст и утилиты БД. Снаружи не используется
- `chat_sync.c/h` - Связывает чат с ETCP-сетью: отслеживает появление/разрыв соединений, обновляет онлайн-статус узлов, запускает синхронизацию участников каналов, обрабатывает приглашения
- `chat_event.c/h` - Доставка событий из ядра чата в GUI. Единый механизм: любой модуль отправляет событие (новое сообщение, смена участников, статус), GUI получает через один обработчик
- `chat_setting.c/h` - Настройки чата: проверяет корректность значений, хранит текущие. Используется и headless-парсером конфига, и GUI. Не зависит от других модулей
- `db_sync.c/h` - Автоматическая репликация данных между подключёнными узлами. Запись на одном узле — появляется у всех остальных. Криптоподписи и цепочка хешей защищают от подделок
- `chat_setting.c/h` - Проверка и хранение настроек чата per-instance; общая логика для конфигурации и GUI
- `chat_join.c/h` - Регистрация invite-ключа с ACK, пересылка запроса инвайтеру и добавление подписанного мембера; контракт join в заголовке
- `db_sync.c/h` - Репликация подписанных записей SQLite через прямые ETCP-соединения; расхождения по цепочке хешей, новые записи через PUSH
- `member_sync.c/h` - Синхронизация участников каналов поверх merkle_sync. Два подписанных блока (мембер/владелец), по-блочное сравнение версий, верификация подписей. Удаление — только битая запись
- `merkle_sync.c/h` - Универсальный движок синхронизации на Merkle-деревьях (5 уровней × 32 бакета, SHA256). Пиры обмениваются хешами и передают только различия; per-session out-очередь с backpressure. Не привязан к конкретному типу данных (коллбэки `data_ops`)
- `chat_profile.c` - Собственный профиль: имя узла, сетевые адреса, сохранение UI-состояния (часть chat_core)
- `chat_channel.c` - Создание и настройка каналов: готовит таблицы в БД, генерирует криптоключи, запускает подключение ко всем участникам (часть chat_core)
- `merkle_tree.c/h` - Производный индекс Merkle-хешей в SQLite; восстанавливается из данных модели, обновляется с ними в одной транзакции
- `chat_profile.c` - Подписанный профиль мембера: имя, аватар и опции сервисов; локальное UI-состояние (часть chat_core)
- `chat_channel.c` - Создание и настройка каналов: таблицы, криптоключи, CHAT-группа и планировщик подключения (часть chat_core)
- `chat_msg.c` - Отправка и приём сообщений: запись в локальную БД, автоматическая рассылка всем участникам канала (часть chat_core)
- `chat_feedback.c/h` - Подписанные реакции и ответы: версии состояния, сохранение отмен и Merkle-синхронизация между участниками
- `chat_poll.c/h` - Создание опросов, голосование, реакции и JSON-снимки результатов для UI/headless
- `chat_poll_codec.c` - Проверка и wire-кодеки опросов, ответов и реакций; общий каталог эмодзи (API в chat_poll.h)
- `chat_attachment.c/h` - Зашифрованные вложения канала: ключи, запросы доступа, ручное одобрение автора и сохраняемый outbox
- `chat_local.c/h` - Обнаружение устройств чата и обмен готовыми invite-ссылками через локальный UDP-транспорт
- `group_avatar.c/h` - Подписанная аватарка канала: хранение версии в SQLite, проверка подписи и передача блоков через router
- `chat_status.c` - Сбор диагностики: текущее время, активные соединения, типы NAT. Отправляется в GUI (часть chat_core)
- `chat_member.c/h` - Единая структура мембера для отображения в GUI (десктоп/headless/Android), фиксированная сериализация на проводе
- `chat_admin.c/h` - Передача канального приватного ключа (прав админа) другому узлу
- `chat_whisper.c/h` - Whisper speech-to-text транскрипция голосовых сообщений (опционально, асинхронно через media_async)
- `chat_headless_control.c/h` - TCP control-socket для headless chat CLI (JSON line protocol)
- `invite_build.c/h` - Сборка invite-ссылок utun:// (выбор лучшего узла + адреса)
- `invite_link.c/h` - Кодирование utun://: версия, пароль, channel_id, join_key, Reality-параметры, ключ и адреса узла
**Личные сообщения (src/dm/)**
- `dm_core.c/h` - Беседы двух узлов, подписанные E2E-сообщения, SQLite/outbox, доставка через router/mailbox и квитанции
- `dm_crypto.c/h` - Вывод ID беседы и ключа X25519, AES-256-CCM сообщений и E2E-контейнеры медиа через media_crypto
- `dm_history.c/h` - Локальное удаление истории и скрытие бесед; поколения задач и отпечатки удалённых сообщений против повторов
- `dm_mailbox.c/h` - Offline-очередь суперузла: хранение подписанных сообщений и повтор доставки до квитанции получателя
- `dm_media.c/h` - Подготовка, доставка и публикация E2E-вложений; ciphertext-хранилище и отдельные квитанции файлов
- `dm_media_priv.h` - Внутренние SQL/control-операции для dm_media и dm_mailbox_media
- `dm_mailbox_media.c/h` - Сохраняемые задания суперузла для размещения медиа, доставки метаданных и удаления копий до DELETE_ACK
**Звонки (src/call/)**
- `call.c/h` - P2P аудио/видеозвонок: подписанный E2E-сигналинг, состояния, политика занятости и оптимизация пути через conn_mgr
- `call_priv.h` - Общее внутреннее состояние сессии для call.c и call_video_transport.c; доступ только из uasync
- `call_proto.h` - Wire-формат сигналинга, аудиокадров и управления видеотреком; треки/кодеки и причины завершения
- `call_audio.c/h` - Общий аудио-движок: кодирование/декодирование Opus, jitter-буфер и тоны, API feed/pull PCM
- `call_jitter.cpp/h` - FIFO Opus, адаптивный сетевой запас, декодирование на pull и восстановление воспроизведения после пауз
- `call_jitter_window.h` - Окно из десяти секундных интервалов для оценки глубины очереди и изменения задержки доставки
- `call_clock.h` - Метки времени захвата и снимок реально проигранного аудио для синхронизации видеотрека
- `call_delay.cpp/h` - Уменьшение накопленной задержки PCM прямым ускорением и SoundTouch с сохранением исходной шкалы времени
- `call_timeline.cpp/h` - Карта исходного PTS через обработку SoundTouch до позиции аудиоустройства
- `call_video.c/h` - Общий формат H.264/H.265 видеотрека, согласование возможностей и сборка кадров из фрагментов
- `call_video_bitstream.c/h` - Проверка конфигурации и IDR без reorder, профиля/уровня и геометрии H.264/H.265; KEY-пробы кодеков
- `call_video_transport.c/h` - Отдельные router-каналы control/audio/video, backpressure, привязка видео к аудио и запросы KEY
- `call_video_rate.c/h` - Регулятор битрейта и допуска новых видеокадров по скорости доставки, RTT и долгу очереди
- `call_video_port.c/h` - Потокобезопасный обмен кадрами и командами между платформенными video workers и uasync-транспортом
- `call_video_playout.c/h` - Расписание показа по аудиочасам, ожидание/отбрасывание кадров и подтверждение фактического показа
- `call_video_fixtures.h` - Встроенные H.264/H.265 KEY-пробы для проверки базовых режимов кодека
- `call_video_fast_fixtures.h` - KEY-пробы видеорежимов с частотой 60 fps
- `call_video_fullhd_fixtures.h` - KEY-пробы видеорежимов Full HD
- `call_video_portrait_fixtures.h` - KEY-пробы видеорежимов в портретной ориентации
- `call_tones.c/h` - Генерация PCM-тонов паузы и завершения звонка
- `call_headless.c/h` - Отдельный TCP-аудиосокет headless-клиента: обмен PCM; сигналинг остаётся на control-сокете
**Рация (src/radio/)**
- `radio.c/h` - Передача Opus по CHAT-группе, подписки, дедупликация и пересылка по ветвям с подписчиками
- `radio_proto.h` - Wire-формат PTT: источник, поток, seq, FIN и hop-list против циклов
- `radio_audio.c/h` - Общий аудио-движок: независимые RX/TX, Opus, микширование источников, ручной PTT и VAD-автомат
- `radio_jitter.cpp/h` - Буфер кодированных кадров каждого источника с pre-roll и SoundTouch time-stretch на pull
- `radio_vad.h` - Ресемплинг 48→16 кГц и автомат авто-PTT с подтверждением речи, задержкой завершения и учётом занятого канала
- `radio_auto.h` - Общий автомат автопередачи по VAD/уровню звука, без захвата аудио и сетевого I/O
- `radio_call.c/h` - Адресное приглашение участника в рацию канала: сигналинг вызова, занятость, принятие и отмена
- `radio_headless.c/h` - TCP-аудиосокет headless-рации: PCM и команды начала/конца PTT
**Прокси (src/proxy/)**
- `socks_proxy.c/h` - SOCKS5 прокси
- `udp_proxy.c/h` - UDP прокси
- `icmp_proxy.c/h` - ICMP прокси
- `tcp_proxy_server.c/h` - TCP прокси (серверная сторона)
- `tcp_proxy_client.c/h` - TCP прокси (клиентская сторона)
- `proxy_protocol.h` - Общий TCP-протокол и управление потоком: CONNECTED, DATA, кредит WINDOW, упорядоченный FIN
- `socks_proxy.c/h` - Клиентские SOCKS5 CONNECT и HTTP proxy/CONNECT, DNS и передача потока выбранному exit-узлу
- `http_proxy.c/h` - Потоковый HTTP/1.x кодек: проверка заголовков, разбор тела/chunked и учёт потреблённых байтов для WINDOW
- `udp_proxy.c/h` - Передача UDP из TUN через exit-узел, сопоставление ответов и удаление неактивных потоков
- `icmp_proxy.c/h` - Проксирование IPv4 ICMP echo через raw socket exit-узла с восстановлением исходных id/seq
- `tcp_proxy_server.c/h` - Exit-сторона TCP-прокси: реальные исходящие сокеты, потоки по (peer, stream_id), окна и half-close
- `tcp_proxy_client.c/h` - Клиентская сторона TCP-прокси: адаптер TUN/lwIP и обмен с настроенным exit через ETCP-router
**lwIP TCP (src/lwip_tcp/)**
- `lwip_tcp.c/h` - lwIP TCP стек
@@ -352,34 +497,102 @@ MEMBER_SYNC=27
- `lwip_tcp_priv.h`, `lwip_tcp_opts.h` - Внутренние настройки lwIP
**Media Delivery (src/media_delivery/)**
- `media_delivery.c/h` + `media_delivery_proto.h` - Доставка медиафайлов, протокол
- `media_download.c/h` - Скачивание медиа
- `media_index.c/h` - Индексация медиафайлов
- `media_delivery.c/h` - Доставка блоков медиа каналов, учёт держателей, репликация доступности между суперузлами и relay
- `media_delivery_proto.h` - Кодограммы канального медиа: QUERY, BLOCK_REQ/CHUNK/DONE, HAVE_BLOCK и SUPER_REPL
- `media_download.c/h` - Загрузка блоков с выбором держателей, failover, контролем простоя, проверкой подписей и сборкой файла
- `media_index.c/h` - Индекс media_files: UUID, хеши и подписи блоков, асинхронная регистрация и удаление медиа канала
- `file_transfer.c/h` - Передача неизменяемого файла между заданными узлами; авторизация lookup и владение CM handle до завершения
**Media Async (src/media_async/)**
- `media_async.c/h` - Асинхронный движок (thread-per-task для crypto/file операций)
- `media_async.c/h` - Фоновые crypto/file-задачи: work в pthread, done в uasync; destroy ждёт workers и завершает ожидающие callbacks с CANCELLED
- `attachment.c/h` - Общие метаданные file/voice/video, проверка и компактная wire-сериализация
- `voice_file.c/h` - Worker-кодирование PCM в контейнер Opus с длительностью и waveform для desktop/Android
- `attachment_send.c/h` - Асинхронная подготовка голоса/видео и отправка в зафиксированный канал или DM
- `media_crypto.c/h` - Общие AES-256-CCM примитивы и порционное шифрование/проверка файлов для DM и групповых вложений
- `encrypted_file.c/h` - Контейнер группового вложения: шифрование метаданных/файла, проверка SHA256 и durable-публикация результата
**Трансляции (src/stream/)** — live-аудио/видео CHAT-группы с подписью источника, Reed–Solomon и раздачей через участников.
- `stream.c/h` - Жизненный цикл трансляции, локальные handles издателя/читателей, события и публикация подписок
- `stream_priv.h` - Внутренний доступ к сессиям, приём проверенных DATA/END и взаимодействие с планировщиком relay
- `stream_proto.h` - Форматы подписанной пачки и RS-частей, дескриптор источника, размеры и коды результата
- `stream_block.c/h` - Подпись пачки Ed25519 до RS-кодирования, разбор частей и проверка восстановленных данных
- `stream_buffer.c/h` - Ограниченное кольцо RS-частей: дедупликация, восстановление пачек, проверка подписи и вытеснение старых данных
- `stream_source.c/h` - Упаковка исходных байтов в подписанные RS-пачки, сброс неполной пачки по таймеру и завершение источника
- `stream_member.c/h` - Объявления источников и подписок в опциях подписанного мембера; разбор и проверка принадлежности источника
- `stream_wire.c/h` - Протокол выдач и relay: кодограммы управления, пути, метрики и подписанное завершение END
- `stream_network.c/h` - Выдача блоков через прямой ETCP, NCD-владение, управляющие очереди, аренда подписок и reverse-запросы
- `stream_optimizer.c/h` - Автоматический выбор и перенос поставщиков субпотоков, пробы доступности и привлечение relay
- `stream_metrics.c/h` - Оценка относительного отставания субпотока и истории проблем по часам источника без NTP
- `stream_load.c/h` - Учёт логической очереди DATA и её роста для обнаружения перегрузки раздающего узла
- `stream_media.c/h` - Формат uAV2: упаковка/проверка видеокадров, конфигурации кодека, PTS и Opus внутри FEC-пачек
- `stream_media_buffer.c/h` - Медиаиндекс общего RS-кольца и независимые курсоры audio/video; сборка кадров и восстановление с KEY
- `stream_publisher.c/h` - Общий потокобезопасный publisher desktop/Android: очередь готовых кадров, публикация и завершение с FIN
- `stream_viewer.c/h` - Независимые viewers: запрос ограниченных батчей по бюджетам треков, уведомления готовности и конца audio/video
- `stream_playback_audio.cpp/h` - Opus/SoundTouch и очередь готового PCM; ускорение без изменения высоты и привязка к позиции устройства
- `stream_playback_clock.c/h` - Общие часы воспроизведения и плавный догон live-потока по непрерывному запасу готового материала
**Файлы участников (src/member_files/)**
- `member_files.c/h` - Сервер и асинхронный клиент хранилища CHAT-группы: права доступа, запросы и события через защищённый router
- `protocol.c/h` - Wire-кодек файловых команд, снимки версий каталогов/файлов и токены временных загрузок
- `storage.c/h` - Серверные каталоги участников, квоты, файловые операции, временные загрузки и их очистка
- `transfer.c` - Передача файлов с CM handle, backpressure, дисковым worker, проверкой хеша и публикацией результата
- `disk.c/h` - Дисковые примитивы POSIX/Windows: безопасное разрешение путей без ссылок и I/O по 64-битному смещению
- `worker.c/h` - Единственный дисковый pthread с ограниченной очередью и возвратом результатов в uasync
- `private.h` - Внутреннее состояние запросов, передач, серверного хранилища и дискового worker
**Локальная сеть (src/local_transport/)**
- `local_transport.c/h` - Открытый UDP-транспорт LAN: обнаружение устройств, доставка сообщений с ACK/повторами и ограничения входящего потока
- `platform.c/h` - Снимок IPv4/IPv6 интерфейсов, broadcast/multicast-адреса, приём метаданных UDP и проверка локальности источника
- `protocol.h` - Wire-формат объявлений, сообщений и ACK: ID/эпохи устройств, ID запроса и проверка UTF-8 имени
**Libraries (lib/)**
- `u_async.c/h` - Async event loop (epoll/poll/select, timers via timeout_heap)
- `ll_queue.c/h` - Lock-free linked list queue with callbacks, hash index, threshold waiter
- `memory_pool.c/h` - Fast object pool allocator (pre-allocated blocks)
- `ll_queue.c/h` - Двусвязная очередь одного uasync-потока с callbacks, хеш-индексом и ожиданием свободного места
- `memory_pool.c/h` - Пул объектов: ленивое выделение и повторное использование, до 64 свободных объектов в кеше
- `debug_config.c/h` - Debug logging system (levels, categories, dual output, runtime config)
- `timeout_heap.c/h` - Min-heap for timer management (used by u_async)
- `mem.c/h` - Memory wrappers with leak tracking (`u_malloc`/`u_free`/`u_calloc`/`u_strdup`)
- `serialize.c/h` - Binary serialization utilities
- `reed_solomon.c/h` - Кодирование и восстановление пачки данных Рида—Соломона с настраиваемым числом субблоков и избыточностью
- `video_plane.c/h` - Копирование YUV-плоскости с поворотом 0/90/180/270°, учётом stride и проверкой границ без выделения памяти
- `swm_min.c/h` - Sliding window minimum (for RTT min tracking)
- `getmyip.c/h` - Get local IP / default route detection
- `getmyip.c/h` - Локальный IP, который ОС выберет для UDP к заданному адресу (bind/connect/getsockname)
- `myip.c` - My IP helper (no header)
- `socket_compat.c/h` - Cross-platform socket compatibility (Linux/FreeBSD/Windows)
- `platform_compat.c/h` - Cross-platform compatibility layer (byte order, time, random)
- `radix.c/h` - Radix tree for IP routing lookups
- `tcp_io.c/h` - TCP I/O abstraction (Windows IOCP, Linux epoll)
- `tcp_io.c/h` - Неблокирующий TCP поверх uasync/ll_queue: backpressure, FIN/close и callbacks
- `sqlite3.c/h` - SQLite3 amalgamation (embedded database)
- `opus_codec.c/h` - Opus audio codec wrapper
- `audio_compressor.c/h` - Audio dynamic range compressor
- `speex_aec.c/h` - Эхоподавление SpeexDSP: согласование playback/capture PCM через линию задержки и диагностика её заполнения
- `silero_vad.c/h` - Стриминговый VAD через ONNX Runtime: окна 512 отсчётов, 16 кГц моно, вероятность речи и сброс состояния
- `silero_vad_model.inc` - Встроенная ONNX-модель Silero VAD для silero_vad_create_default
- `json_flat.c/h` - Flat JSON parser
- `strbuf.c/h` - Безопасный растущий printf-буфер (замена snprintf-цепочек)
- `async_dns.c/h` - Асинхронный (неблокирующий) DNS-резолвер A-записей поверх uasync, адаптер над libdns
- `dns.c/h` - Рекурсивный reentrant DNS-резолвер (libdns, MIT)
- `miniaudio.h` - Воспроизведение/захват аудио (single-header)
- `dr_mp3.h` - MP3-декодер (single-header, на базе minimp3)
- `liblmdb/` - Встраиваемый key-value store LMDB
- `libopus/` - Встраиваемый кодек Opus
- `speexdsp/` - Встроенные исходники эхоканселлера SpeexDSP и KISS FFT
- `soundtouch-master/` - SoundTouch: изменение темпа аудио в jitter-буферах звонка и рации
- `wintun.h` - Wintun API header for Windows TUN driver
**Проверки (tests/)**
- `Makefile.am` - Состав тестовых целей, зависимости и отдельные проверки runner, service IDs и скрипта сборки
- `run_tests.py` - Запуск тестов ограниченным пулом с отдельными логами и последовательными барьерами для CPU/LAN-проверок
- `test_run_tests.py` - Проверки пула runner, эксклюзивного запуска, обработки ошибок и сигналов
- `test_service_ids.py` - Проверка уникальности ID транспортных и router-сервисов
- `test_build_script.py` - Регрессии выбора платформы, конфигурации и инкрементальной работы build.sh
- `stream_netns/` - Linux network namespaces: топологии, NAT/STUN-сценарии и проверка трансляций в изолированной сети
- `local_transport_ipv6.sh` - Проверка IPv6 link-local обнаружения и доставки в изолированной локальной сети
**Memory Pools in UTUN_INSTANCE:**
- `data_pool` — для данных пакетов (payload), используется input_queue/output_queue фрагментами
- `pkt_pool` — для struct ETCP_DGRAM (сетевые пакеты на отправку/приём)
@@ -387,7 +600,7 @@ MEMBER_SYNC=27
### Key Components
- **UASYNC:** One per thread. Async event loop (epoll/poll), timers via timeout_heap
- **LL_QUEUE:** Lock-free queue with auto-callback, hash index lookup, threshold waiter
- **LL_QUEUE:** Очередь с auto-callback, хеш-индексом и threshold waiter; не средство обмена между потоками
- **Memory Pool:** Fast allocation for hot-path objects (packets, inflight entries, fragments)
- **ETCP:** TCP-like reliable protocol with encryption, multi-link, load balancing
- **Secure Channel:** AES-CCM + X25519 key exchange, nonce-based encryption, pubkey obfuscation
@@ -403,6 +616,8 @@ MEMBER_SYNC=27
- Очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой.
- Порог задаётся через `queue_set_threshold(q, max_packets, max_bytes)`.
- Используй `queue_waiter_wait` для ожидания освобождения очереди до заданного порога.
- Handle ожидания обнуляется целиком, живёт до callback/отмены и отменяется до освобождения владельца.
Callback может быть синхронным. `queue_entry_free` не освобождает dgram — его освобождают отдельно.
### Чтение из очереди
- Используй `queue_set_callback`: при вызове callback обработай один или несколько элементов, потом вызови `queue_resume_callback`.
@@ -422,12 +637,13 @@ MEMBER_SYNC=27
## Config Rules
- В серверном конфиге только собственные ключи и нет секций `[client]`
- В клиентском конфиге есть собственные ключи и pubkey каждого сервера в секции `[client]`
- Chat-настройки хранятся в секции `[chat]`. Парсер вызывает `chat_setting_set(key, value)` для каждого ключа — неизвестные ключи вызывают ошибку. Пример:
- Chat-настройки хранятся в `[chatserver]` (также `[chat]`). Парсер обрабатывает общие поля секции,
затем вызывает `chat_setting_set_state(&cfg->global.chat_settings, key, value)`; неизвестные ключи логируются как ошибка. Пример:
```ini
[chat]
[chatserver]
storage_autoload=1
storage_autoload_maxsize_mb=10
storage_maxsize_gb=1
storage_unit_size=10M
storage_total_size=1G
opus_codec_preset=1
compressor_enabled=0
compressor_max_gain_db=25
@@ -445,7 +661,8 @@ MEMBER_SYNC=27
Если видишь спам-логи - подумай как их выборочно отключить чтобы не мешали.
Для отладки добавляй в DEBUG_CATEGORY_DEBUG диагностические сообщения где надо. И включи эту категорию в настройках. Убирай только после проверки (путём запуска) когда все ошибки устранены.
сообщение выводится по или - либо debug_set_level(DEBUG_LEVEL_TRACE) - выводится ВСЁ независимо от настроек по категориям. Тоесть берется max(global level, category lavel)
Явный уровень категории заменяет глобальный. DEBUG_LEVEL_NONE наследует глобальный уровень;
DEBUG_LEVEL_DISABLED полностью отключает категорию, в том числе при глобальном TRACE (см. debug_should_output).
Твой бич - ты постоянно гадаешь и анализируешь код. Что приводит к снежному кобу ошибок и неверных гипотез. 10 раз повторяю - только логи логи логи и никакого гадания. Лоооги!!! правильные логи покажут всё с предельной точностью. Вся суть отладки - информативные логи
И смотри логи. не задавливай их grep-ом. лучше больше. единственное с чем борись - это бесполезные спам логи.
@@ -455,7 +672,8 @@ MEMBER_SYNC=27
Но полезные смотри всегда, и всегда оставляй логи которые выводятся нечасто.
Частые логи - это трафик которые >100 раз повторяются. Но которые мало раз обязательно оставлять и выводить.
можешь в тесте включить логи в нужный интервал чтобы не спамить. debug_set_level(DEBUG_LEVEL_TRACE) и выключить debug_set_level(DEBUG_LEVEL_NONE)
В тесте можно временно поднять нужные категории до TRACE, сохранив прежние уровни и восстановив их после участка.
Одного debug_set_level(DEBUG_LEVEL_NONE) недостаточно для отключения категорий с явно заданным уровнем.
Если видишь проблему и ее решение неочевидно то выстраивай диагностику вокруг неё пока не будет очевидно где и что происходит не так. Это базовое и обязательное требование к отладке.
Подробные логи ты должен выводить не обрезая всегда и анализировать. Если лог большой - выведи в файл и анализируй файл.
Рассуждение должно быть примерно таким:
@@ -497,7 +715,7 @@ MEMBER_SYNC=27
- Нельзя ничего восстанавливать из репозитория не спрашивая
- Для отладки не printf а DEBUG_*
- Запрещено прямое включение arpa/inet.h, sys/time.h, unistd.h — использовать ../lib/platform_compat.h + socket_compat.h
- Перед сборкой всегда make clean
- Меняй режим и флаги сборки через `./build.sh`: он отслеживает конфигурацию и очищает объекты только при необходимости.
- Все лишнее что менял при отладке - строго вернуть назад в состояние до вмешательства
## Написание кода
@@ -506,12 +724,17 @@ MEMBER_SYNC=27
- Обязательно наличие подробной диагностики в функциях кода которые не сильно спамят (не часто вызываются).
- Для каждого отладочного вывода подумай какая из доступной информация будет полезна чтобы можно было наиболее завершенно оценить состояние алгоритма и состояний влияющих на алгоритм.
## Оформление кода
- в .c: перед каждой функцией - краткое но понятное описание что делает функция (кроме совсем простых)
- в .h: в начале - описание модуля - для чего предназначен, как пользоваться, нюансы. Далее структуры с комментариями, далее публичные функции с описаниями (что делает, как пользоваться, какие аргументы, возврат, нюансы работы)
## chatgui (GUI Chat Client)
Chatgui — десктопный GUI-чат на Qt 6 (Qt 5 fallback), отдельный проект внутри репозитория. Не связан с autotools-сборкой utun.
Chatgui (бинарник `vibechat`) — десктопный чат на Qt 6 (Qt 5 fallback), отдельная CMake-сборка общего ядра.
Корневой `build.sh` дополнительно вызывает её, если уже существует `tools/chatgui/build/CMakeCache.txt`.
В chat gui интегрированы библиотеки utun. Чат и библиотеки работают в разных потоках. Поэтому нужно использовать семафоры, сокеты или другие механизмы синхронизации (uasync_post, uasync_memsync, uasync_get_wakeup_fd)
GUI работает в Qt-потоке, ядро и чат — в uasync-потоке. Команды передаются через
`gui_bridge_post_uasync()` / `uasync_post()`, события возвращаются через Qt-сигналы.
База данных sqlite в chatgui: можно писать (update) из потока utun / instance. можно только читать из gui потока.
@@ -519,38 +742,45 @@ Chatgui — десктопный GUI-чат на Qt 6 (Qt 5 fallback), отде
- **Язык:** C++20
- **Фреймворк:** Qt 6 (предпочитаемый) или Qt 5.15+ (Widgets + Network)
- **Сборка:** CMake 3.16+
- **БД:** SQLite3 (WAL mode, встроенный `db/sqlite3.c` amalgamation)
- **БД:** SQLite3 (WAL mode, компилируется общий `lib/sqlite3.c`)
- **Анимации:** rlottie (C API) + zlib (gzip-декомпрессия TGS)
### Сборка
```bash
# Установка зависимостей
sudo apt install librlottie-dev zlib1g-dev qt6-base-dev
sudo apt install librlottie-dev zlib1g-dev qt6-base-dev libssl-dev libx11-dev libxfixes-dev libpipewire-0.3-dev
# или qtbase5-dev для Qt 5 fallback
# Сборка
cd tools/chatgui && mkdir -p build && cd build
cmake .. && make -j4
./chatgui
./vibechat
```
### Конфиг и логи чатгуи
- **Конфиг:** `tools/chatgui/build/chatgui.cfg` (в `.gitignore`, содержит приватные ключи)
- **Конфиг:** `vibechat.cfg` рядом с бинарником (обычно `tools/chatgui/build/vibechat.cfg`, содержит приватные ключи)
- Секция `[gui]`: `debug_file`, `debug_level`, `debug_categories=cat=level,...`
- Секция `[control]`: `ip`, `port` для подключения etcpmon
- Секция `[ntp]`: синхронизация времени
- **Лог:** `tools/chatgui/build/chatgui.log` (путь задаётся в конфиге `debug_file`)
- **БД чата:** `tools/chatgui/build/chat_data/` (SQLite, путь из `db_path` в конфиге)
- **БД чата:** `chats.db` в каталоге `[gui] db_path` (по умолчанию `chat_data/` рядом с бинарником)
### Структура файлов (src/)
| Файл | Назначение |
|------|-----------|
| `main.cpp` | Точка входа, инициализация QApplication |
| `mainwindow.h/cpp` | Главное окно, QSplitter (ChannelList \| MessageList \| AccountList), трей |
| `chatview.h/cpp` | QListView с фоновым изображением, hover-сигнал, drag-to-select |
| `messagelist.h/cpp` | QStandardItemModel, stub-данные, инициализация анимаций, hover→activate |
| `main.cpp` | Точка входа, QApplication, язык интерфейса и запуск единственного экземпляра профиля |
| `single_instance.h/cpp` | Блокировка каталога профиля и передача запроса показа окна уже работающему экземпляру |
| `gui_language.h` | Выбор языка интерфейса независимо от регионального формата дат и чисел |
| `mainwindow.h/cpp` | Главное окно, каналы/DM, трей, события ядра, звонки, локальные приглашения и окна трансляций |
| `chatview.h/cpp` | Список сообщений: фон, hover, выделение текста и прокрутка истории мышью/клавиатурой |
| `messagelist.h/cpp` | Модель сообщений из БД, обновления доставки/медиа, анимации, состояние просмотра канала |
| `messagedelegate.h/cpp` | QStyledItemDelegate: бабблы, текст, цитаты, реакции, оверлей анимаций |
| `messagefeedback.cpp` | Действия MessageList: создание опроса, голосование, выбор реакции и показ результатов |
| `attachment_key_ui.cpp` | Список запросов на расшифровку групповых вложений и решения автора о выдаче ключа |
| `message_links.h/cpp` | Разбор границ utun:// и HTTP/HTTPS ссылок для подсветки и обработки нажатий в сообщении |
| `message_notification.h/cpp` | Уведомление о сообщении без захвата фокуса, переход в беседу и скрытие после активности пользователя |
| `user_activity_monitor.h/cpp` | Ожидание первой активности через системный idle time либо события приложения |
| `emoji.h/cpp` | EmojiData/EmojiCategory/EmojiType, 4 категории, `g_animatedEmojiMap` |
| `emojipanel.h/cpp` | Эмодзи-пикер: QTabWidget + QGridLayout, EmojiButton с hover-анимацией |
| `emojitabbar.h/cpp` | Кастомный QTabBar для эмодзи-панели |
@@ -558,20 +788,107 @@ cmake .. && make -j4
| `lottieicon.h/cpp` | Загрузка TGS (gzip→zlib), рендер кадров через `lottie_animation_render` |
| `animtimer.h/cpp` | Синглтон-таймер 30fps, refcount activate/deactivate, сигнал ticked |
| `channellist.h/cpp` `channeldelegate.h/cpp` | Список каналов с аватарами и превью |
| `accountlist.h/cpp` | Список аккаунтов/пользователей |
| `accountlist.h/cpp` `accountdelegate.h/cpp` | Список аккаунтов/пользователей и его делегат отрисовки |
| `memberlistmodel.h/cpp` | Модель списка участников канала |
| `memberpropsdialog.h/cpp` | Диалог свойств участника |
| `flagpainter.h/cpp` | Отрисовка иконок мембера (admin/mod/supernode/storage) |
| `joindialog.h/cpp` | Диалог присоединения к группе/каналу |
| `invitedialog.h/cpp` | Диалог приглашения пользователя |
| `invite_link.h/cpp` | Логика invite-ссылок |
| `inviteby.h/cpp` | Диалог приглашения по invite-ссылке (отправка через узел) |
| `dminvitedialog.h/cpp` | Выбор канала для приглашения собеседника из DM с сопоставлением результата по request_id |
| `settingsdialog.h/cpp` | Окно настроек |
| `creategroupdialog.h/cpp` | Диалог создания группы |
| `channelsettingsdialog.h/cpp` | Диалог настроек канала |
| `renamedialog.h/cpp` | Диалог переименования |
| `networksettingspage.h/cpp` | Страница сетевых настроек |
| `addressprofiletext.h` | Читаемое представление mapping, доступности входящих и источника NAT-измерения |
| `nodespage.h/cpp` | Таблица узлов + дамп в настройках |
| `statuspage.h/cpp` | Страница статуса (NTP + соединения + живые часы) |
| `databasesettingspage.h/cpp` | Страница обслуживания БД в настройках |
| `storagesettingspage.h/cpp` | Страница настроек хранилища |
| `soundsettingspage.h/cpp` | Страница настроек звука |
| `audiodevicesettingspage.h/cpp` | Страница настроек аудиоустройств |
| `sound_manager.h/cpp` | Общий miniaudio-контекст, уведомления/PCM, микширование голоса, маршруты устройств и восстановление |
| `audio_device.h/cpp` | Жизненный цикл miniaudio-устройства, прогресс callbacks, пауза и повтор запуска после сбоя |
| `audio_buffer_tuning.h` | Подбор размера аппаратного аудиобуфера по повторным XRUN с сохранением настройки при reopen |
| `audio_capture.h/cpp` | Совместное использование одного микрофона трансляцией и рацией, подписки PCM и смена профиля захвата |
| `audio_capture_lease.h` | Владение захватом, исключающее одновременную работу publisher, звонка и записи голосового сообщения |
| `audio_event_queue.h` | Ограниченная очередь PCM/команд с несколькими producers и резервом для управляющих событий |
| `audio_frame_ring.h` | Предвыделенное SPSC-кольцо кадров переменной длины под mutex; при переполнении удаляет старейший |
| `voice_audio_io.h/cpp` | Workers захвата/AEC и воспроизведения; упорядочивание PCM/PTT/mute и итоговый render-reference |
| `voice_capture_clock.h` | Непрерывные PTS захваченного PCM по позиции сэмплов с плавной поправкой аппаратного времени |
| `voice_playback_buffer.h` | Адаптивный запас готового PCM: увеличение после недостачи и уменьшение при стабильном выводе |
| `audiorecorder.h/cpp` | Захват голосовых сообщений, worker обработки PCM, компрессор, индикатор уровня и передача записи задаче |
| `voicemessageencoder.h/cpp` | Выбор Opus preset и создание медиа-каталога; кодирование выполняет общий voice_file |
| `voiceplayback.h/cpp` | Воспроизведение голосовых сообщений |
| `callwindow.h/cpp` | Окно аудио/видеозвонка: управление, выбор камеры, локальное/удалённое видео и статистика |
| `call_audio_engine.h/cpp` | Desktop-контур звонка: захват, VoiceAudioIo, общий выход SoundManager и завершение аудиосессии |
| `call_video_engine.h/cpp` | Desktop-видеозвонок: камера, TX/RX workers, обмен с ядром и показ кадров по аудиочасам |
| `call_video_encoder.h/cpp` | Кодер звонка: согласованный формат, смена битрейта с drain прежнего кодера и подтверждение настройки |
| `call_video_decoder.h/cpp` | FFmpeg-декодер H.264/H.265 с выбором GPU/CPU и сохранением PTS при задержанном выходе |
| `call_camera_capture.h/cpp` | Управление отдельным процессом камеры, каталог устройств/режимов и получение кадров |
| `call_camera_worker.cpp` | Процесс Qt-захвата камеры: настройка устройства, отбор кадров и выдача пикселей через shared memory |
| `call_camera_source.h` | Интерфейс backend камеры для процесса захвата и подстановки устройств в тестах |
| `call_camera_protocol.h` | Локальный протокол управляющих команд, каталога устройств и FRAME/ACK процесса камеры |
| `call_camera_buffer.h` | Владение shared-memory буфером пикселей между процессами захвата и GUI |
| `call_camera_rate.h` | Временное отключение снижения FPS из-за автоэкспозиции V4L2 с восстановлением настройки камеры |
| `call_video_cadence.h` | Отбор исходных кадров под заданный FPS без накопления ошибки дробного периода |
| `call_video_capture_clock.h` | Привязка времени камеры к монотонным часам с коррекцией задержки первого callback |
| `call_video_queue.h` | Ограниченная SPSC-очередь кадров между video workers без mutex и выделений памяти |
| `call_video_wake.h` | Пробуждение video worker по событию или абсолютному монотонному deadline на POSIX/Windows |
| `radio_audio_engine.h/cpp` | Desktop-контур рации: независимые захват/прослушивание, PTT/VAD и подключение к общему выходу |
| `radiopanel.h/cpp` | Панель активной рации: выбор аудиоустройств, VAD, PTT и состояние передачи из ядра |
| `radiosettingspage.h/cpp` | Настройки рации и запись глобальных сочетаний PTT |
| `ptt_hotkey_manager.h/cpp` | Глобальный PTT: физические клавиши Windows/X11, системный портал Wayland |
| `ptt_key_chord.h` | Хранение физических сочетаний с отдельными левыми/правыми модификаторами и запись одновременного нажатия |
| `ptt_portal.h/cpp` | D-Bus-сессия GlobalShortcuts для Wayland, настройка сочетаний и обработка нажатий/отпусканий |
| `media_blocks.h/cpp` | Чтение/сборка фрагментированных медиафайлов |
| `imageviewer_window.h/cpp` | Окно просмотра изображений |
| `videoplayer_engine.h/cpp` | Движок декодирования видео |
| `videoplayer_window.h/cpp` | Окно просмотра видео |
| `live_video_encoder.h/cpp` | Общий FFmpeg-кодер live-видео для звонка/трансляции, GPU/CPU, KEY-конфигурация и capture PTS |
| `video_frame_image.h` | Выделение выровненного RGB-буфера FFmpeg и передача владения QImage без копирования |
| `stream_publish_engine.h/cpp` | Публикация камеры/экрана и Opus: workers кодирования, локальный preview и завершение через общий publisher |
| `stream_publish_window.h/cpp` | Настройки и управление трансляцией; закрытие окна сохраняет работающий engine |
| `stream_portal_capture.h/cpp` | Захват экрана Wayland через ScreenCast portal/PipeWire, выбор монитора и режима курсора |
| `stream_screen_cursor.h/cpp`, `stream_screen_cursor_win.cpp` | Наложение системного курсора на кадр выбранного экрана средствами X11/Windows |
| `stream_video_modes.h` | Выбор размеров/FPS камеры и пропорционального масштабирования экрана для трансляции |
| `stream_qp_control.h` | Плавная корректировка потолка QP по фактическому выходному видеобитрейту |
| `stream_sources.h` | Разбор снимка активных источников канала из CHAT_EVT_STREAM_SOURCES |
| `streamplayer_window.h/cpp` | Просмотр live-потока: отдельные audio/video workers, смешивание PCM и синхронизация по часам устройства |
| `streamgrid_window.h/cpp` | Сетка трансляций канала и разворачивание плитки с сохранением плееров; своя плитка использует локальный preview |
| `connmonitorwindow.h/cpp` | Окно мониторинга ETCP-соединений (standalone) |
| `qrcode_utils.h/cpp` | Утилиты для QR-кодов |
| `debug_ui.h` | Отладочный UI |
### Другие поддиректории
- `db/` — SQLite3 amalgamation (`sqlite3.c/h`), `db_manager.h/cpp` (схема: nodes, node_addresses, channels, msg_<channel>, peers_<channel>, local_identity, accounts, ui_state)
- `transport/` — интеграция с uTun: `gui_bridge.h/gui_bridge_impl.cpp` (Qt signal-based обмен), `chat_core.h/c` (управление), `chat_sync.h/c` (синхронизация), `db_sync_stub.c` (заглушка БД), `node_config.h/cpp`, `config_updater.h/cpp`, `utun_node.h/cpp`, `topo_node_sqlite.h/c`
- `db/` — `db_manager.h/cpp`: доступ GUI к данным nodes, channels, msg_<channel>, peers_<channel> и состоянию интерфейса;
копия `db/sqlite3.c/h` не используется текущей CMake-целью, SQLite берётся из `lib/`
- `transport/` — интеграция GUI с ядром; файлы перечислены ниже
- `src/avatars/` — `avatarmanager.h/cpp`: загрузка/публикация аватаров участников через member_files, подписанные ссылки и очередь передач;
`avatarcache.h/cpp`: дисковый кеш, подготовка изображений и очистка; `avatarcrop.h/cpp`: выбор области;
`avatargallery.h/cpp`: просмотр аватаров пользователя; `groupavatarmanager.h/cpp`: подготовка и кеш подписанных аватарок каналов
- `src/member_files/` — `browser.h/cpp`: браузер серверных файлов группы; `batch.h/cpp`: последовательные операции над файлами/деревьями,
фоновый локальный обход, загрузка/скачивание и отмена
- `libutun/CMakeLists.txt` — состав и зависимости CMake-сборки общего ядра из `src/` и `lib/`
- `CMakeLists.txt` — приложение vibechat, Qt-зависимости, ресурсы и цели GUI-тестов
- `tests/` — проверки аудиотракта, PTT, видеозвонка, захвата камеры/экрана, трансляций, файлового браузера и GUI-компонентов
- `resources/` — `bg.jpg`, `chatgui.qrc` (встраивает 5 TGS в бинарник), `animations/*.tgs`
- `zxing-cpp/` — встроенная библиотека QR-кодов; `third_party/qhotkey/` — исходники QHotkey
### Интеграция с ядром (transport/)
| Файл | Назначение |
|------|-----------|
| `gui_bridge.h` / `gui_bridge_impl.cpp` | Команды GUI → uasync и события ядра → Qt-сигналы |
| `utun_node.h/cpp` | Запуск и остановка ядра/чата в выделенном uasync-потоке |
| `node_config.h/cpp` | Загрузка и сохранение конфигурации узла |
| `config_updater.h/cpp` | Изменение настроек в конфиге |
| `miniaudio_impl.c` | Реализация single-header miniaudio в отдельной единице трансляции |
| `audio_diagnostics.h/cpp` | Диагностика этапов аудио и событий backend; записывает метаданные без PCM |
| `stream_bridge.h/cpp` | Потокобезопасный мост к общим publisher/viewers: session IDs, каталог источников, запросы батчей и пробуждение workers |
### Система анимированных эмодзи
@@ -665,29 +982,81 @@ void lottie_animation_destroy(Lottie_Animation *anim);
---
## Key Documentation Files
- `/doc/etcp_protocol.txt` - ETCP протокол (формат кодограмм, ACK, handshake, keepalive)
- `/doc/etcp_arch.md` - ETCP архитектура
- `/doc/etcp_config.txt` - Конфигурация ETCP
- `/doc/etcp_router_arch.md` - ETCP роутер архитектура
- `/doc/route_p2pconn.txt` - Route peer-to-peer соединения
- `/src/route_bgp.txt` - BGP обмен маршрутами (дизайн-документ)
- `doc/service_lifecycle.md` — ядро, независимые сервисы, владение ресурсами и остановка
- `doc/node_snapshot.md` — подписанная запись узла, timestamp, SQLite и обновление NCD
- `doc/etcp_protocol.txt` — ETCP: кодограммы, ACK, handshake, keepalive
- `doc/etcp_arch.md` — архитектура ETCP
- `doc/etcp_router_arch.md` — архитектура маршрутизатора
- `src/chat/chat_join.h` — нормативный протокол добавления участника
- `src/routing_layer/topo_group.h` — протокол групповой сессии и критерий READY
- `tests/test_chat_join_e2e.c`, `tests/test_topo_recovery.c`, `tests/test_services.c` — проверки join, recovery и жизненного цикла
- `doc/dm_arch.md`, `doc/chat_call_arch.md` — личные сообщения и звонки
- `doc/call_video.txt` — видеотрек звонка: возможности кодеков, транспорт, битрейт, аудиочасы и показ кадров
- `doc/stream.txt` — трансляции: RS-пачки, выдачи/relay, выбор поставщиков, медиакурсоры и платформенные плееры
- `doc/stun_nat.md`, `doc/nat_detection.txt` — STUN-измерения и определение доступности рабочих UDP-сокетов
- `doc/member_files.txt` — файловое хранилище участников, права, wire-команды и передачи
- `doc/group_avatar.txt` — подписанная аватарка канала, хранение и репликация
- `doc/encrypted_attachments.md` — контейнер группового вложения, запросы ключа и ручная выдача доступа
- `doc/pm_invites.md`, `doc/message_links.md` — приглашения из личной беседы и ссылки внутри сообщений
- `doc/proxy_protocol.md` — TCP/UDP/ICMP-прокси, управление окнами, half-close и проверки
- `doc/lwip_nuances.md` — особенности адаптированного lwIP TCP-стека
- `doc/linux_audio_recovery.md` — диагностика аудиотракта Linux, контракты владения и восстановление устройств
- `src/media_delivery/file_transfer.h`, `src/dm/dm_media.h` — контракты передачи файлов и E2E-вложений DM
- `src/call/call_audio.h`, `src/radio/radio_audio.h` — API общего голосового стека и правила потоков
- `tools/chatcli_commands.txt` — команды headless CLI, в том числе DM, звонки и рация
## Список задач по проекту
Список задач — `doc/tasks.md` относительно корня репозитория (текущая работа, берём по одной задаче сверху).
При нахождении попутных багов/fail/флаков тестов добавляем задачу (либо фиксим сразу если баг простой).
Как сделали - помечаем [+] выполнено.
## chatgui-android (Android P2P Chat)
**Расположение:** `tools/chatgui-android/`
Android-версия чатгуи — P2P чат на STCP (TCP), UI на Jetpack Compose (Kotlin).
C-ядро: `libutun_lite` (выборочная компиляция нужных .c из `lib/` и `src/`).
Android-версия чатгуи — P2P чат на общем ETCP/STCP-ядре, UI на Jetpack Compose (Kotlin).
C-библиотека `utun_lite` собирает `lib/` и `src/` по `libutun_lite/utun_sources.cmake`;
`instance_lite` запускает ядро и чат в отдельном потоке, без UTUN-сервиса.
Сборка: CMake (headless, Linux) + Gradle/NDK (Android APK).
- 'fw' - собрать и обновить chatgui-android на телефоне (clean + сборка + install)
- 'fw' - собрать и обновить chatgui-android на всех подключенных телефонах (clean + сборка + install)
**Подробная инструкция:** `tools/chatgui-android/AGENTS.md`
**Chat-модули:** все файлы из `src/chat/` (описаны выше в секции «Chat») компилируются в `libutun_lite`.
### Основные файлы native-слоя
Пути относительны к `tools/chatgui-android/`; UI и Android-сервисы описаны в локальном `AGENTS.md`.
| Файл | Назначение |
|------|-----------|
| `libutun_lite/utun_sources.cmake` | Общий список исходников ядра для Android и headless |
| `libutun_lite/CMakeLists.txt` | Сборка native-библиотеки и зависимостей |
| `libutun_lite/instance_lite.c/h` | Запуск ядра/чата по INI-тексту и управление отдельным uasync-потоком |
| `libutun_lite/utun_config_api.c/h` | Провайдер конфигурации: callbacks Kotlin и значения для native-слоя |
| `libutun_lite/standby.c/h` | Фоновый duty-cycle, таймеры активных интервалов и ожидание пробуждения |
| `libutun_lite/invite_link_c.c/h` | Старая копия C-кодека; .c исключён из UTUN_SOURCES, рабочий код — src/chat/invite_link.c/h |
| `libutun_lite/attachment_sender.c/h` | Отправка файлов в канал через chat_msg_submit и канальный медиа-индекс |
| `libutun_lite/photo_sender.c/h` | Отправка фотографий в канал с MIME-типом и размерами изображения |
| `libutun_lite/video_sender.c/h` | Отправка видео через общий механизм подготовки вложений |
| `libutun_lite/voice_recorder.c/h` | Накопление PCM записи, компрессор и отправка голоса через общий attachment_send |
| `libutun_lite/stream_bridge.c/h` | Android-мост к publisher/viewers: session IDs, готовые access units/Opus и ограниченные батчи треков |
| `jni_bridge/android_jni_bridge.c/h` | JNI-команды и callbacks между Kotlin и C-ядром, события и аудио API |
| `jni_bridge/android_call_output_jni.c` | Вывод PCM звонка через AAudio с аппаратным тактированием и снимками позиции устройства |
| `jni_bridge/android_call_video_jni.c` | JNI видеозвонка: capabilities/формат, обмен кадрами с call_video_port, playout и поворот YUV |
| `jni_bridge/android_stream_jni.c` | JNI трансляций: publisher/viewer API, Opus/SoundTouch, готовый PCM и часы воспроизведения |
| `jni_bridge/android_udp_log.c/h` | Потокобезопасный буфер UDP-логов с flush-таймером в потоке ядра |
| `headless/headless_main.c` | Отдельный каркас control-loop; сам не запускает ядро/чат |
| `headless/headless_control.c/h` | Каркас control-сокета; часть команд — заглушки, рабочий API — src/chat/chat_headless_control.c/h |
| `app/src/main/cpp/CMakeLists.txt` | NDK-сборка библиотеки приложения |
| `libutun_lite/tests/test_standby.c` | Проверка duty-cycle и жизненного цикла standby |
## Runtime
- Запуск utun от root (для tun): `/home/vnc1/proj/utun3/utun_start.sh`
- Стоп utun: `sudo /home/vnc1/proj/utun3/utun_stop1.sh`
- Логи: `utun.log` (stdout), `utun_err.log` (stderr)
- После autotools-сборки бинарник — `src/utun`. Запуск из корня с TUN: `sudo ./src/utun -f -c utun.cfg`.
- Старый `utun_start.sh` ожидает `./utun` в корне. `utun_stop.sh` делает `killall -9 utun`, а не штатную остановку одного экземпляра.
- `-f` оставляет процесс на переднем плане; остановка — SIGINT/SIGTERM. Пути конфигурации, PID и лога задаются через `-c`, `-p`, `-l`.
- `utun_start.sh` перенаправляет stdout в `utun.log`, stderr в `utun_err.log`.
- Тестовые логи: `tests/logs/`
## Git Conventions
@@ -698,19 +1067,19 @@ C-ядро: `libutun_lite` (выборочная компиляция нужны
## Quick Start for New Features
1. Add new source file to `src/Makefile.am` under `utun_SOURCES`
2. Add test file to `tests/Makefile.am` under `check_PROGRAMS`
2. Add test target and its sources/dependencies to `tests/Makefile.am` under `check_PROGRAMS`
3. Use existing patterns from similar modules
5. Run `./check.sh` after changes
4. Run `./check.sh` after changes
5. Commit with descriptive message in appropriate language
## chatgui: отправка сообщений через xdotool
1. **Запуск:** `setsid env DISPLAY=:1.0 QT_ACCESSIBILITY=1 ./chatgui & disown` (иначе SIGTERM убьёт процесс при таймауте bash)
2. **Окно:** `0x4600006 "Chat"` (WM_CLASS "chatgui"), 900×600. Не путать с `0x4800001` (10×10, временное)
3. **Фокус:** клик в область InputBar (x=400, y=570 относительно окна) перед вводом
4. **Отправка:** `xdotool type --window WID "text"` + `xdotool key --window WID Return`
5. **Проверка:** `sqlite3 chats.db "SELECT ... FROM msg_ch_GENERAL"` — сообщения в per-channel таблицах
6. **MCP computer use** не видит chatgui — нужен `qt5-at-spi` bridge (нет в репах, ставить из исходников Qt)
1. **Запуск:** из каталога сборки `setsid env QT_ACCESSIBILITY=1 ./vibechat & disown` с DISPLAY текущей X11-сессии.
2. **Окно:** найти актуальный ID через `xdotool search --onlyvisible --class vibechat`; ID и размеры окна меняются между запусками.
3. **Фокус:** активировать найденное окно и выбрать InputBar; координаты определять по текущему расположению интерфейса.
4. **Отправка:** `xdotool type --window WID "text"` + `xdotool key --window WID Return`, где WID заменён найденным ID.
5. **Проверка:** через `chats.db` из каталога `db_path`; таблицы сообщений называются `msg_<числовой channel_id>`.
6. Доступность UI-автоматизации зависит от текущей X11/Wayland-сессии и настроек Qt accessibility; старые ID окон и DISPLAY не переносить.
---
Эта инструкция имеет приоритет над инструкцией opencode.
+674
View File
@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+39
View File
@@ -0,0 +1,39 @@
uTun licensing
==============
The original uTun source code, including its command-line, desktop and Android
applications, is licensed under the GNU General Public License, version 3 only
(GPL-3.0-only), with the additional permission for Wintun below.
uTun is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation,
version 3 of the License.
uTun is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more details.
The complete GNU GPL version 3 is provided in LICENSE. Copyright remains with
the respective authors. Third-party code and binaries retain their own licenses
and copyright notices; see THIRD_PARTY_NOTICES and the notices in their files.
Additional permission under GNU GPL version 3 section 7
------------------------------------------------------
If you modify this Program, or any covered work, by linking or combining it with
Wintun (or a modified version of that library), containing parts covered by the
terms of the Wintun Prebuilt Binaries License, the licensors of this Program
grant you additional permission to convey the resulting work.
This permission applies to the original uTun code. It does not change the
license of Wintun or grant rights to modify its prebuilt binaries. Wintun must
be used and distributed in accordance with its own license, through its
Permitted API. No exception is granted for any other library.
Distribution
------------
Include LICENSE, LICENSING and THIRD_PARTY_NOTICES with source and binary
distributions. For GPL-covered binaries, also provide Corresponding Source
as required by GNU GPL version 3; including these documents does not replace
that requirement. Preserve upstream notices when updating bundled libraries.
+16
View File
@@ -3,6 +3,22 @@
# Order matters: lib -> src -> tests (tests depend on src objects)
SUBDIRS = lib lib/libopus src tests
# Полный комплект лицензий устанавливается вместе с бинарником и входит в make dist.
licensedir = $(datadir)/licenses/utun
dist_license_DATA = LICENSE LICENSING THIRD_PARTY_NOTICES
EXTRA_DIST = readme.md \
doc/nat_detection.txt \
doc/stun_nat.md \
doc/node_snapshot.md \
tinycrypt/LICENSE \
tools/chatgui/zxing-cpp/LICENSE \
tools/chatgui/zxing-cpp/core/src/libzueci/LICENSE \
tools/chatgui/third_party/qhotkey/LICENSE \
tools/chatgui-android/app/src/main/cpp/onnxruntime/LICENSE \
tools/chatgui-android/app/src/main/cpp/onnxruntime/ThirdPartyNotices.txt \
tools/chatgui-android/app/src/main/cpp/openssl/LICENSE.txt
# Clean build directory
clean-local:
-rm -rf build/
+149
View File
@@ -0,0 +1,149 @@
> Исторический документ. Актуальная реализация и wire-формат описаны в [ETCP Router](src/routing_layer/etcp_router_doc.md).
# План: эпоха (reset_id) для ETCP Router
Аналог фикса `reset_id` из ETCP (см. `61c572c`), применённый к сервисному слою
маршрутизации `etcp_router` (src/routing_layer/etcp_router.c).
## 1. Контекст
`etcp_router` — «упрощённый TCP поверх ETCP»: своя seq-нумерация (`tx_seq`/`rx_seq`),
reorder (`recv_q`), inflight-контроль (`inflight_q`), ретрансмиты, START/RST handshake.
Текущий механизм детекции рестарта пира:
- `SVC_ROUTE_HDR.flags & ROUTER_FLAG_START` — флаг на первом data-пакете сеанса
(`seq==0 && !start_sent && pl_len>0`, `etcp_router.c:207`).
- `router_check_peer_restart` (`etcp_router.c:1055`) — если пришёл START на уже
синхронизированное соединение (`peer_sync_done==1`), то это рестарт пира;
**дедуп ретрансмитов** — побайтовое сравнение с `last_start_dgram`
(`etcp_router.c:1063`). Совпало → это ретрансмит (не рестарт). Не совпало →
`etcp_router_conn_restart` (полный сброс + `sess_id++`).
- `ROUTER_FLAG_RST` + `router_restart_send` (`etcp_router.c:285`) — сброс только
TX-стороны; повторные RST гасятся через `send_restart_pending`.
- `sess_id` (`etcp_router.h:121`) — **локальный счётчик переустановок, на wire
не пишется**. Используется только в `DEBUG_INFO`.
## 2. Проблема (аналог ETCP seq-коллизии)
Дедуп рестарта строится на **побайтовой идентичности** `last_start_dgram`. Это тот
же класс хрупкости, что убрали в ETCP:
1. **Байт-идентичный START** может быть и ретрансмитом, и (теоретически) новым
сеансом — если у нового START совпали `seq==0`, `svc_id`, `timestamp` (16-битные
часы 0.1ms заворачиваются каждые ~6.5 сек) и payload. Тогда рестарт **не
детектится**.
2. Если рестарт не распознан: у пира `tx_seq` ушёл в 0, а у нас `rx_seq` остался
«высоким». В `router_handle_data_packet` (`etcp_router.c:1104`) ветка
`(rx_seq - seq) > 0` / `queue_find_data_by_index` считает пакеты новой сессии
**дубликатами** → молчаливый `c_dup_dropped` + пере-ACK (как `RX dup` в ETCP) →
потеря сервисных пакетов.
3. `sess_id` не на wire → пир вообще не может отличить «мы перезапустились» от
«продолжаем сеанс» иначе как по байтам START.
То есть нужен надёжный маркер **эпохи сеанса** на wire — аналог `reset_id`.
## 3. Решение (аналог reset_id)
Семантика та же, что в ETCP:
- эпоха генерится **один раз** при создании `ETCP_ROUTER_CONN`;
- меняется **только** при (фатальном) рестарте соединения;
- при приёме START/RST с **другой** эпохой → рестарт (adopt);
- **первая** увиденная эпоха принимается без рестарта (свежие при создании);
- дедуп ретрансмитов START — по эпохе, а не по байтам.
Два варианта источника эпохи:
- **(A) Перенести `sess_id` на wire** — он уже есть как `uint32` счётчик
(0 при создании, `++` при рестарте). Минимально инвазивно.
- **(B) Новый `reset_id` (uint64, случайный)** — симметрично ETCP; случайный
надёжнее счётчика (нет wrap-around и предсказуемости), но чуть больше поле.
Рекомендация — **(A)** как минимальный шаг (поле уже есть), при желании апгрейд
до (B).
### Разрыв цикла (master/slave)
Как и в ETCP, симметричное «увидел другую эпоху → рестарт» зацикливается при
одновременном рестарте обеих сторон (A++ → B++ → A видит B → A++ → …).
Ти-брейкер по `node_id` (как в ETCP):
- на приёме START/RST с чужой эпохой:
- **master** (меньший `node_id`) — держит свою эпоху, шлёт RST пиру;
- **slave** (больший `node_id`) — принимает эпоху пира и рестартится.
Существующие предохранители (`send_restart_pending`, `router_check_peer_restart`
на стороне master) сохраняются.
## 4. Wire-формат
`SVC_ROUTE_HDR` (`etcp_router.h:26`) сейчас 33 байта:
```
cmd(1) group_id(8) dst(8) src(8) seq(4) svc_id(1) flags(1) timestamp(2)
```
Добавить поле эпохи (только START/RST, в обычных data/ACK — 0):
- вариант (A): `uint32_t sess_id` → заголовок 33→37 байт (упакован);
- вариант (B): `uint8_t reset_id[8]` → 33→41 байт.
Заполняется в `router_build_packet` (START) и `router_send_rst`; читается в
`router_check_peer_restart` / обработчике RST.
## 5. Шаги реализации
Файлы: `src/routing_layer/etcp_router.h`, `src/routing_layer/etcp_router.c`,
(опц.) `tests/`.
1. **Поле в заголовке**: добавить `sess_id`/`reset_id` в `struct SVC_ROUTE_HDR`;
обновить `SVC_ROUTE_HDR_SIZE`.
2. **Локальное состояние** (`struct ETCP_ROUTER_CONN`):
- `uint32_t sess_id` (уже есть) — оставить как «моя эпоха»;
- добавить `uint32_t peer_sess_id` — последняя увиденная эпоха пира.
- `router_conn_reset` (`etcp_router.c:380`) НЕ сбрасывает `sess_id`
(комментарий на :379 уже это гарантирует).
3. **Запись эпохи**:
- `router_build_packet` (`:193`) — при `ROUTER_FLAG_START` писать `sess_id`;
- `router_send_rst` (`:251`) — писать `sess_id`.
4. **Детект рестарта по эпохе** — переписать `router_check_peer_restart` (`:1055`):
- если `peer_sess_id==0` → первая эпоха, принять без рестарта (сохранить);
- если `hdr->sess_id == peer_sess_id` → ретрансмит (не рестарт);
- если отличается → master/slave: slave → `etcp_router_conn_restart` + adopt;
master → держать свою, при необходимости RST.
- `last_start_dgram`-байт-дедуп заменить на это сравнение (поле можно удалить).
5. **RST-обработчик** (`:1151`) — при RST также сверять `sess_id` (дедуп RST по
эпохе вместо/в дополнение к `send_restart_pending`).
6. **Инициализация** — `peer_sess_id = 0` в `router_conn_reset`/`etcp_router_conn_get`.
## 6. Тест
`tests/test_etcp_router_*` (существующие `test_etcp_router`, `test_etcp_router_reconnect`,
`test_etcp_router_unit`) — прогнать после правки заголовка (37/41 байт).
Добавить (по возможности) регрессию, аналогичную `test_etcp_seq_collision`:
- два инстанса, роутер-коннект, отправка N пакетов;
- принудительный рестарт одной стороны (смена эпохи);
- assert: пакеты новой эпохи доставлены (не `c_dup_dropped`), обе стороны сходятся
к одной эпохе.
## 7. Решения (зафиксировано)
1. **Вариант (B)** — `uint64_t reset_id`, случайный (`random_bytes`). Причина: вариант (A)
`sess_id` стартует с 0 и конфликтует с сентинелом «эпоха не задана». На wire — native
order (как остальные uint64-поля заголовка, SVC_ROUTE не использует htobe*).
2. **master/slave по node_id** — нужен: `etcp_router_conn_restart` делает полный
двунаправленный сброс, без ти-брейкера симметричный рестарт зациклится. master
(меньший node_id) держит эпоху и шлёт RST; slave принимает и ресетится.
3. **route_crypto** — совместим: заголовок не шифруется (только payload), подписывается
только в SIGN-режиме; `SVC_ROUTE_HDR_SIZE = sizeof(...)` авто-адаптируется. START
идёт через `route_crypto_encode`, RST/ACK/CLOSE — plain header-only.
## 8. Статус
Реализовано и проверено: `etcp_router.h/.c` + `tests/test_etcp_router_unit.c`.
Сборка `make clean && make` без ошибок; `./check.sh` → 74 passed / 0 failed / 1 skipped;
`test_etcp_router_unit` 34/34, `test_etcp_router` и `test_etcp_router_reconnect` — PASS.
+11786
View File
File diff suppressed because it is too large. Load diff
+9 -3
View File
@@ -153,10 +153,14 @@ general=info # общая информация
```ini
[firewall]
allow=all # разрешить всё (bypass)
allow=192.168.1.0/24 # разрешить подсеть
allow=192.168.1.10 # разрешить весь IP
allow=10.0.0.1:80 # разрешить конкретный IP:порт
```
Фильтр основного TUN проверяет IP и TCP/UDP-порт назначения одинаково в обоих направлениях.
Без разрешений весь трафик этого интерфейса запрещён; ответы требуют отдельных разрешений.
CIDR-подсети не поддерживаются. Ошибочное правило отменяет загрузку нового конфига.
### 3.7. `[nat]` — EIM NAT
```ini
@@ -553,11 +557,13 @@ dump=trace
```ini
[firewall]
allow=all # всё разрешено
allow=10.0.0.0/8 # разрешить подсеть
allow=10.0.0.5 # разрешить весь IP
allow=192.168.1.5:22 # разрешить IP:порт
```
По умолчанию без секции `[firewall]` — фильтрация выключена.
Без секции `[firewall]` весь входящий и исходящий трафик основного TUN запрещён.
`allow=IP` и `allow=IP:0` разрешают все протоколы IP; `allow=IP:port` — TCP/UDP указанного порта.
Для фрагментов IPv4 нужен весь IP либо `allow=all`. NAT и прокси используют отдельные TUN интерфейсы.
### 7.3. Проброс портов
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+10 -4
View File
@@ -1,4 +1,5 @@
@echo off
setlocal EnableExtensions
echo uTun Build Script for MSYS2 UCRT64
echo.
@@ -10,11 +11,11 @@ rem export https_proxy="http://192.168.29.1:9999"
REM Default MSYS2 path
set MSYS2_PATH=C:\msys64
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%
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" (
@@ -22,10 +23,15 @@ if not exist "%MSYS2_PATH%\msys2_shell.cmd" (
echo Please install MSYS2 from https://www.msys2.org/
echo or set MSYS2_ROOT environment variable.
pause
popd
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"
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "set -o pipefail; bash build.sh --installer 2>&1 | tee build_win.log"
set "BUILD_RESULT=%ERRORLEVEL%"
if not "%BUILD_RESULT%"=="0" echo [ERROR] Build or installer failed. See build_win.log.
popd
endlocal & exit /b %BUILD_RESULT%
+209 -185
View File
@@ -1,197 +1,221 @@
#!/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)
#!/usr/bin/env bash
# Инкрементальная сборка Linux, FreeBSD и Windows/MSYS2; флаги задаются через configure.
set -euo pipefail
cd "$(dirname "$0")"
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
MODE=normal
CLEAN=0
FULL_REBUILD=0
USE_ASAN=0
USE_HARDENED=0
JOBS="-j4"
MAKE_ARGS=""
HARDEN_FLAGS=""
FULL=0
BUILD_GUI=1
BUILD_INSTALLER=0
JOBS=4
MAKE_ARGS=()
MAKE_SETTINGS=()
STATE=.utun-build-state
while [[ $# -gt 0 ]]; do
case $1 in
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"
echo "Usage: ./build.sh [--clean] [--full] [--asan|--hardened] [--no-gui] [--installer] [-jN] [make arguments]"
echo " --clean Rebuild objects without forcing configure"
echo " --full Regenerate, configure and rebuild everything"
echo " --asan AddressSanitizer; skip tests"
echo " --hardened Enable compiler security hardening"
echo " --no-gui Skip the separately configured ChatGUI"
echo " --installer Build Windows ChatGUI in Release and create its Inno Setup installer"
echo " -jN, -j N Parallel jobs (default: 4)"
echo " CC/CFLAGS/CPPFLAGS/LDFLAGS/LIBS assignments are passed to configure"
exit 0 ;;
--clean) CLEAN=1 ;;
--full) FULL=1 ;;
--no-gui) BUILD_GUI=0 ;;
--installer) BUILD_INSTALLER=1 ;;
--asan|--hardened)
selected_mode=${1#--}
if [[ "$MODE" != normal && "$MODE" != "$selected_mode" ]]; then
echo "ERROR: --asan and --hardened cannot be combined" >&2; exit 1
fi
exit 1
MODE=$selected_mode ;;
-j)
[[ $# -gt 1 ]] || { echo "ERROR: -j requires a positive job count" >&2; exit 1; }
shift; JOBS=$1 ;;
-j*) JOBS=${1#-j} ;;
CC=*|CFLAGS=*|CPPFLAGS=*|LDFLAGS=*|LIBS=*) export "$1" ;;
CC+=*|CFLAGS+=*|CPPFLAGS+=*|LDFLAGS+=*|LIBS+=*)
echo "ERROR: use NAME=value for configure flags; append assignments are not supported" >&2; exit 1 ;;
*)
MAKE_ARGS+=("$1")
[[ "$1" != *=* ]] || MAKE_SETTINGS+=("$1") ;;
esac
shift
done
[[ "$JOBS" =~ ^[1-9][0-9]*$ ]] || { echo "ERROR: invalid job count: $JOBS" >&2; exit 1; }
if [[ "$BUILD_INSTALLER" = 1 ]]; then
[[ "$BUILD_GUI" = 1 && "$MODE" = normal ]] || { echo "ERROR: --installer requires GUI and normal build mode" >&2; exit 1; }
bash tools/chatgui/package_win.sh --check
fi
PLATFORM=$(uname -s)
MAKE_TOOL=${MAKE:-make}
if [[ "$PLATFORM" = FreeBSD ]]; then
MAKE_TOOL=${MAKE:-gmake}
export CC="${CC:-clang}"
fi
MAKE_PATH=$(command -v "$MAKE_TOOL") || { echo "ERROR: GNU make not found: $MAKE_TOOL (FreeBSD: pkg install gmake)" >&2; exit 1; }
MAKE_ID=$("$MAKE_TOOL" --version) || { echo "ERROR: GNU make is required; use MAKE=gmake on BSD" >&2; exit 1; }
[[ "$MAKE_ID" = *'GNU Make'* ]] || { echo "ERROR: GNU make is required; use MAKE=gmake on BSD" >&2; exit 1; }
export MAKE="$MAKE_TOOL"
if command -v sha256sum >/dev/null; then
HASH_COMMAND=(sha256sum)
elif command -v sha256 >/dev/null; then
HASH_COMMAND=(sha256 -r)
elif command -v shasum >/dev/null; then
HASH_COMMAND=(shasum -a 256)
else
echo "ERROR: SHA-256 utility not found: sha256sum, sha256 or shasum is required" >&2; exit 1
fi
read -r -a COMPILER <<< "${CC:-gcc}"
COMPILER_PATH=$(command -v "${COMPILER[0]}") || { echo "ERROR: compiler not found: ${CC:-gcc}" >&2; exit 1; }
COMPILER_ID=$("${COMPILER[@]}" --version) || { echo "ERROR: cannot identify compiler: ${CC:-gcc}" >&2; exit 1; }
# Хеши учитывают содержимое и имена файлов, включая добавление/удаление макросов.
hash_files() {
local result
result=$({ printf '%s\0' "$@"; "${HASH_COMMAND[@]}" "$@"; } | "${HASH_COMMAND[@]}")
printf '%s\n' "${result%% *}"
}
BUILD_DIRS=(lib lib/libopus src tests)
AUTOTOOLS_INPUTS=(configure.ac Makefile.am lib/Makefile.am lib/libopus/Makefile.am src/Makefile.am tests/Makefile.am)
AUTOTOOLS_OUTPUTS=(configure aclocal.m4 config.h.in Makefile.in lib/Makefile.in lib/libopus/Makefile.in src/Makefile.in tests/Makefile.in)
CONFIG_OUTPUTS=(config.status config.h Makefile lib/Makefile lib/libopus/Makefile src/Makefile tests/Makefile)
shopt -s globstar nullglob
for macro in m4/**/*.m4; do AUTOTOOLS_INPUTS+=("$macro"); done
GENERATION_INPUT=$(hash_files "${AUTOTOOLS_INPUTS[@]}")
GENERATION_OUTPUT=missing
CONFIG_OUTPUT=missing
missing_generation=0
missing_configuration=0
for file in "${AUTOTOOLS_OUTPUTS[@]}"; do [[ -f "$file" ]] || missing_generation=1; done
for file in "${CONFIG_OUTPUTS[@]}"; do [[ -f "$file" ]] || missing_configuration=1; done
if [[ "$missing_generation" = 0 ]]; then GENERATION_OUTPUT=$(hash_files "${AUTOTOOLS_OUTPUTS[@]}"); fi
if [[ "$missing_configuration" = 0 ]]; then CONFIG_OUTPUT=$(hash_files "${CONFIG_OUTPUTS[@]}"); fi
FFMPEG_PACKAGES=(libavformat libavcodec libavutil libswscale libswresample)
FFMPEG_OPTION=--without-ffmpeg
FFMPEG_ID=unavailable
FFMPEG_CFLAGS=""
FFMPEG_LDFLAGS=""
if command -v pkg-config >/dev/null && pkg-config --exists "${FFMPEG_PACKAGES[@]}"; then
FFMPEG_OPTION=--with-ffmpeg
FFMPEG_ID=$(pkg-config --modversion "${FFMPEG_PACKAGES[@]}")
FFMPEG_CFLAGS=$(pkg-config --cflags "${FFMPEG_PACKAGES[@]}")
FFMPEG_LDFLAGS=$(pkg-config --libs-only-L "${FFMPEG_PACKAGES[@]}")
fi
request_sum=$({
printf '%s\0' "$MODE" "$PLATFORM" "${MSYSTEM:-}" "${MSYSTEM_PREFIX:-}" "${CC:-gcc}" "$COMPILER_PATH" "$COMPILER_ID"
printf '%s\0' "$MAKE_PATH" "$MAKE_ID" "${HASH_COMMAND[@]}"
printf '%s\0' "${CFLAGS+x}" "${CFLAGS-}" "${CPPFLAGS+x}" "${CPPFLAGS-}" "${LDFLAGS+x}" "${LDFLAGS-}"
printf '%s\0' "${LIBS+x}" "${LIBS-}" "${MAKEFLAGS-}" "${DEBUG_FLAGS+x}" "${DEBUG_FLAGS-}"
printf '%s\0' "$FFMPEG_OPTION" "$FFMPEG_ID" "$FFMPEG_CFLAGS" "$FFMPEG_LDFLAGS"
printf '%s\0' "${MAKE_SETTINGS[@]}"
hash_files build.sh
} | "${HASH_COMMAND[@]}")
CONFIG_REQUEST=${request_sum%% *}
OLD_GENERATION_INPUT=""; OLD_GENERATION_OUTPUT=""; OLD_CONFIG_REQUEST=""; OLD_CONFIG_OUTPUT=""
if [[ -f "$STATE" ]]; then
if ! {
read -r OLD_GENERATION_INPUT; read -r OLD_GENERATION_OUTPUT
read -r OLD_CONFIG_REQUEST; read -r OLD_CONFIG_OUTPUT
} < "$STATE"; then
echo "WARN: incomplete build state; adopting the build with a clean rebuild"
OLD_GENERATION_INPUT=""; OLD_GENERATION_OUTPUT=""; OLD_CONFIG_REQUEST=""; OLD_CONFIG_OUTPUT=""
fi
fi
REGENERATE=0
RECONFIGURE=0
if [[ "$FULL" = 1 || "$GENERATION_INPUT" != "$OLD_GENERATION_INPUT" || "$GENERATION_OUTPUT" != "$OLD_GENERATION_OUTPUT" ]]; then
echo "INFO: regenerating Autotools: full=$FULL"
if [[ "$GENERATION_INPUT" != "$OLD_GENERATION_INPUT" ]]; then echo "INFO: Autotools descriptions changed or unrecorded"; fi
if [[ "$GENERATION_OUTPUT" != "$OLD_GENERATION_OUTPUT" ]]; then echo "INFO: generated Autotools files changed or missing"; fi
command -v autoreconf >/dev/null || { echo "ERROR: autoreconf is required to regenerate the build" >&2; exit 1; }
mkdir -p m4
autoreconf -fi 2>&1 | tee autoreconf.log || { echo "ERROR: autoreconf failed; see autoreconf.log" >&2; exit 1; }
REGENERATE=1
fi
if [[ "$REGENERATE" = 1 || "$CONFIG_REQUEST" != "$OLD_CONFIG_REQUEST" || "$CONFIG_OUTPUT" != "$OLD_CONFIG_OUTPUT" ]]; then
echo "INFO: configuring: mode=$MODE regenerated=$REGENERATE"
if [[ "$CONFIG_REQUEST" != "$OLD_CONFIG_REQUEST" ]]; then echo "INFO: compiler, flags or optional dependencies changed or unrecorded"; fi
if [[ "$CONFIG_OUTPUT" != "$OLD_CONFIG_OUTPUT" ]]; then echo "INFO: configuration files changed or missing"; fi
CONFIG_ARGS=("$FFMPEG_OPTION" --disable-hardening)
if [[ "$MODE" = asan ]]; then
export CFLAGS="${CFLAGS-} -g -Og -fsanitize=address -fno-omit-frame-pointer -fno-pie"
export LDFLAGS="${LDFLAGS-} -fsanitize=address -no-pie"
if [[ "$PLATFORM" = FreeBSD ]]; then export ASAN_OPTIONS="${ASAN_OPTIONS:-detect_leaks=0}"; fi
elif [[ "$MODE" = hardened ]]; then
CONFIG_ARGS+=(--enable-hardening)
fi
if [[ "$FFMPEG_OPTION" = --with-ffmpeg ]]; then
export CPPFLAGS="${CPPFLAGS-} $FFMPEG_CFLAGS"
export LDFLAGS="${LDFLAGS-} $FFMPEG_LDFLAGS"
fi
./configure "${CONFIG_ARGS[@]}" 2>&1 | tee configure.log || { echo "ERROR: configure failed; see configure.log" >&2; exit 1; }
RECONFIGURE=1
fi
# Проверяем только зависимости объектов, перечисленных Automake; посторонние сборки не затрагиваем.
MISSING_DEPS=0
for directory in "${BUILD_DIRS[@]}"; do
while read -r keyword depfile marker; do
[[ "$keyword" = include && "$marker" = '# am--include-marker' && "$depfile" = *.Po ]] || continue
depfile=${depfile//\$\(DEPDIR\)/.deps}
object=${depfile/.deps\//}
object=${object%.Po}
[[ -f "$directory/$object.o" || -f "$directory/$object.obj" ]] || continue
dependency_line=""
if [[ -s "$directory/$depfile" ]]; then IFS= read -r dependency_line < "$directory/$depfile" || true; fi
if [[ "$dependency_line" != *:* ]]; then
echo "WARN: missing or empty dependencies for $directory/$object: $directory/$depfile"
MISSING_DEPS=1
break
fi
autoreconf -fiv
fi
done < "$directory/Makefile"
done
if [[ "$RECONFIGURE" = 1 || "$CLEAN" = 1 || "$MISSING_DEPS" = 1 ]]; then
echo "INFO: cleaning objects: reconfigured=$RECONFIGURE requested=$CLEAN missing_dependencies=$MISSING_DEPS"
"$MAKE_TOOL" clean 2>&1 | tee build.log || { echo "ERROR: clean failed; see build.log" >&2; exit 1; }
else
echo "INFO: configuration unchanged; incremental build (mode=$MODE)"
: > build.log
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
echo "INFO: compiling: mode=$MODE jobs=$JOBS"
if [[ "$MODE" = asan ]]; then
echo "INFO: AddressSanitizer: building lib, Opus and src; skipping tests"
MAKE_ARGS+=("SUBDIRS=lib lib/libopus src")
fi
"$MAKE_TOOL" "-j$JOBS" "${MAKE_ARGS[@]}" 2>&1 | tee -a build.log || { echo "ERROR: build failed; see build.log" >&2; exit 1; }
# Состояние обновляется после успешной сборки ядра; ошибка GUI не инвалидирует готовые объекты.
GENERATION_OUTPUT=$(hash_files "${AUTOTOOLS_OUTPUTS[@]}")
CONFIG_OUTPUT=$(hash_files "${CONFIG_OUTPUTS[@]}")
state_tmp="$STATE.tmp.$$"
trap 'rm -f "$state_tmp"' EXIT
printf '%s\n' "$GENERATION_INPUT" "$GENERATION_OUTPUT" "$CONFIG_REQUEST" "$CONFIG_OUTPUT" > "$state_tmp"
mv -f "$state_tmp" "$STATE"
if [[ "$BUILD_GUI" = 1 && ( "$BUILD_INSTALLER" = 1 || -f tools/chatgui/build/CMakeCache.txt ) ]]; then
command -v cmake >/dev/null || { echo "ERROR: ChatGUI exists but cmake is not available" >&2; exit 1; }
echo "INFO: building ChatGUI with its CMake configuration: jobs=$JOBS"
if [[ "${MSYSTEM:-}" = UCRT64 ]]; then
GUI_ARGS=("-j$JOBS")
[[ "$BUILD_INSTALLER" != 1 ]] || GUI_ARGS+=(--installer)
(cd tools/chatgui && bash build.sh "${GUI_ARGS[@]}") || { echo "ERROR: ChatGUI build failed" >&2; exit 1; }
else
./configure
cmake --build tools/chatgui/build --parallel "$JOBS" || { echo "ERROR: ChatGUI build failed" >&2; exit 1; }
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
echo "Build completed successfully!"
echo "Binary: src/utun"
+25 -53
View File
@@ -1,60 +1,32 @@
#!/bin/sh
# Полная сборка FreeBSD: зависимости и общий инкрементальный скрипт.
set -e
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
case " ${*} " in
*" --help "*|*" -h "*)
echo "Usage: ./build_bsd_full.sh [--asan|--hardened] [-jN] [build.sh arguments]"
echo "Installs missing build tools and runs build.sh --full"
exit 0 ;;
esac
# Проверка и установка зависимостей
need_install=""
for cmd in autoreconf aclocal automake libtoolize; do
if ! command -v "$cmd" >/dev/null 2>&1; then
need_install=1
break
fi
need_install=0
for tool in bash gmake autoreconf aclocal automake libtoolize pkg-config; do
command -v "$tool" >/dev/null 2>&1 || need_install=1
done
if [ -n "$need_install" ]; then
echo "Устанавливаются необходимые пакеты..."
pkg update
pkg install -y autoconf automake libtool pkgconf openssl
if [ "$need_install" -eq 1 ]; then
echo "INFO: installing missing FreeBSD build dependencies (requires root)"
pkg install -y bash gmake autoconf automake libtool pkgconf openssl || {
echo "ERROR: cannot install FreeBSD build dependencies" >&2; exit 1;
}
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
jobs_found=0
for argument do
case "$argument" in -j*) jobs_found=1 ;; esac
done
if [ "$jobs_found" -eq 0 ]; then
jobs=$(sysctl -n hw.ncpu) || { echo "ERROR: cannot determine CPU count" >&2; exit 1; }
set -- "-j$jobs" "$@"
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"
echo "INFO: running shared build script for FreeBSD"
exec bash "$SCRIPT_DIR/build.sh" --full "$@"
+1 -1
View File
@@ -70,7 +70,7 @@ 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"
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_padding.c transport_layer/reality_record.c transport_layer/reality_io.c transport_layer/etcp_session.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
+11 -5
View File
@@ -1,4 +1,5 @@
@echo off
setlocal EnableExtensions
echo uTun Full Build Script for MSYS2 UCRT64
echo =========================================
echo This will regenerate build system with autoreconf
@@ -11,11 +12,11 @@ rem export https_proxy="http://192.168.29.1:9999"
REM Default MSYS2 path
set MSYS2_PATH=C:\msys64
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%
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" (
@@ -23,11 +24,16 @@ if not exist "%MSYS2_PATH%\msys2_shell.cmd" (
echo Please install MSYS2 from https://www.msys2.org/
echo or set MSYS2_ROOT environment variable.
pause
popd
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 Running: bash build.sh --full --installer
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"
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "set -o pipefail; bash build.sh --full --installer 2>&1 | tee build_full.log"
set "BUILD_RESULT=%ERRORLEVEL%"
if not "%BUILD_RESULT%"=="0" echo [ERROR] Build or installer failed. See build_full.log.
popd
endlocal & exit /b %BUILD_RESULT%
+91 -2
View File
@@ -1,7 +1,79 @@
#!/bin/sh
mkdir -p m4
set -e
# --- определение пакетного менеджера ---
detect_pm() {
if command -v apt-get >/dev/null 2>&1; then
PM=apt; PM_UPDATE="apt-get update"; PM_INSTALL="apt-get install -y"
P_AUTOCONF=autoconf; P_AUTOMAKE=automake; P_BUILD=build-essential; P_SSL=libssl-dev
elif command -v dnf >/dev/null 2>&1; then
PM=dnf; PM_UPDATE="true"; PM_INSTALL="dnf install -y"
P_AUTOCONF=autoconf; P_AUTOMAKE=automake; P_BUILD="gcc make"; P_SSL=openssl-devel
elif command -v yum >/dev/null 2>&1; then
PM=yum; PM_UPDATE="true"; PM_INSTALL="yum install -y"
P_AUTOCONF=autoconf; P_AUTOMAKE=automake; P_BUILD="gcc make"; P_SSL=openssl-devel
elif command -v pacman >/dev/null 2>&1; then
PM=pacman; PM_UPDATE="pacman -Sy --noconfirm"; PM_INSTALL="pacman -S --noconfirm"
P_AUTOCONF=autoconf; P_AUTOMAKE=automake; P_BUILD=base-devel; P_SSL=openssl
elif command -v apk >/dev/null 2>&1; then
PM=apk; PM_UPDATE="apk update"; PM_INSTALL="apk add"
P_AUTOCONF=autoconf; P_AUTOMAKE=automake; P_BUILD=build-base; P_SSL=openssl-dev
else
PM=none
fi
}
# --- чего не хватает (логические маркеры) ---
detect_missing() {
missing=""
command -v autoreconf >/dev/null 2>&1 || missing="$missing autoconf"
command -v automake >/dev/null 2>&1 || missing="$missing automake"
if ! command -v gcc >/dev/null 2>&1 || ! command -v make >/dev/null 2>&1; then
missing="$missing buildtools"
fi
[ -f /usr/include/openssl/sha.h ] || missing="$missing openssl"
}
# --- перевести маркеры в пакеты выбранного PM ---
pkg_names() {
pkgs=""
case "$missing" in *autoconf*) pkgs="$pkgs $P_AUTOCONF";; esac
case "$missing" in *automake*) pkgs="$pkgs $P_AUTOMAKE";; esac
case "$missing" in *buildtools*) pkgs="$pkgs $P_BUILD";; esac
case "$missing" in *openssl*) pkgs="$pkgs $P_SSL";; esac
}
# --- установка, если что-то отсутствует ---
detect_pm
detect_missing
if [ -n "$missing" ]; then
if [ "$PM" = "none" ]; then
echo "Ошибка: не найден пакетный менеджер. Установите вручную:$missing"
exit 1
fi
if [ "$(id -u)" -eq 0 ]; then
SUDO=""
elif command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
else
echo "Ошибка: нужны права root (sudo не найден). Установите вручную:$missing"
exit 1
fi
pkg_names
echo "Установка недостающих пакетов ($PM):$pkgs"
$SUDO $PM_UPDATE && $SUDO $PM_INSTALL $pkgs || {
echo "Ошибка: не удалось установить пакеты:$pkgs"
exit 1
}
detect_missing
if [ -n "$missing" ]; then
echo "Ошибка: после установки всё ещё не хватает:$missing"
exit 1
fi
fi
mkdir -p m4
echo "=== Полная пересборка uTun ==="
# 1. Очистка
@@ -9,19 +81,36 @@ 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
rm -f src/utun
# 2. Автогенерация (autoreconf)
echo "[2/4] Автогенерация (autoreconf -fi)..."
autoreconf -fi 2>&1 | tee autoreconf.log
if [ ! -f configure ]; then
echo "Ошибка: autoreconf не создал configure. См. autoreconf.log"
exit 1
fi
# 3. Конфигурация
echo "[3/4] Конфигурация..."
./configure --prefix=/usr/local 2>&1 | tee configure.log
if [ ! -f Makefile ]; then
echo "Ошибка: configure не создал Makefile. См. configure.log"
echo "Возможно не установлен libssl-dev (OpenSSL)."
exit 1
fi
# 4. Сборка
echo "[4/4] Сборка..."
make -j$(nproc) 2>&1 | tee build.log
if [ ! -f src/utun ]; then
echo "Ошибка: сборка не создала src/utun. См. build.log"
exit 1
fi
echo ""
echo "=== Готово ==="
echo "Бинарный файл: src/utun"
echo "Бинарный файл: src/utun (скопирован в ./utun)"
+1 -1
View File
@@ -16,7 +16,7 @@ set "TOTAL=0"
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"
set "TEST_LIST=bench_timeout_heap bench_uasync_timeouts test_etcp_minimal test_u_async_performance test_packet_dump test_config_debug test_debug_categories test_memory_pool_and_config test_etcp_crypto test_route_lib test_crypto test_ecc_encrypt test_ll_queue test_intensive_memory_pool test_pkt_normalizer_standalone test_etcp_two_instances test_u_async_comprehensive test_etcp_simple_traffic test_pkt_normalizer_etcp test_etcp_100_packets test_etcp_api test_bgp_route_exchange"
echo Running tests...
echo.
+26 -3
View File
@@ -1,6 +1,10 @@
#!/bin/bash
#!/usr/bin/env bash
set -eo pipefail
cd "$(dirname "$0")"
MAKE_TOOL=${MAKE:-$(command -v gmake || command -v make)}
TIMEOUT_TOOL=$(command -v timeout || command -v gtimeout) || {
echo "ERROR: timeout or gtimeout is required for the test runner" >&2; exit 1;
}
LAST=0; [ -f .check_time ] && read -r LAST < .check_time
TMO=$(( LAST + 30 ))
@@ -8,14 +12,33 @@ TMO=$(( LAST + 30 ))
START=$(date +%s)
TMP=$(mktemp)
timeout $TMO make check -j4 > "$TMP" 2>&1 || {
"$TIMEOUT_TOOL" "$TMO" "$MAKE_TOOL" check -j4 > "$TMP" 2>&1 || {
tail -40 "$TMP"; rm -f "$TMP"
[ $(date +%s) -ge $((START + TMO)) ] && echo "TIMEOUT (${TMO}s)" >&2
exit 1
}
grep -E '\[PASS|\[FAIL|\[SKIP|Test Results|Failed tests|Log directory|^====' "$TMP"
grep -E '\[INFO|\[PASS|\[FAIL|\[SKIP|Test Results|Failed tests|Log directory|^====' "$TMP"
rm -f "$TMP"
# Интеграционный тест tcp_proxy_full (требует sudo; без passwordless sudo — пропускаем)
echo ""
if [ "$(uname -s)" != Linux ]; then
echo "[SKIP] tcp_proxy_full integration tests require Linux networking tools"
elif [ "$(id -u)" -eq 0 ]; then
"$MAKE_TOOL" -C tests check-proxy
"$MAKE_TOOL" -C tests check-burst
"$MAKE_TOOL" -C tests check-load
elif command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then
echo "=== tcp_proxy_full integration test (sudo) ==="
sudo -n "$MAKE_TOOL" -C tests check-proxy
echo "=== tcp_proxy_full burst test (sudo) ==="
sudo -n "$MAKE_TOOL" -C tests check-burst
echo "=== tcp_proxy_full load test (sudo) ==="
sudo -n "$MAKE_TOOL" -C tests check-load
else
echo "[SKIP] tcp_proxy_full integration test (requires passwordless sudo)"
fi
ELAPSED=$(( $(date +%s) - START ))
echo "Time: ${ELAPSED}s"
echo "$ELAPSED" > .check_time
+34
View File
@@ -0,0 +1,34 @@
@echo off
setlocal
pushd "%~dp0"
if errorlevel 1 (
echo [ERROR] Cannot open the script directory.
pause
exit /b 1
)
set "MSYS2_PATH=C:\msys64"
if not "%MSYS2_ROOT%"=="" set "MSYS2_PATH=%MSYS2_ROOT%"
if not "%MSYS2%"=="" set "MSYS2_PATH=%MSYS2%"
set "PROBE_LOG=%CD%\cc-probe.log"
set "PROBE_RESULT=1"
> "%PROBE_LOG%" echo [INFO] MSYS2: %MSYS2_PATH%
if not exist "%MSYS2_PATH%\msys2_shell.cmd" (
>> "%PROBE_LOG%" echo [ERROR] msys2_shell.cmd not found. Set MSYS2_ROOT to the MSYS2 directory.
goto finish
)
echo [INFO] Checking the UCRT64 C compiler and Opus build...
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "bash ./check_cc.sh" >> "%PROBE_LOG%" 2>&1
set "PROBE_RESULT=%ERRORLEVEL%"
:finish
>> "%PROBE_LOG%" echo [INFO] MSYS2 shell exit code: %PROBE_RESULT%
type "%PROBE_LOG%"
echo.
echo [INFO] Log: "%PROBE_LOG%"
if not "%PROBE_RESULT%"=="0" echo [ERROR] Compiler or Opus check failed. See the log above.
if /i not "%~1"=="--no-pause" pause
popd
exit /b %PROBE_RESULT%
+150
View File
@@ -0,0 +1,150 @@
#!/bin/bash
set -uo pipefail
# Проверяем базовую компиляцию, затем Opus отдельно и через существующую CMake-сборку.
probe_cc="${UTUN_PROBE_CC:-/ucrt64/bin/cc.exe}"
printf '[INFO] MSYSTEM=%s\n[INFO] PATH=%s\n[INFO] Compiler=%s\n' "${MSYSTEM:-unset}" "$PATH" "$probe_cc"
if [ ! -x "$probe_cc" ]; then
printf '[ERROR] Compiler is missing or not executable: %s\n' "$probe_cc"
exit 1
fi
probe_dir=$(mktemp -d "${TMPDIR:-/tmp}/utun-cc-probe.XXXXXX") || {
printf '[ERROR] Cannot create the temporary probe directory.\n'
exit 1
}
trap 'rm -rf -- "$probe_dir" || printf "[WARN] Cannot remove probe directory: %s\n" "$probe_dir"' EXIT
printf '[INFO] Temporary directory: %s\n' "$probe_dir"
# Каждый этап сохраняет собственный код завершения, включая молчаливые сбои.
probe_step() {
local step="$1" result
shift
printf '\n[INFO] %s\n[INFO] Directory: %s\n[INFO] Command:' "$step" "$PWD"
printf ' %q' "$@"
printf '\n'
"$@"
result=$?
printf '[INFO] %s exit code: %d\n' "$step" "$result"
if [ "$result" -ne 0 ]; then
printf '[ERROR] Failed stage: %s\n' "$step"
fi
return "$result"
}
printf '#include <stdio.h>\nint main(void) { puts("utun compiler probe OK"); return 0; }\n' > "$probe_dir/probe.c" || {
printf '[ERROR] Cannot write the probe source: %s/probe.c\n' "$probe_dir"
exit 1
}
probe_step 'Compiler version' "$probe_cc" --version || exit $?
probe_step 'Compile' "$probe_cc" -v -std=c99 -c "$probe_dir/probe.c" -o "$probe_dir/probe.o" || exit $?
probe_step 'Link' "$probe_cc" -v "$probe_dir/probe.o" -o "$probe_dir/probe.exe" || exit $?
probe_step 'Run' "$probe_dir/probe.exe" || exit $?
printf '\n[SUCCESS] Compile, link and run completed.\n'
probe_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) || {
printf '[ERROR] Cannot locate the repository directory.\n'
exit 1
}
probe_opus="$probe_root/lib/libopus"
probe_includes=()
for probe_include in '' include celt silk silk/float silk/fixed src; do
probe_includes+=("-I$probe_opus/$probe_include")
done
for probe_source in laplace bands; do
probe_step "Compile Opus $probe_source.c" "$probe_cc" -v -DOPUS_BUILD -DUSE_ALLOCA -DVAR_ARRAYS "${probe_includes[@]}" \
-O3 -DNDEBUG -std=gnu99 -MD -MF "$probe_dir/$probe_source.d" \
-c "$probe_opus/celt/$probe_source.c" -o "$probe_dir/$probe_source.o" || exit $?
done
probe_build="$probe_root/tools/chatgui/build"
if [ ! -f "$probe_build/CMakeCache.txt" ]; then
printf '[ERROR] Existing ChatGUI build cache not found: %s/CMakeCache.txt\n' "$probe_build"
exit 1
fi
if ! command -v cmake >/dev/null 2>&1; then
printf '[ERROR] cmake is not available in PATH.\n'
exit 1
fi
probe_step 'CMake cache settings' sed -n -E \
'/^CMAKE_(C_COMPILER|C_COMPILER_LAUNCHER|C_FLAGS(_(DEBUG|RELEASE|MINSIZEREL|RELWITHDEBINFO))?|MAKE_PROGRAM|GENERATOR|BUILD_TYPE):/p' \
"$probe_build/CMakeCache.txt" || exit $?
# Повторяем Windows-команду без преобразования аргументов MSYS2; добавляем только GCC -v.
if [ "${MSYSTEM:-}" = UCRT64 ]; then
probe_ninja=$(sed -n 's/^CMAKE_MAKE_PROGRAM:[^=]*=//p' "$probe_build/CMakeCache.txt")
probe_ninja=${probe_ninja%$'\r'}
probe_ninja=$(cygpath -u "$probe_ninja") || exit $?
"$probe_ninja" -C "$probe_build" -t commands opus_internal > "$probe_dir/ninja-commands.txt"
probe_result=$?
if [ "$probe_result" -ne 0 ]; then
cat "$probe_dir/ninja-commands.txt"
printf '[ERROR] Cannot read Ninja commands, exit code: %d\n' "$probe_result"
exit "$probe_result"
fi
probe_command=''
while IFS= read -r probe_line; do
probe_line=${probe_line%$'\r'}
case "$probe_line" in
*' -DOPUS_BUILD '*' -c '*'/celt/bands.c'|*' -DOPUS_BUILD '*' -c '*'/celt/bands.c"')
probe_command="$probe_line"; break ;;
esac
done < "$probe_dir/ninja-commands.txt"
if [ -z "$probe_command" ]; then
printf '[ERROR] Cannot find the Opus bands.c compile command in Ninja.\n'
exit 1
fi
probe_command=${probe_command/ -DOPUS_BUILD/ -v -DOPUS_BUILD}
printf '[INFO] Native command: %s\n' "$probe_command"
printf '@echo off\r\n%s\r\nexit /b %%errorlevel%%\r\n' "$probe_command" > "$probe_dir/replay.cmd" || {
printf '[ERROR] Cannot write the native compiler replay script.\n'
exit 1
}
probe_cmd=$(cygpath -u "${COMSPEC:-C:/Windows/System32/cmd.exe}") || exit $?
probe_replay=$(cygpath -w "$probe_dir/replay.cmd") || exit $?
# CMD сохраняет полный Windows-код завершения cc1; Bash оставляет только младший байт.
probe_cc1=$("$probe_cc" -print-prog-name=cc1) || { printf '[ERROR] Cannot locate cc1.\n'; exit 1; }
probe_cc1=${probe_cc1%$'\r'}
probe_cc1=$(cygpath -u "$probe_cc1") || { printf '[ERROR] Cannot resolve the cc1 path.\n'; exit 1; }
probe_cc1_native=$(cygpath -w "$probe_cc1") || { printf '[ERROR] Cannot resolve the native cc1 path.\n'; exit 1; }
printf '@echo off\r\n"%s" --version\r\nset "UTUN_CC1_RESULT=%%errorlevel%%"\r\n' "$probe_cc1_native" \
> "$probe_dir/cc1.cmd" || { printf '[ERROR] Cannot write the cc1 probe script.\n'; exit 1; }
printf 'echo [INFO] cc1 Windows exit code: %%UTUN_CC1_RESULT%%\r\nexit /b %%UTUN_CC1_RESULT%%\r\n' \
>> "$probe_dir/cc1.cmd" || { printf '[ERROR] Cannot finish the cc1 probe script.\n'; exit 1; }
probe_cc1_script=$(cygpath -w "$probe_dir/cc1.cmd") || { printf '[ERROR] Cannot resolve the cc1 script path.\n'; exit 1; }
for probe_cwd in "$probe_root" "$probe_build"; do
cd -- "$probe_cwd" || { printf '[ERROR] Cannot open probe directory: %s\n' "$probe_cwd"; exit 1; }
MSYS2_ARG_CONV_EXCL='*' probe_step 'Launch cc1 before runtime refresh' "$probe_cmd" /d /c call "$probe_cc1_script"
# При ошибке продолжаем сравнение каталогов и DLL, затем повторяем исходный сбой.
done
cd -- "$probe_root" || { printf '[ERROR] Cannot open the repository directory.\n'; exit 1; }
probe_objdump="${probe_cc%/*}/objdump.exe"
"$probe_objdump" -p "$probe_cc1" > "$probe_dir/cc1-imports.txt" || {
printf '[ERROR] Cannot inspect cc1 imports.\n'; exit 1;
}
printf '\n[INFO] cc1 DLL imports:\n'
sed -n '/DLL Name:/p' "$probe_dir/cc1-imports.txt" || { printf '[ERROR] Cannot read cc1 imports.\n'; exit 1; }
source "$probe_root/tools/chatgui/compiler_runtime.sh"
for probe_dll in "$probe_build/"*.dll; do
[ -f "$probe_dll" ] || continue
chatgui_is_compiler_runtime_dll "${probe_dll##*/}" || continue
probe_system_dll="${probe_cc%/*}/${probe_dll##*/}"
[ -f "$probe_system_dll" ] || continue
cmp -s "$probe_dll" "$probe_system_dll"
probe_result=$?
case "$probe_result" in
0) printf '[INFO] DLL matches UCRT64: %s\n' "${probe_dll##*/}" ;;
1) printf '[WARN] DLL differs from UCRT64: %s\n' "${probe_dll##*/}"
probe_step 'DLL hashes' sha256sum "$probe_dll" "$probe_system_dll" || exit $? ;;
*) printf '[ERROR] Cannot compare DLL: %s, exit code: %d\n' "$probe_dll" "$probe_result"; exit "$probe_result" ;;
esac
done
probe_step 'Refresh compiler runtime DLLs' chatgui_refresh_compiler_runtime "$probe_build" "${probe_cc%/*}" || exit $?
cd -- "$probe_build" || { printf '[ERROR] Cannot open the ChatGUI build directory.\n'; exit 1; }
MSYS2_ARG_CONV_EXCL='*' probe_step 'Launch cc1 after runtime refresh' "$probe_cmd" /d /c call "$probe_cc1_script" || exit $?
MSYS2_ARG_CONV_EXCL='*' probe_step 'Replay Ninja bands.c command with GCC verbose output' \
"$probe_cmd" /d /c call "$probe_replay" || exit $?
fi
probe_step 'Build Opus with CMake' cmake --build "$probe_build" --target opus_internal --parallel 1 --verbose || exit $?
printf '\n[SUCCESS] Standalone Opus compilation and CMake Opus build completed.\n'
+101 -5
View File
@@ -62,11 +62,24 @@ AC_ARG_ENABLE([hardening],
[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"
HARDEN_CFLAGS="-D_FORTIFY_SOURCE=2 -fstack-protector-strong -Wformat=2"
compiler_version=$($CC --version 2>&1) || AC_MSG_ERROR([Cannot identify compiler for hardening])
case "$compiler_version" in
*clang*) ;;
*) HARDEN_CFLAGS="$HARDEN_CFLAGS -fstack-clash-protection -Wformat-overflow=2" ;;
esac
case "$host" in
*mingw* | *msys* | *cygwin*)
HARDEN_LDFLAGS="-Wl,--dynamicbase,--nxcompat,--high-entropy-va"
;;
*)
HARDEN_CFLAGS="$HARDEN_CFLAGS -fPIE"
HARDEN_LDFLAGS="-Wl,-z,relro -Wl,-z,now -pie"
;;
esac
CFLAGS="$CFLAGS $HARDEN_CFLAGS"
LDFLAGS="$LDFLAGS $HARDEN_LDFLAGS"
AC_MSG_NOTICE([Hardening flags enabled: $HARDEN_CFLAGS])
AC_MSG_NOTICE([Hardening flags enabled: CFLAGS=$HARDEN_CFLAGS LDFLAGS=$HARDEN_LDFLAGS])
fi
# ==================== НАДЁЖНЫЙ ДЕТЕКТ WINDOWS ====================
@@ -95,6 +108,19 @@ AC_MSG_RESULT([$os_freebsd])
AM_CONDITIONAL([OS_FREEBSD], [test "x$os_freebsd" = "xyes"])
# ==================== DETECT LINUX ====================
AC_MSG_CHECKING([for Linux])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#ifndef __linux__
#error "Not Linux"
#endif
]], [[ return 0; ]])],
[os_linux=yes],
[os_linux=no])
AC_MSG_RESULT([$os_linux])
AM_CONDITIONAL([OS_LINUX], [test "x$os_linux" = "xyes"])
# Set TUN libs based on platform
if test "x$os_windows" = "xyes"; then
TUN_LIBS="-liphlpapi -lws2_32 -ladvapi32 -lbcrypt -ldbghelp"
@@ -127,6 +153,75 @@ fi
AM_CONDITIONAL([HAVE_WHISPER], [test "x$with_whisper" = "xyes"])
AC_SUBST([WHISPER_LIBS])
# ==================== FFMPEG (OPTIONAL) ====================
AC_ARG_WITH([ffmpeg],
AS_HELP_STRING([--with-ffmpeg], [Enable video transcoding (requires FFmpeg libav libraries)]),
[with_ffmpeg=$withval],
[with_ffmpeg=no])
if test "x$with_ffmpeg" = "xyes"; then
save_LIBS="$LIBS"
FFMPEG_LIBS="-lavformat -lavcodec -lavutil -lswscale -lswresample"
LIBS="$FFMPEG_LIBS $LIBS"
AC_CHECK_HEADER([libavformat/avformat.h], [
AC_CHECK_HEADER([libavcodec/avcodec.h], [
AC_CHECK_HEADER([libavutil/imgutils.h], [
AC_CHECK_HEADER([libswscale/swscale.h], [
AC_CHECK_HEADER([libswresample/swresample.h], [
AC_CHECK_LIB([avformat], [avformat_open_input], [
AC_DEFINE([HAVE_FFMPEG], [1], [Define to enable FFmpeg video transcoding])
AC_MSG_NOTICE([FFmpeg enabled])
], [
AC_MSG_WARN([FFmpeg libs not found, disabling ffmpeg])
with_ffmpeg=no
])
], [AC_MSG_WARN([swresample.h not found, disabling ffmpeg]); with_ffmpeg=no])
], [AC_MSG_WARN([swscale.h not found, disabling ffmpeg]); with_ffmpeg=no])
], [AC_MSG_WARN([imgutils.h not found, disabling ffmpeg]); with_ffmpeg=no])
], [AC_MSG_WARN([avcodec.h not found, disabling ffmpeg]); with_ffmpeg=no])
], [AC_MSG_WARN([avformat.h not found, disabling ffmpeg]); with_ffmpeg=no])
LIBS="$save_LIBS"
fi
if test "x$with_ffmpeg" != "xyes"; then
FFMPEG_LIBS=""
FFMPEG_CFLAGS=""
fi
AM_CONDITIONAL([HAVE_FFMPEG], [test "x$with_ffmpeg" = "xyes"])
AC_SUBST([FFMPEG_LIBS])
AC_SUBST([FFMPEG_CFLAGS])
# ==================== SILERO VAD (OPTIONAL, requires ONNX Runtime) ====================
AC_ARG_WITH([silero-vad],
AS_HELP_STRING([--with-silero-vad], [Enable Silero voice activity detector (requires ONNX Runtime)]),
[with_silero_vad=$withval],
[with_silero_vad=no])
if test "x$with_silero_vad" = "xyes"; then
AC_CHECK_HEADER([onnxruntime/onnxruntime_c_api.h], [
save_LIBS="$LIBS"
LIBS="-lonnxruntime $LIBS"
AC_CHECK_LIB([onnxruntime], [OrtGetApiBase], [
SILERO_VAD_LIBS="-lonnxruntime"
AC_DEFINE([HAVE_SILERO_VAD], [1], [Define to enable Silero VAD via ONNX Runtime])
AC_MSG_NOTICE([Silero VAD enabled (ONNX Runtime)])
], [
AC_MSG_WARN([libonnxruntime not found, disabling silero-vad])
with_silero_vad=no
])
LIBS="$save_LIBS"
], [
AC_MSG_WARN([onnxruntime/onnxruntime_c_api.h not found, disabling silero-vad])
with_silero_vad=no
])
fi
if test "x$with_silero_vad" != "xyes"; then
SILERO_VAD_LIBS=""
fi
AM_CONDITIONAL([HAVE_SILERO_VAD], [test "x$with_silero_vad" = "xyes"])
AC_SUBST([SILERO_VAD_LIBS])
# Checks for header files, typedefs, functions...
AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h netinet/in.h stdint.h stdlib.h string.h sys/ioctl.h sys/socket.h unistd.h])
AC_TYPE_SIZE_T
@@ -153,10 +248,11 @@ AC_OUTPUT
# Summary
echo "
Configuration summary:
----------------------
---------------------
Host: ${host}
Windows: ${os_windows}
Prefix: ${prefix}
Compiler: ${CC}
CFLAGS: ${CFLAGS}
"
Silero VAD: ${with_silero_vad}
"
+4 -4
View File
@@ -197,14 +197,14 @@ SRC_SOURCES=(
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.c transport_layer/etcp_session.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
etcp_router.c route_crypto.c
proxy/udp_proxy.c proxy/socks_proxy.c proxy/http_proxy.c
lwip_tcp/lwip_pbuf.c
)
@@ -272,7 +272,7 @@ 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 " 2. Copy utun.exe + wintun.dll + LICENSE + LICENSING + THIRD_PARTY_NOTICES to the same directory"
echo " 3. Run: utun.exe -f -p utun.pid -l utun.log"
else
echo "Run on Windows:"
-35
View File
@@ -1,35 +0,0 @@
План доработок
[ ] продумать архитектуру репликации таблицы узлов группы
[ ] добавить узлы хелперы (для проксирования трафика)
Категории узлов:
- узлы без нат []
- узлы с 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% (обновляется каждый час)
Сделать подключение к группе
+1524
View File
File diff suppressed because it is too large. Load diff
+465
View File
@@ -0,0 +1,465 @@
# P2P аудио- и видеозвонок пользователю — архитектура
Статус: реализовано. Звонок использует роутер, CM подготавливает оптимальный путь.
## 1. Область
Аудио и видео 1:1. Звонок можно инициировать **из CHAT-группы или из p2p-чата (DM)**.
Модуль оперирует `group_id` + `peer_node_id`. Для `dm:<conv_id>` функция `dm_call_route`
выбирает общую CHAT-группу с живым BGP-маршрутом к собеседнику; отдельная DM-группа не создаётся.
Аудио и видео используют отдельные каналы `etcp_router`; возможности видеокодеков
согласуются в активном звонке (см. [call_video.txt](call_video.txt)). Работает desktop↔desktop и desktop↔android
(обе стороны компилируют общий стек `etcp_router` / `topo_group` (BGP) / `conn_mgr`).
Полный цикл: проверка онлайна (BGP) → INVITE → звонок → ACCEPT/DECLINE →
аудиопоток и опциональное видео через роутер → HANGUP. CM инициируется вызывающим при старте, вызываемым при принятии вызова.
### Входящие вызовы и телефонная занятость
Android хранит пять независимых настроек: показывать статус звонка, предупреждать вызывающего,
отклонять при телефонной занятости, отклонять все обычные звонки, отклонять все приглашения в рацию.
Они передаются маской `CALL_POLICY_*` через `[chat] incoming_call_policy` при старте,
а при изменениях — вместе с живым состоянием телефона через JNI в uasync.
Первые три настройки используют `READ_PHONE_STATE`; запреты всех входящих разрешения не требуют.
Наблюдатель учитывает все активные SIM, набор номера, удержание и входящий телефонный вызов.
Без разрешения или без телефонной службы состояние неизвестно, а не свободно.
Приоритет: запрет типа вызова → автоматический отказ при телефонной занятости → предупреждение с выбором.
Отказ при занятости возвращает `CALL_REASON_PHONE_BUSY`/`RADIO_CALL_PHONE_BUSY`.
Предупреждение переводит попытку в `WAIT_DECISION`: у вызываемого нет события INCOMING, рингтона и регистрации в Telecom.
Вызывающий видит «Абонент разговаривает по телефону», «Всё равно позвонить» и «Завершить».
Продолжение подтверждается повторяемой командой внутри той же попытки; освобождение телефона само не запускает звонок.
Повторные пакеты не создают повторных входящих событий. При изменении политики перед продолжением запрет проверяется снова.
Предел всей попытки — 60с от начала, включая выбор; продолжение deadline не меняет.
У рации первый ответ и подтверждение продолжения ограничены 2с; сообщение о занятости является ответом, но не RINGING.
Рация, уже включённая у адресата в указанном канале, возвращает `RADIO_CALL_ALREADY_ACTIVE`:
вызывающий получает уведомление, входящий вызов не показывается, его собственная рация не включается.
Этот ответ описывает существующую подписку и не создаёт нового входящего вызова даже при включённом запрете приглашений.
Принятие обычного приглашения в рацию по-прежнему сначала включает местную подписку, затем отправляет ACCEPT.
Android обычного звонка показывает своё уведомление после `onShowIncomingCallUi` и обрабатывает отказ Telecom.
## 2. Разбиение файлов (отдельный каталог `src/call/`)
| Файл | Язык | Сборки | Назначение |
|---|---|---|---|
| `src/call/call_proto.h` | C | везде | wire-формат сигналинга (`0x36`) и аудио (`0x3F`), subcmd |
| `src/call/call.h` / `call.c` | C | везде (autotools + GUI) | сигналинг, реестр сессий, таймеры, watchdog (20с по приёму), релей медиа с backpressure, счётчики |
| `src/call/call_audio.h/c` | C | только GUI-сборки | Opus encode/decode, фрейминг, pull-обвязка с тонами; регистрация через `call_audio_ops` |
| `src/call/call_tones.h/c` | C | только GUI-сборки | генератор тонов (glitch/ended), синус |
| `src/call/call_jitter.h/cpp` | C++ | только GUI-сборки | SoundTouch джиттер-буфер, C API |
| `src/call/call_video.h/c`, `call_video_transport.h/c` | C | везде | формат видеотрека, согласование возможностей и передача кадров отдельными каналами |
Autotools-сборка демона остаётся C-only (`.cpp` не компилируется); сигналинг `call.c`
присутствует и в демоне, аудиопроцессинг — только там, где есть GUI.
Диагностика: категория `call` (`DEBUG_CATEGORY_CALL`); транспортная доставка — `etcp_route`.
Desktop направляет сообщения FFmpeg и Qt в общий `debug_file` (по умолчанию `chatgui.log`).
Начальная проверка codec пишет краткий итог на info, детали и ожидаемые отказы — на debug:
`[gui] debug_categories=call=debug,video=debug`. Повторы FFmpeg внутри пробы сворачиваются со счётчиком;
рабочие ошибки параллельного звонка сохраняют error/warn. Информационный stderr libva отключён по умолчанию;
для диагностики драйвера можно явно запустить с `LIBVA_MESSAGING_LEVEL=2`.
Отсутствующая desktop-запись приложения создаётся в XDG data applications для регистрации в portal.
## 3. Протокол
Каналы роутера независимы:
| svc_id | Константа | Назначение |
|---|---|---|
| `0x36` | `ETCP_RT_ID_CALL` | сигналинг звонка |
| `0x3D` | `ETCP_RT_ID_CALL_VIDEO_CONTROL` | согласование и восстановление видеотрека |
| `0x3E` | `ETCP_RT_ID_CALL_VIDEO` | видеокадры |
| `0x3F` | `ETCP_RT_ID_CALL_AUDIO` | аудиокадры `CALL_MEDIA` |
Формат приёма сигналинга и аудио: `entry->dgram[0]=svc_id`, служебный заголовок роутера до
`ROUTER_SVC_PAYLOAD_OFF=26`, затем subcmd и тело команды.
Сигналинг (`0x36`):
```
CALL_INVITE 0x01 {call_id:8, group_id:8, num_tracks:1, force:1, expires_us:8, tracks:[{type:AUDIO, codec:OPUS, sr:4, ch:1, bitrate:4}]}
CALL_RINGING 0x02 {call_id:8}
CALL_ACCEPT 0x03 {call_id:8}
CALL_DECLINE 0x04 {call_id:8, reason:1}
CALL_BUSY 0x05 {call_id:8}
CALL_CANCEL 0x06 {call_id:8}
CALL_HANGUP 0x07 {call_id:8, reason:1}
CALL_END_ACK 0x08 {call_id:8}
CALL_PHONE_BUSY 0x09 {call_id:8}
```
Аудио (`0x3F`):
```
CALL_MEDIA 0x10 {call_id:8, track_id:1, seq:2, ts_ms:4, capture_pts_us:8, capture_epoch:8, codec_delay_samples:4, payload}
```
- `call_id` — случайный uint64, генерит вызывающий; идентифицирует сессию на обеих сторонах.
- `group_id` — CHAT-группа; для DM-звонка выбирается общая группа участников с живым BGP-маршрутом.
- Сигналинг: `etcp_route_send(..., ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT)`.
- Сигналинг, аудио и видео: обязательные `ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT` между конечными узлами.
- AES-128-CCM шифрует тело, Ed25519 подписывает заголовок роутера и шифртекст. Посредники не имеют ключа для расшифровки.
- Ключ получается из постоянных X25519-идентичностей собеседников; forward secrecy отсутствует.
- Маска защиты задаётся через `etcp_router_require_crypto`: открытые/частично защищённые данные отбрасываются
на конечном узле до изменения состояния приёма, очередей и `receive_ready`; отправка без требуемых флагов возвращает ошибку.
Пустые служебные пакеты роутера не являются медиаданными. Перехода на открытое медиа при ошибках нет.
- Каждый пакет получает случайный nonce(13) от OpenSSL CSPRNG; ошибка RNG прекращает шифрование.
Надбавка к сервисным данным: nonce(13) + CRC(4) + CCM tag(16) + Ed25519(64) = 97 байт.
- `CALL_MEDIA` переносит только аудио (`track_id=CALL_TRACK_AUDIO`); остальные значения приёмник отклоняет.
Видео имеет отдельный формат и транспорт: `call_video.h`, `call_video_transport.h`, [call_video.txt](call_video.txt).
- Аудио: Opus 48kHz mono, 20ms (960 сэмплов), ~24–32 kbps. ~50 кадров/с.
- Заголовок медиа — 36 байт с subcmd. `ts_ms` — время отправки для оценки сетевого джиттера;
`capture_pts_us` — время первого входного сэмпла в монотонной шкале отправителя, `INT64_MIN` при отсутствии метки.
`capture_epoch` различает разрывы захвата; `codec_delay_samples` передаёт Opus lookahead.
Старый формат не поддерживается; медиа принимается только от пира текущего звонка.
## 4. Машина состояний
```
Caller: IDLE → OUTGOING(INVITE) → RINGING(RINGING) → ACTIVE(ACCEPT) → ENDED
Callee: IDLE → INCOMING(INVITE) → RINGING(sent RINGING) → ACTIVE(accept) → ENDED
```
`ENDED` reasons: `LOCAL_HANGUP / REMOTE_HANGUP / DECLINE / BUSY / CANCEL /
NO_TRAFFIC / RING_TIMEOUT / CONNECT_FAIL`.
Таймауты:
- **кодограмма INVITE 300мс** — вызывающий шлёт её весь период вызова
(`OUTGOING` + `RINGING`) как «сердцебиение»: покрывает потерю INVITE/RINGING и
держит маршрут живым;
- **watchdog 2с у вызываемого** — нет кодограммы 2с → вызов считается сорванным
(отмена потерялась), сессия закрывается локально (`CALL_REASON_CANCEL`);
- ring-timeout 60с от начала, включая ожидание решения вызывающего (нет ответа → CANCEL);
- `CONN_EVENT_DOWN/TIMEOUT` CM не завершают звонок: роутер использует доступные пути;
- **NO_TRAFFIC 20с** — нет входящих медиа-кадров → авто HANGUP.
Завершающие кодограммы HANGUP/CANCEL/DECLINE повторяются каждые 300мс до END_ACK,
не дольше 20с. Это необходимо при смене эпохи роутера: старые очереди удаляются,
даже если отправитель ранее получил успешный результат постановки команды в очередь.
Медиа и их локальные очереди освобождаются сразу, ENDED публикуется один раз.
В реестре до ACK/таймаута остаётся только завершённая сессия с CM handle и таймером.
Получатель подтверждает также повторные команды после удаления сессии, без повторного ENDED.
Команда и ACK проверяются по подписи, шифрованию, размеру, call_id, пиру и группе существующей сессии.
ACK не подтверждается. Новый звонок имеет другой call_id и не затрагивается поздними командами старого.
При одновременном завершении стороны независимо подтверждают команды друг друга.
После ACK сессия удаляется на ближайшем tick завершения; при удалённом завершении CM удерживается
ещё 500мс для отправки ACK. Остановка ядра отменяет таймер без повторных отправок.
Запоздалые кодограммы вызова при идущем разговоре (`ACTIVE`) игнорируются.
Онлайн-проверка: `topo_node_find_by_id(group, peer) == NULL` → «не в сети» (event).
CM handle инициирует оптимальное подключение и удерживается до завершения звонка.
Медиа всегда идёт через `etcp_route_send`: роутер сам выбирает лучший путь и fallback.
Звонок не хранит ETCP_CONN и не завершается при потере отдельного транспорта.
Очистка записи CM отсоединяет handles и отменяет отложенные UP; handle закрывает владелец.
Закрытие CM handle не посылает DISCONNECT удалённым владельцам. Механизм NCD не меняется.
## 5. Отправка медиа — backpressure через локальный буфер
Требование: не дропать аудио при переполнении роутера — держим в локальном буфере
отправки, дренируем при появлении места.
Механика (`etcp_router` уже предоставляет):
- `etcp_route_send(force=0)` возвращает `-1`, когда `send_q` роутера полон
(`count >= ROUTER_MAX_SEND_Q_PACKETS`).
- `etcp_router_send_q_has_room(...)` — есть ли место (порог `ROUTER_MAX_SEND_Q_PACKETS-1`).
- `etcp_router_on_send_ready(..., &waiter, drain_cb, arg)` — одноразовый waiter:
колбэк вызывается, когда `send_q->count <= threshold` (освободилось место).
В сессии:
- `tx_q` — локальная FIFO очередь закодированных кадров (ll_entry с dgram), ёмкостью
15с (750 кадров). При переполнении — дроп старейшего + счётчик `c_tx_dropped`.
- Алгоритм (`call_tx_flush`): пока `tx_q` не пуст:
- если `send_q_has_room` → `queue_data_get` + `etcp_route_send(force=1)` (после проверки места);
- иначе — если waiter не зарегистрирован → `etcp_router_on_send_ready(... drain_cb)` и выход.
- `drain_cb(q, arg)` → снова `call_tx_flush(session)` (re-arm при необходимости).
- Весь код — в uasync-потоке (single-threaded, реентерабельность как в
`media_delivery.c:stream_send_chunk_cb`).
## 6. Потоки и жизненный цикл
`call_session`, CM handle, очередь TX и таймеры принадлежат uasync-потоку.
Аудиопоток кодирует PCM и постит `call_send_media`; приём в uasync кладёт Opus в `call_jitter`.
Декодер, SoundTouch и генератор тонов принадлежат аудиопотоку.
Кольцо кодированных кадров и снимок статистики защищены мьютексом; статистика не читает SoundTouch из другого потока.
`CallTimeline` хранит фактическое соответствие входных участков SoundTouch выходному PCM.
Платформа публикует позицию воспроизведения с локальной монотонной меткой; `call_audio_get_clock`
возвращает исходный PTS слышимого участка. Локальная тишина и тоны удерживают PTS.
Невалидная/устаревшая отметка устройства не разрешает продвижение видео. Контракт и ограничения — `call_video.txt`.
GUI запускает аудиоустройство по CALL_ACCEPTED. При CALL_ENDED начинается финальный тон;
устройство останавливается перед `call_audio_release`. Явное завершение звонка прекращает
воспроизведение накопленной речи и включает тон завершения — на обеих платформах.
Android останавливает гудки до настройки звонкового аудиотракта. После выбора маршрута
capture/play потоки независимо создают и запускают AudioRecord/AudioTrack; каждый сразу обслуживает своё направление.
`start()` подтверждает запуск подготовки, ошибки устройств передаются владельцу на main через `onFailure`.
Ошибка любого направления завершает звонок. Общий `stop()` отменяет подготовку, разблокирует I/O и ждёт оба потока
до освобождения Android-устройств и native-аудиостека. Отдельные locks защищают подготовку/остановку каждого устройства.
Замер AEC планируется через 300мс после готовности обоих направлений и не задерживает воспроизведение.
При наличии Telecom Connection режимом и выходом звонка управляет только Telecom.
ACTIVE запрашивается до создания аудиоустройств; AudioManager/прямой preferred device не дублируют выбор.
На API 34+ маршрут запрашивается через доступный CallEndpoint, на старых — через Connection.setAudioRoute.
Подтверждённый маршрут приходит из callback системы, отдельно от желания пользователя; ошибка запроса логируется.
Если Connection ещё нет, используется AudioManager. Поздняя регистрация передаёт управление Telecom,
снимая заявку приложения на режим и прямые устройства. Рация сохраняет отдельный AudioManager-путь.
Реальное время запуска/переключения зависит от Android и требует проверки на устройстве.
Маршрутизатор использует один снимок входных/выходных устройств; обновляет его при старте,
событиях устройств/BT-профиля и явной переоценке. По завершении снимок освобождается.
INFO-логи Android показывают длительности настройки режима, регистрации callback, BT-профиля, выбора маршрута,
создания/старта устройств, первого чтения микрофона и первой записи в AudioTrack, а также готовность другого направления.
Общий `call_audio` логирует время ожидания первого PCM; запись в AudioTrack может содержать начальную тишину.
Эффективный рабочий буфер AudioTrack на всех поддерживаемых API при старте запрашивается размером
два блока PCM (40мс); на API 31+ стартовый порог следует фактически принятому размеру.
Ёмкость выделенной памяти может быть больше. Ограничение повторяется по callback фактической
смены выхода на main, под trackLock; callback старого AudioTrack игнорируется и снимается при stop.
Подачу PCM задаёт свободное место в AudioTrack, без дополнительного таймера 20мс.
Частичная запись удерживает остаток одного блока до полной передачи; следующий pull ждёт.
Сетевой запас остаётся в адаптивном JB, его рост и SoundTouch-догон не изменены.
Лог `playback buffering` показывает причину настройки, запрошенный/принятый размер, порог и ёмкость;
ежесекундный `audio summary` — queueUs, текущий порог и underruns; новый episode underrun имеет уровень WARN.
Это очередь AudioTrack, а не полная сквозная задержка и не сетевой jitter reserve.
Ранее на Samsung startFrames=1920 был принят, но без ограничения рабочего буфера
written-played оставался 120–160мс; одного стартового порога недостаточно.
Замер Samsung подтвердил постоянную очередь 40мс. При прежнем параллельном управлении маршрутом
AudioTrack пересоздавался, очередь временно достигала 160мс, а ожидание свободного места — 328–336мс.
При старте AudioTrack.play занимал 1080мс. После перехода к единственному владельцу Telecom
в проверенном звонке старт занял 62мс, очередь оставалась не более 40мс и при смене выхода.
Переключение всё ещё останавливало потребление на 293–328мс; этот всплеск принимает сетевой JB.
На desktop `AudioDiag: capture clock` при call=debug выводит секундную сводку исходных backend-блоков:
число блоков/сэмплов, накопленную последовательность, диапазон ошибки относительно конца предыдущего блока,
latency PulseAudio, число скачков >2мс, hole-сэмплы и блоки без достоверного измерения.
Первый скачок за секунду содержит обе latency/поправки конвертера, expected/actual PTS и монотонное время.
Backend публикует только числовой снимок в ограниченное кольцо; форматирование выполняет reporter.
Исходный backend-снимок остаётся наблюдением. В capture worker позиция PCM задаёт непрерывные PTS,
а backend медленно корректирует их привязку: постоянная времени 2с, предел 0.5% (100мкс на 20мс PCM).
Скорректированное время используется и для Opus, и для выбора AEC-референса. Сам PCM не изменяется.
Разрыв sequence, смена route и скачок исходных меток >20мс по-прежнему сбрасывают capture/AEC и эпоху;
порог 2мс в call_audio не увеличивается. Секундный `voice-io: capture clock` показывает phase/error/adjustment.
Тесты проверяют десять минут дрейфа +/-300ppm, скачки оценки до 14мс, разбиение чанков 25мс на Opus 20мс,
сохранность PCM, явную потерю, смену устройства и большой разрыв исходных часов.
Счётчик sequence описывает полученные backend-сэмплы; отсутствие потерь очереди само по себе
не доказывает непрерывность физического захвата. Проверять вместе с hole/overflow и route-событиями.
Для захвата звонка desktop явно запрашивает период 20мс вместо дефолта PulseAudio 25мс;
фактический период сообщает `audio: call-capture initialized`. Сборщик по-прежнему принимает произвольные чанки.
Секундные замеры задержки при `call=debug`:
- `voice-io: capture latency`: возраст первого сэмпла перед native feed (включает сам блок 20мс),
подготовка/AEC в worker и вызов native feed; диапазоны min/avg/max в микросекундах.
- `voice-io: playback latency`: приблизительный остаток PCM после mix, включая частичный блок,
длительность pull и недостающие сэмплы. Это очередь приложения, отдельно от backend.
- `AudioDiag: playback clock`: latency backend, задержка конвертера/cache и максимальный размер
backend-порции в микросекундах. При measured=0 нулевые границы не означают нулевую задержку.
- Windows `AudioDiag: WASAPI queue`: текущий padding shared-mode, отдельный stream latency,
converter/cache, размер callback и незавершённого mapped buffer. Диапазоны — min/avg/max в мкс;
`padding_samples=0` означает отсутствие пригодного замера, а не пустую очередь.
Снимок снимается перед заполнением client PCM; запись и форматирование выполняет reporter.
`AudioDiag: audio timing` для стадий `callback`/`write` при `call=debug` показывает интервалы
callback и время блокирующей передачи в WASAPI. HRESULT ошибок и потери диагностической очереди логируются.
Снимки только наблюдают: Windows-аудиочасы остаются `callback-estimate` (`measured=0`).
- `video playout`: показанные кадры, `speed_ppm`, диапазон A/V-фазы, интервалы показа,
опоздание worker и ожидания аудиочасов. Опоздание кадра не вызывает пропуск; догон до 1.6x.
- Desktop `video RX worker`: число декодированных кадров, максимальное время декодирования,
возраст кадра после декодирования и опоздание пробуждения worker после sleep. Снимок аудио
содержит `audio_measured`, PTS и позиции played/written. Все эти строки относятся к `call=debug`;
`video=trace` для этой диагностики не требуется.
- `video GUI delivery`: доставленные изображения, ожидание уведомления, промежуток доставки,
`paint_wait_max_us` до подтверждения рисования. В GUI один ожидающий кадр, замены нет.
- `video port TX paused/resumed`: сохранённый хвост пачки, первый ID, длительность ожидания
и число повторов. Busy сохраняет encoded-кадры и не запрашивает KEY.
- `video port RX full/drained`: заполнение и полная разгрузка encoded FIFO перед возобновлением
приёма. Hysteresis переносит ограничение до новых входов sender encoder, освобождая запас для догона.
- `call audio TX timing`: ожидание g_mtx, обработка/кодирование под ним, ожидание posted task;
post включает ожидание lock при выполнении задачи, его разрешение 100мкс. На Android этот же
замер обработки включает программный AEC, на desktop AEC уже выполнен в capture worker.
- `call audio TX queue`: число ожидающих кадров, возраст старейшего и avg/max ожидания кадров,
переданных роутеру за интервал. Отправка роутеру не является подтверждением доставки пиру.
- Android `audio capture latency`: возраст первого сэмпла перед native feed и число измеренных
привязок; дополнительные поля старта устройств разделяют min-buffer query, constructor, routing и buffer setup.
Возраст PCM содержит часть измеренной обработки: эти числа нельзя механически складывать.
PTS/возраст используют локальный MONOTONIC, интервалы внутри native — локальный get_time_us/get_time_tb;
эти временные базы и часы разных устройств напрямую не вычитаются. Все сводки формируются вне device callback.
Период захвата 20мс и новые измерения требуют контрольного звонка; дополнительная desktop PCM-очередь,
жизненный цикл подготовки Android и таймер показа видео этим изменением не менялись.
На Android `CallPlaybackBuffer` подбирает рабочую очередь отдельно от JB/SoundTouch:
после 30с наблюдаемого продвижения playback head без underrun пробует уменьшить её на один
блок PCM (960 сэмплов, 20мс), до одного блока или фактического минимума устройства.
При росте AudioTrack.underrunCount добавляет один блок в пределах выделенной ёмкости.
Интервал следующей пробы удваивается (60/120/240/300с), чтобы не повторять частые неудачные уменьшения.
После старта, запроса/подтверждения маршрута и увеличения даются 2с на стабилизацию;
после уменьшения первый underrun учитывается немедленно. Отсутствие продвижения head или
пропуск наблюдений >1с обнуляет стабильный участок. Сброс системного счётчика учитывается отдельно.
Фактическая смена выхода начинает подбор с 40мс заново. Непринятая меньшая заявка фиксирует
нижнюю границу до следующего выхода; ошибки API выключают подбор с WARN/ERROR, сохраняя воспроизведение.
Reporter управляет размером под trackLock независимо от фильтра debug; playback не берёт этот lock.
Нет flush/recreate, частичная запись PCM сохраняется. После смены размера/выхода через секунду
повторно измеряется задержка для AEC. Ожидание полного буфера и недостача сети не увеличивают его размер.
Лог настройки содержит reason, previous/requested/actualFrames и прирост underruns;
достижение ёмкости предупреждает не чаще раза в 10с. Принятие и стабильность 20мс ещё требуют
проверки на Samsung; буферы HAL и Bluetooth могут добавлять собственную задержку.
На desktop адаптация устройства ещё не реализована: выход принадлежит SoundManager,
изменение backend-буфера должно выполняться его владельцем, а не отдельным звонком.
Диагностика Android при `call=debug`: playback сохраняет один числовой снимок на блок (20мс)
в кольце 512 элементов, без форматирования/вывода. Отдельный reporter раз в 200мс забирает записи;
раз в секунду выводит `audio summary` с min/avg/max очередей, задержек pull/write и опоздания потока,
целевым запасом/разбросом доставки JB, темпом, недостающим PCM и отдельными счётчиками JB/AudioTrack.
Снимок JB не сбрасывает события для CALL_STATS. encodedMs — очередь Opus после pull,
processingMs — оставшийся PCM SoundTouch в длительности исходной речи; reserveMs зависит только от доставки.
Reporter опрашивает AudioTimestamp, routedDevice, underrunCount и размеры буфера не чаще раза в 200мс.
Playback читает только опубликованный неизменяемый снимок. Его begin/end и возраст deviceAgeUs
отличают задержку опроса от задержки аудио; stampQueryUs/routeQueryUs/propertiesQueryUs показывают стоимость запросов.
Android API может удерживать общие внутренние locks даже при вызове из reporter: перенос убирает
прямое ожидание диагностического запроса в playback, но сам по себе не доказывает отсутствие влияния на write/head.
frame/time аппаратного timestamp и время начала опроса печатаются отдельно;
при недоступности стоит stampOk=0. Времена trace — MONOTONIC; очередь устройства — written-played.
Эти показатели нельзя складывать с возрастом AudioTimestamp как независимые задержки.
В playback отдельно измеряются writeCallUs (только вызовы write), headCallUs (чтение позиции),
progressCallUs (публикация позиции ядру), jbSnapshotUs (нативный снимок), writeSpanUs (весь цикл записи)
и blockUs (pull до формирования строки). При частичных записях длительности вызовов суммируются за блок.
pullGapExcessUs — превышение интервала между началами соседних pull над 20мс;
включает ожидание устройства, а не только задержку планировщика (прежнее поле wakeLateUs удалено).
Старое поле writeUs включало чтение позиции и прогресс: его нельзя трактовать как длительность одного AudioTrack.write.
Stop дожидается reporter, включая непрерываемый Android-запрос, до освобождения AudioTrack.
`audio detail` сохраняет 2с до и 3с после старта, фактической смены routedDevice или новой аномалии:
очередь устройства >100мс в течение секунды, отсутствие продвижения head 100мс при наличии данных,
либо новый episode underrun (затухает через 3с без повторений). Порог очереди — диагностический,
не настройка JB и не изменение воспроизведения. События внутри окна объединяются, между выгрузками
не менее 10с; продолжающаяся аномалия не создаёт повторных дампов. Восстановление логируется INFO.
Подробные строки содержат по четыре снимка и заголовок с порядком колонок; при stop окно может быть неполным.
Переполнение кольца при задержке reporter имеет отдельный WARN lost=N. Аудиоданные не сохраняются.
Выключение call=debug прекращает сбор/выгрузку; буфер и reporter освобождаются с playback worker.
Сравнивать сводки следует по call ID и monoUs; рост сетевого запаса сам по себе не является ошибкой.
## 7. Буфер звонка и восстановление
`call_jitter` независим от `radio_jitter`: рация сохраняет свои правила задержки и сброса.
Буфер звонка хранит до 750 кодированных кадров (15с): 10с перебоя и 5с запаса.
Целевой запас — `clamp(max-min, 60, max_reserve_ms)` мс по изменению задержки доставки:
на входящем кадре накапливается разность интервалов приёма и отправки, десять секундных интервалов хранят её экстремумы.
Отправитель уже передаёт timestamp в CALL_MEDIA; смещение часов пиров исключается, переполнение uint32 учитывается.
Подготовка местного аудиоустройства и ускоренное воспроизведение очереди не изменяют оценку доставки.
Минимум/максимум глубины очереди для GUI собираются отдельно в аудиопотоке за те же десять секунд.
Верхний предел задаётся `call_jitter_max_reserve_ms`: 500–2000 мс, по умолчанию 1000 мс.
Настройка есть в аудиопараметрах desktop и Android и применяется к следующему звонку.
Начальное и повторное накопление ждёт этот запас; увеличение запаса не прерывает текущую речь.
Ускорение зависит от превышения запаса. При отсутствии входящих кадров обычное воспроизведение
может исчерпать запас: это не жёсткий запрет воспроизведения ниже цели.
Статистика раз в секунду отображается в desktop-окне и Android-экране звонка (под кнопками): глубина, запас, минимум/максимум,
темп, RTT, удаления при переполнении и недостачи PCM. Диагностика буфера — категория `call=debug`.
После восстановления кадры последовательно декодируются; темп плавно растёт до 1.6x
без изменения высоты голоса: линейный предел роста 1.0x→1.6x за 500мс воспроизведения.
При уменьшении накопления целевой темп снижается к 1.0x; скорость снижения ограничена тем же пределом.
Пауза аудиоустройства не сбрасывает буфер. Переполнение удаляет старейший кадр с WARN и счётчиком.
Роутер сохраняет последовательность и неподтверждённые данные при смене пути.
Для сервиса звонка срок без прогресса ACK увеличен примерно до 20с; прочие сервисы сохраняют прежний срок.
Перезапуск удалённого процесса/роутерной сессии не является кратковременным перебоем пути и может сбросить данные.
SoundTouch: `USE_QUICKSEEK=1, SEQUENCE_MS=20, SEEKWINDOW_MS=10, OVERLAP_MS=4`.
## 8. Тоны и диагностика
- **Исходящий звонок — две фазы с разными гудками** (у вызывающего):
- фаза 1 «пытаемся связаться» (`OUTGOING`: INVITE отправлен/ретранслируется, маршрут
устанавливается) — гудок «соединение» (короткие бипы);
- фаза 2 «звоним» (`RINGING`: получен RINGING, у пира идёт вызов) — длинные гудки (ringback).
Гудки генерируются платформенно-нативно: desktop — цикличные MP3 через `SoundManager`
(`call_connecting`/`call_ringing`), Android — цикличные MP3 через `CallRingback`/`MediaPlayer`.
Переключение — по `CHAT_EVT_CALL_RINGING`,
остановка — по `CALL_ACCEPTED`/`CALL_ENDED`/`CALL_ERROR`/hangup.
- **Начальное ожидание первого PCM** → тишина без сигнала потери звука. Подготовка удалённого микрофона
и начальное накопление jitter не считаются перебоем. Существующий таймаут 20с без медиа действует с ACTIVE.
- **Недобор PCM ≥500мс после начала воспроизведения** → в `call_audio_pull_pcm` воспроизводится
`CALL_TONE_GLITCH`: `пи-пи (2×150мс, gap 100мс) … тишина до 1.0с … пи(200мс)
пауза(200мс) пи(200мс)`, 1000 Гц, цикл пока длится сталл. До порога — тишина при нехватке PCM.
При возобновлении PCM сигнал останавливается; одного входящего пакета без достаточного запаса недостаточно.
- **Завершение звонка → всегда** `CALL_TONE_ENDED`: 3 нисходящих 700/500/400 Гц по 150 мс.
- Счётчики: tx/rx кадры, `c_tx_dropped`/`c_rx_dropped`, дропы буфера, сталлы,
tempo-события, причины завершения. Логи `DEBUG_CATEGORY_CALL` (+стата раз в 1с).
## 9. GUI-события (extend `chat_event.h` + `gui_bridge.h` + android)
```
CHAT_EVT_CALL_INCOMING 32 [ch_id_len][ch_id][caller_node_id:8][call_id:8]
CHAT_EVT_CALL_RINGING 33 [call_id:8]
CHAT_EVT_CALL_ACCEPTED 34 [call_id:8]
CHAT_EVT_CALL_ENDED 35 [call_id:8][reason:1]
CHAT_EVT_CALL_DECLINED 36 [call_id:8][reason:1]
CHAT_EVT_CALL_STATS 39 [call_id:8][rtt:2][buffer:2][tempo:2][dropped:2][underruns:2][min:2][max:2][reserve:2]
CHAT_EVT_CALL_ERROR 38 [call_id:8][err:1][text:var]
CHAT_EVT_CALL_AUDIO_ROUTE 39 (Android: текущий маршрут/наличие гарнитуры)
```
## 10. Основные функции публичного API
`call.h`: сигналинг и отправка готового Opus выполняются в uasync-потоке.
Из других потоков команды передаются через `uasync_post`/мост GUI и соответствующие `call_*_trampoline`.
`ch_id` — идентификатор CHAT-группы либо `dm:<conv_id>`; `out_call_id` — значение, заранее сгенерированное вызывающим.
```c
void call_start(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_node_id, uint64_t out_call_id);
void call_accept(struct UTUN_INSTANCE* inst, uint64_t call_id);
void call_decline(struct UTUN_INSTANCE* inst, uint64_t call_id);
void call_hangup(struct UTUN_INSTANCE* inst, uint64_t call_id);
int call_is_online(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_node_id);
int call_send_media(struct UTUN_INSTANCE* inst, uint64_t call_id, const uint8_t* opus, int len,
const struct call_capture_time* timing);
```
`call_audio.h`: движок сам кодирует PCM и ставит отправку Opus в uasync.
Захват и воспроизведение используют по одному аудиопотоку на направление.
```c
int call_audio_feed_pcm(uint64_t call_id, const int16_t* pcm, int count, int64_t capture_pts_us);
int call_audio_feed_prepared_pcm(uint64_t call_id, const int16_t* pcm, const uint8_t* mute_mask, int64_t capture_pts_us);
int call_audio_pull_pcm(uint64_t call_id, int16_t* out, int max_samples);
void call_audio_release(uint64_t call_id);
```
`feed_pcm` принимает ровно 960 отсчётов; `feed_prepared_pcm` — 960 отсчётов после платформенного AEC
и маску mute. `call_audio_release` вызывается в GUI-потоке после остановки обоих направлений I/O.
Создание движка — `call_audio_start`, для подготовленного PCM — `call_audio_start_prepared`.
Видео настраивается через `call_video_configure`, кадры отправляются через `call_video_send_frame`
в uasync-потоке; полный контракт — в `call_video_transport.h`.
`call_init / call_destroy` вызываются из `chat_service_start / chat_service_stop` в `utun_instance.c`:
инициализация после `dm_core_init`, освобождение перед `dm_core_destroy`.
## 11. Платформенный I/O
- **Desktop:** `tools/chatgui/src/call_audio_engine.cpp` — miniaudio full-duplex
(capture через VoiceAudioIo → `call_audio_feed_prepared_pcm`, `call_audio_pull_pcm` → playback) + окно звонка (UI)
+ события в `gui_bridge_impl.cpp`.
- **Android:** JNI `nativeCall*` + Kotlin `AudioRecord`/`AudioTrack`.
**Переключение источника:** `AudioManager` (API31+ `setCommunicationDevice` +
`availableCommunicationDevices`; fallback `setSpeakerphoneOn` + `startBluetoothSco`),
детект гарнитуры (`ACTION_HEADSET_PLUG`, BT-профиль, `ACTION_SCO_AUDIO_STATE_UPDATED`),
кнопка-цикл трубка/громкая/гарнитура, авто-выбор гарнитуры. `nativeCallSetAudioRoute(route)`.
## 12. Сборка
- `src/Makefile.am` + `tests/Makefile.am`: `call.c` (C, без `.cpp`). `src/call/` в include path.
- `tools/chatgui/libutun/CMakeLists.txt`: +`call_audio.c`, `call_tones.c`,
`audio_jitter.cpp`, soundtouch sources (AAFilter, FIFOSampleBuffer, FIRFilter,
InterpolateCubic/Linear/Shannon, RateTransposer, SoundTouch, TDStretch, cpu_detect_x86,
mmx_optimized, sse_optimized) как C++. `call.c` попадёт через GLOB `src/*.c`.
- `tools/chatgui-android/libutun_lite/CMakeLists.txt` + `utun_sources.cmake`:
`LANGUAGES C CXX`, те же файлы.
- `src/transport_layer/etcp_api.h`: `ETCP_RT_ID_CALL=0x36`, `ETCP_RT_ID_CALL_VIDEO_CONTROL=0x3D`,
`ETCP_RT_ID_CALL_VIDEO=0x3E`, `ETCP_RT_ID_CALL_AUDIO=0x3F`.
- `lib/debug_config.h`: `DEBUG_CATEGORY_CALL 31`, `DEBUG_CATEGORY_COUNT` → 32.
## 13. Порядок задач
1. `call_proto.h` + svc_id + `DEBUG_CATEGORY_CALL`.
2. `call.c` (машина состояний, backpressure-media, watchdog, события, API) + `chat_core` хуки.
3. `audio_jitter.cpp` (SoundTouch) + `test_audio_jitter.c`.
4. `call_tones.c` + `call_audio.c` (Opus + фрейминг + тон-обвязка).
5. Desktop: `call_audio_engine.cpp` + UI + события.
6. Android: JNI + Kotlin AudioRecord/Track + переключение источника + UI.
7. Сборка (Makefile.am / CMake обе).
8. `test_call.c` (два инстанса), интеграция desktop↔desktop и desktop↔android.
-292
View File
@@ -1,292 +0,0 @@
# 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 вместо первого попавшегося.
+228
View File
@@ -0,0 +1,228 @@
# DM: доставка сообщений и файлов
Личные беседы принадлежат `src/dm/`. Общие CHAT-группы используются для знакомства,
проверки ролей и маршрутизации; группа не становится идентичностью беседы.
## Реализовано
- История и постоянная исходящая очередь сообщений в общей SQLite ядра.
- Доставка через живой маршрут router, включая BGP-транзит.
- При отсутствии маршрута — подписанный PUT доступному суперузлу общей группы сторон.
- Приём после проверки подписи и расшифровки, подтверждение только после commit.
- Доставка с суперузла после возвращения получателя, независимо от отправителя.
- Точное удаление промежуточного сообщения и сохранение квитанции для поздних повторов.
- Подготовка, шифрование, проверка и durable публикация медиа в рабочих потоках.
- Прямая передача файлов либо custody на отдельном M, доставка после restart и подтверждённое удаление.
- TTL и квота M; вложения и статусы доставки в Qt и Android.
- Голосовые Opus и видео MP4 с зашифрованными метаданными; общий ввод и отображение с каналами.
## Идентичность и шифрование
```text
conv_hash = SHA256("utun_dm_v" || min(node_a,node_b) || max(node_a,node_b))
conv_id = native_u64(conv_hash[0:8]) & 0x7FFFFFFFFFFFFFFF
content_key = SHA256(X25519(my_private, target_public) || "utun_dm_content")
msg_nonce = SHA256(conv_id || author || seq)[..13]
media_nonce = SHA256("utun_dm_media" || author || media_id || part)[..13]
```
Префикс conv_hash — 9 ASCII-байт без NUL. ID узлов подаются в хеш в порядке
байтов хоста; native_u64 читает первые 8 байт хеша в том же порядке.
Нулевой conv_id заменяется на 1.
Сообщения и медиа используют один content_key target, без ключей отдельных файлов.
AES-256-CCM: nonce 13 байт, tag 16 байт. Максимальная порция plaintext — 65535 байт;
порция медиа — 32768 байт. UUID медиа новый для каждой подготовки. Повтор доставки
использует неизменяемый ciphertext. Повторная подготовка после ошибки требует нового UUID.
Каноническое тело сообщения:
```text
[conv:8][seq:8][ts:8][author:8][ct_len:1][ct][enc_len:2][ciphertext+tag][author_sig:64]
```
Автор подписывает все поля до sig. conv связывает автора с получателем.
Для `ct=file` перед подписью добавляется descriptor `[UUID:16][plaintext_size:8][SHA256(ciphertext):32]`.
В зашифрованном теле находится `attachment_info`; пути хранения строятся только из UUID.
Размер plaintext сообщения ограничен 1024 байтами. seq — положительное int64.
```text
[kind:1][name_len:2][duration_ms:4][width:2][height:2][UTF8_name:name_len][waveform:100, только voice]
```
Поля метаданных многобайтной длины имеют big-endian формат. kind: 1=file,
2=voice, 3=video. Имя — basename до 255 байт, без разделителей, управляющих символов
и некорректного UTF-8. FILE имеет нулевые duration/dimensions; VOICE — положительную
длительность и 100 символов уровней waveform; VIDEO — длительность и размеры.
Длительность ограничена 24 часами, размеры — 16384. Метаданные проверяются до
локального commit и до подтверждения получателем; S/M видят только ciphertext.
Медиа ciphertext — последовательность порций с отдельным tag каждой порции.
Последняя порция короче; пустой файл содержит один tag. Размер plaintext и SHA256
ciphertext должны входить в подписанный manifest. Потоковая расшифровка проверяет все
tags, точный размер и хеш; владелец публикует временный файл только после успеха
и собственного durable сохранения.
## Владение и данные
- `dm_core`: беседы, история, outbox и подтверждение получателя.
- `dm_mailbox`: промежуточные сообщения суперузла и квитанции.
- `dm_crypto`: вывод conv_id, content_key и nonce сообщений без БД/сети.
- `media_crypto`: общие CCM и потоковые файловые примитивы для DM и групповых вложений.
- `dm_media`: файлы endpoint, загрузки и отдельная custody M.
- `dm_mailbox_media`: durable задания S, выбор M и повтор удаления до DELETE_ACK.
- `file_transfer`: подписанная передача байтов, backpressure и отдельный CM handle каждой операции.
- `attachment`: формат, валидация и тип метаданных, без сети и БД.
- `voice_file`: общий Opus-кодек отправки; `attachment_send` готовит файл в worker
и передаёт результат отдельно в `chat_core` либо `dm_media`.
```text
dm_conversations(conv_id, peer_node_id, peer_x25519, peer_ed25519, peer_name,
group_id, last_out_seq, last_in_seq, created_at, last_ts)
dm_messages(conv_id, dir, seq, ts, author, ct, data, sig)
dm_outbox(conv_id, seq, body, retry_at)
dm_pending_messages(recipient, sender, seq, msg, group_id, retry_at)
dm_mail_receipts(recipient, sender, seq, receipt, group_id)
```
Запись исходящего сообщения, увеличение seq и запись неизменяемого body в outbox —
одна транзакция. При ошибке откатывается всё, сообщение не выходит в сеть.
Приём, создание/обновление беседы и история — также одна транзакция.
Dedup — точный ключ (conv,dir,seq) с проверкой сохранённой подписи. last_in_seq —
статистика максимального номера, не курсор подтверждения: seq=99 после seq=100
сохраняется. Тело с другой подписью для уже сохранённого seq отклоняется.
У каждой локальной очереди таймер 1с; порция обработки до 32 задач, повтор задачи
через 5с. retry_at предотвращает голодание хвоста очереди. Перед отправкой проверяется
backpressure router. Потеря пути сохраняет durable задачу; остановка отменяет таймеры,
но сохраняет БД. При старте очередь продолжает работу.
## Подтверждения и протокол
Сервис `ETCP_RT_ID_DM=0x34`: MSG=1, RECEIPT=2.
```text
["DMACK001":8][conv:8][seq:8][author:8][recipient:8][SHA256(message):32][recipient_sig:64]
```
Квитанцию создаёт только получатель после commit. Подпись охватывает первые 72 байта.
Отправитель удаляет outbox только после проверки подписи и точного хеша тела.
История беседы сохраняется. Подтверждение транспорта router не заменяет квитанцию.
Сервис `ETCP_RT_ID_DM_MAILBOX=0x35`:
```text
PUT=1 [recipient:8][message]
DELIVER=2 [message]
RECEIPT=3 [receipt:136]
STORED=4 [conv:8][seq:8]
```
Все пакеты идут с подписью router; unsigned control отклоняется.
PUT принимается только от автора, при роли supernode=yes у локального узла
в общей группе автора и получателя. Роль берётся из подписанного adm_tags;
node_type=4 — производная сетевой классификации, для авторизации не используется.
storage=yes само по себе не даёт права хранить сообщения.
STORED подтверждает durable запись суперузлом, outbox до квитанции получателя остаётся
у автора. Суперузел повторяет DELIVER при появлении живого маршрута. RECEIPT проверяется
по точному сохранённому телу; запись квитанции и удаление копии — одна транзакция.
Квитанция пересылается автору. Поздний PUT возвращает её и не возрождает задачу.
dm_mail_receipts содержит только служебные квитанции; доставленное содержимое
с промежуточного узла удаляется. Ранее использовавшийся dm_mail/PULL/HELLO и
диапазонные ACK не поддерживаются: это devel, совместимость не сохраняется.
TTL текстовых сообщений не введён; недельный TTL из pm.txt относится к медиа.
## Доставка медиа по pm.txt
1. Общая передача байтов: явные peer/group/file/size, backpressure и CM handle
на всю активную операцию. Владение handle сохраняется после DOWN/TIMEOUT.
Используется `ETCP_RT_ID_FILE_TRANSFER=0x37`. Один callback читает/отправляет
не более 8 КиБ, продолжение через `call_soon`; inflight ограничен 16 пакетами.
2. Отдельный owner dm_media: подготовка ciphertext, manifest, отправка/скачивание,
временные файлы, TTL и лимит хранения. Каталоги dm_media/ и dm_pending/ вне
группового обхода очистки orphan-файлов; групповые объявления HAVE_BLOCK не используются.
3. Суперузел сначала durable регистрирует сообщение и media job, затем выбирает M.
M скачивает ciphertext у A и сообщает S подтверждённое хранение, место и expiry.
4. Текстовая квитанция и квитанция медиа независимы. Получатель подтверждает медиа
после проверки, публикации файла и commit. S хранит задачу удаления у M до DELETE_ACK;
поздний STORED не возрождает завершённую доставку.
5. TTL медиа `dm_media_ttl_sec` по умолчанию 604800 с; квота `dm_media_storage_mb`
по умолчанию 1024 МиБ. Настройки на M. Новая custody отклоняется при
недостатке квоты; уже подтверждённые копии не вытесняются молча раньше срока.
6. Stop снимает новые задачи и закрывает transfer/CM handles, затем ждёт
media_async с живыми owner-контекстами. Группы уничтожаются после owners.
Control `ETCP_RT_ID_DM_MEDIA=0x38`: OFFER, STORE, STORED, RECEIPT, DELETE,
DELETE_ACK, EXPIRED, REJECT; точные форматы заданы в `dm_media.h` и реализациях owners.
Подпись router обязательна. Media receipt содержит `DMFILE01`, conv, seq, author,
recipient, UUID и SHA256 полного подписанного сообщения; B подписывает первые 88 байт.
Отдельные таблицы `dm_files`, `dm_custody`, `dm_custody_done`, `dm_media_jobs`,
`dm_media_holders` сохраняют состояния и tombstones. Все выданные grants M
учитываются при удалении; повторный STORE не продлевает TTL.
Qt и Android получают историю через события ядра: 41 — conv + JSON сообщений,
45 — JSON бесед. Снимки ограничены 200 записями. Отправка вложения вызывает
`dm_send_file` в uasync; длительная обработка выполняется в `media_async`.
Android копирует выбранный URI и сохраняет полученный файл через `Dispatchers.IO`;
Qt сохраняет файл из контекстного меню в отдельном потоке. Выбор «как файл» сохраняет
оригинал; «как видео» в Qt использует общий FFmpeg worker и метаданные конечного MP4.
Android записывает MP4 через CameraX; импорт MP4 проходит через платформенный
MediaMetadataRetriever в IO. Другие контейнеры на Android отправляются как файлы.
Голосовая запись отдаёт worker собственный PCM либо компрессор без копирования
в GUI/uasync; worker выполняет flush, Opus-кодирование и вычисление waveform.
Кодирование Qt и Android использует один `voice_file`. Адресат фиксируется до
записи, выбора URI и подготовки; завершение не читает текущую беседу GUI.
Подготовленный PM-файл и Android cache удаляются после копирования, ошибки или отмены;
пользовательский исходник Qt остаётся во владении пользователя.
Событие 46 `[target_len:1][target][job_UUID:16][state:1]` сообщает начало (0),
готовность (1) и ошибку (2). Target канала — числовой ID, PM — `dm:<conv_id>`.
Подготовка, доставка и воспроизведение имеют независимые состояния. UI отображает
только задания текущей беседы. Состояния файла и delivery receipt приходят в JSON
41; `content_type`, duration, dimensions и waveform преобразуются в общие роли
сообщения. Канальные скачивания вызываются контроллером только для каналов;
PM-файлы доставляются автоматически через `dm_media`.
Android использует общие `ConversationComposer`/`ConversationMessages` для обеих
бесед; Qt — `InputBar` и `MessageDelegate`. Превью Qt создаются в общем пуле
с двумя workers; декодирование длинного Opus-файла выполняется вне GUI. Оба плеера
проверяют заголовок, пакеты и end marker; Qt ограничивает PCM буфер 256 МиБ.
## Проверки
`test_dm`: симметрия ключа target, CCM round-trip, пустое тело, пределы длины,
неверный ключ/tag, разделение nonce; потоковые файлы на границах 32 КиБ,
неверные author/UUID/hash, обрезание и лишние байты.
`test_dm_e2e`: реальные A–C–B соединения и BGP; нет физического A–B линка.
Проверяет out-of-order, dedup/conflict, подписи, SQL rollback при приёме и enqueue,
ACK чужого тела, отказ mailbox без роли, точное удаление и поздний PUT.
Первый сценарий передаёт 40 offline-сообщений (больше порции pump) и перезапускает источник с pending outbox; второй перезапускает
суперузел и доставляет первое сообщение при выключенном источнике.
`test_dm_media`: реальные A/B/S/M с раздельными ролями supernode и storage;
прямая передача, custody, restart S/M при выключенном A, точный файл на B,
удаление копии и тела S, поздние STORE/STORED, TTL, отказ по квоте, неверная квитанция,
SQL rollback регистрации, состояния JSON и отмена операции с CM handle.
Голосовое передаётся напрямую; метаданные видео сохраняются после offline custody
и restart S/M при выключенном источнике.
`test_dm_worker`: повторное шифрование 8 МиБ не менее 0,5 с и проверка расшифровки;
work выполняется вне uasync, done в его потоке, heartbeat продолжает работать.
Destroy отменяет уже posted completion, callback вызывается ровно один раз.
`test_attachment`: строгий формат, повреждения и граничные длины, реальное
кодирование/декодирование 50 Opus-кадров, длительность и waveform.
`test_attachment_send`: выбранная беседа после смены текущего target, освобождение
PCM в worker, остановка до completion без регистрации и удаление отклонённого cache.
Qt `test_voice_file`: реальные PCM/Opus, повреждённые файлы и отмена async decoder.
Проверка 01.10.2026: чистые сборки Qt и Android APK; полный `check.sh` —
117 PASS, 0 FAIL, 1 SKIP (auto_socket_dynamic требует root), proxy/burst/load PASS.
Qt CTest: 7 PASS, 0 FAIL; Android APK собран после `clean`.
В тесте worker максимальный интервал heartbeat составил 10,3 мс.
Интерактивная проверка GUI на телефоне и десктопе не выполнялась.
+154
View File
@@ -0,0 +1,154 @@
# Зашифрованные вложения группового чата
В desktop chatgui после выбора файла или перетаскивания в чат рядом с его именем
появляется флажок «Зашифровать вложение». Он применяется к выбранному файлу,
изображению или видео. Удаление, замена файла и переход в другую беседу сбрасывают
флажок. Выбор фиксируется вместе с адресатом при отправке, до подготовки.
В DM флажок скрыт: используется существующее автоматическое E2E-шифрование лички.
Android показывает зашифрованное вложение отдельной карточкой со статусом запроса
ключа. После одобрения карточка обновляется на исходный файл, изображение, видео
или голосовое. В интерфейс передаётся только расшифрованный путь; контейнер `.uenc`
нельзя открыть или сохранить как готовое вложение. До расшифровки цитата показывает
название «Зашифрованное вложение», после — обычный preview исходного вложения.
Список чатов сохраняет общее название зашифрованного вложения вместо manifest.
Автор генерирует независимые случайные crypto_UUID и ключ AES-256 на каждую
подготовку. Ключ записывается в локальную SQLite до шифрования. Готовый контейнер
передаётся через обычные media_index/media_delivery. UUID транспортного медиа
и crypto_UUID имеют разные назначения: первый идентифицирует сетевые блоки,
второй — контейнер и ключ. Повтор доставки передаёт прежний контейнер.
## Подписанное сообщение и контейнер
В подписанном сообщении `ct=application/x-utun-encrypted`, базовое имя тела —
`<crypto_UUID:32hex>.uenc`. Остальные поля manifest описывают шифротекст:
размер, SHA256 всего контейнера, UUID и подписи блоков. Ключ, исходное имя,
MIME, waveform и остальные медиаметаданные в историю канала не записываются.
Контейнер:
```text
magic "UTENC001":8
crypto_UUID:16
plaintext_size:8BE
encrypted_metadata_size:2BE
SHA256(encrypted_file_body):32
encrypted_metadata:variable
encrypted_file_body:variable
```
Метаданные содержат MIME в 32 байтах и сериализованный `attachment_info`.
MIME — непустая NUL-строка длиной1..31 байт: ASCII33..126 без двойной кавычки
и обратной косой черты. Общий `encrypted_file_validate_type` применяется в send,
write и read; это проверка представления поля, без полного разбора MIME-грамматики.
Send отклоняет неверное значение до создания задачи/ключа/worker, write — до файловых
операций. Read проверяет расшифрованное аутентифицированное поле и при ошибке сохраняет
прежние info/type. Сырые неверные строки в диагностику не выводятся.
Каждая часть шифруется AES-256-CCM с тегом 16 байт и nonce 13 байт.
Nonce метаданных использует `part=UINT64_MAX`; файл — порции 32768 байт
с номерами от нуля. Общие примитивы находятся в `media_async/media_crypto`;
формат существующего DM при их выделении не менялся.
Получатель проверяет подписанный SHA256 полного контейнера, UUID, tag метаданных,
все tags файла, точный размер и hash тела. Результат пишется во временный файл
и публикуется после успешной проверки и durable rename. Подготовка файлов
и криптография выполняются в media_async worker.
`encrypted_file_write/read` возвращают закрытый проверенный `file_io_temp`,
не изменяя конечное назначение. Задача владеет им до publish/discard; запись идёт
в эксклюзивно созданный соседний файл с правами 0600 на POSIX. Одинаковые файлы,
включая `/./`, hardlink и конечную ссылку назначения, отклоняются до подготовки;
публикация повторно проверяет идентичность. При ошибке read не выдаёт новые метаданные.
Режим `FILE_IO_DURABLE` действует весь срок temp: flush/sync открытого файла/close,
затем rename и сохранение каталога. После успешного rename ошибка directory sync
не откатывает установленный результат; temp уже отсоединён от владельца.
Completion не удаляет файлы по заранее вычисленному имени: очистка учитывает,
какие артефакты задача действительно создала или опубликовала.
Реестр `file_io` защищает активные temp от очистки кеша, включая время после close.
Восстановление до запуска workers и очистка после их завершения удаляют оставшиеся
`<UUID>.uenc.tmp.<UUID>` / `<UUID>.file.tmp.<UUID>` в `media/decrypted`.
Конечные файлы и имена других форматов этот проход сохраняет; активные temp другого
instance того же процесса остаются защищены общим реестром.
## Хранение и разрешения
Локальные таблицы не регистрируются в db_sync или Merkle:
- `attachment_keys`: ключ автора/получателя, связь с общим message_id,
crypto_UUID, hash и пути двух копий, расшифрованные метаданные и состояние.
- `attachment_key_requests`: один запрос на `(channel,message_id,peer)`,
направление, решение и дата создания.
- `attachment_key_outbox`: команда, адресат и время следующей попытки.
Шифротекст хранится в `media/<channel>/<crypto_UUID>.uenc`, локальная открытая
копия — в `decrypted/<channel>/<crypto_UUID>.file`. В media_files и объявлениях
держателей остаётся только шифротекст, включая backfill после restart.
Квота медиакеша учитывает обе копии; очистка кеша не удаляет ключ. При повторном
открытии удалённая открытая копия восстанавливается с сохранённым ключом.
После полной проверенной загрузки обычный клиент автоматически запрашивает
ключ автора. Фоновая копия узла с подписанной ролью `storage=yes` запрос не
создаёт; ручная пользовательская загрузка на таком узле запрашивает доступ.
Сетевая классификация `peers.storage` для этого не используется.
В шапке desktop-клиента кнопка «Запросы расшифровки» видна при наличии запросов без решения
и показывает их число.
Список содержит участника, вложение, дату и отдельные кнопки выдачи/отказа.
Двойной клик переводит к исходному сообщению. После выдачи видны ожидание доставки
и подтверждённая доставка ключа; отказ также сохраняется.
## Адресный протокол
Router-сервис `ETCP_RT_ID_ATTACHMENT_KEYS=0x42`. Каждый пакет обязан иметь
`ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED`; промежуточные узлы ключа не видят.
Протокол не создаёт личную беседу и не публикует служебные сообщения в канал.
```text
magic "AK01":4 | command:1 | message_id:32 | crypto_UUID:16 | cipher_hash:32
GRANT дополнительно содержит key:32
REQUEST=1, REQUEST_ACK=2, GRANT=3, DENY=4, RESULT_ACK=5
```
Автор принимает запрос только на собственное опубликованное сообщение с
сохранённым ключом. Участие запрашивающего проверяется по полному подписанному
мемберу, join и дереву приглашений; удалённые мемберы отклоняются. Проверка
повторяется при решении и перед каждой отправкой GRANT. Получатель принимает
ответ только от автора, по своему существующему запросу и точной идентичности
контейнера. Конфликтующие ключи и результаты отклоняются.
REQUEST_ACK отправляется после сохранения запроса. GRANT/DENY появляются
в outbox только после сохранения решения. Полученный ключ сохраняется до
RESULT_ACK и до запуска расшифровки. Повторы возвращают прежнее решение и не
создают новых строк. Таймер обрабатывает ограниченную порцию очереди с
backpressure и повтором через 5 секунд; restart продолжает очереди.
Для доставки нужен живой маршрут обеих сторон: офлайн-mailbox этого сервиса
не реализован. Запрос ждёт появления автора; решение ждёт появления получателя.
Уже выданный ключ невозможно отозвать у получателя. Защита содержимого не
скрывает автора, канал, время и размер шифротекста. Автоматическая публикация
транскрипции зашифрованного голосового в канал не запускается.
## Проверки
`test_encrypted_file`: границы порций, пустой файл, metadata roundtrip,
неверные ключ/автор/UUID/hash, подмена метаданных с пересчитанным внешним hash,
лишние байты, alias обоих API, сохранность существующего назначения/метаданных,
23 сценария отказов I/O с контролем fd/temp, включая ошибку после rename.
Для MIME проверяются все256 значений первого байта, NULL и границы поля,
восемь ранних отказов writer с сохранением файлов/output, три допустимых roundtrip
и семь аутентифицированных некорректных wire-полей с сохранением info/type/target.
`test_media_primitives` проверяет оба режима temp и границу durable-публикации;
`test_attachment_send` проверяет восемь отказов MIME до ключей/worker/файлов,
затем останавливает сервис после настоящего crypto rename до completion.
`test_encrypted_attachment`: реальные A–S–B/C, скачивание с S при выключенном A,
перезапуски автора с запросами и получателя с ключом, выдача B и отказ C,
отсутствие ключа на S/C, отрицательные control-пакеты, повтор GRANT,
восстановление удалённого plaintext без нового согласия и удаление канала.
При startup/restart дополнительно проверяются оба дерева: stale temp удаляются,
открытый и закрытый активные temp, конечные файлы и чужие имена сохраняются.
+5 -1
View File
@@ -1,4 +1,8 @@
Архитектура etcp:
> Исторический документ: описания NAT, модулей и ограничения обмена маршрутами ниже не отражают текущую реализацию.
> Актуальные правила группового обмена маршрутами — в [topo_group.h](../src/routing_layer/topo_group.h),
> модель узла — в [node_snapshot.md](node_snapshot.md), протокол роутера — в [etcp_router_doc.md](../src/routing_layer/etcp_router_doc.md).
Архитектура etcp:
ETCP устанавливает только одно подключение между двумя узлами. Это подключение может добавлять-убирать каналы связи (динамически) без разрыва соединения.
Также каналы связи мониторятся и трафик распределяется между доступными каналами. Если канал определяется как недоступен - он исключается из обмена.
-10
View File
@@ -1,10 +0,0 @@
В конфиге явно прописываются сокеты с ip и портом.
Через эти сокеты идёт весь трафик, случайные порты клиент не использует - только эти сокеты.
Соответственно, возможен только один линк между двумя пирами по одному каналу.
виртуальные сокеты - плохо т.к. используются ip/port для поиск подключения
./configure --with-openssl - openssl
./configure --without-openssl - tinycrypt
+178
View File
@@ -0,0 +1,178 @@
# Разбор полного прогона 2026-09-27
Исходный результат `./check.sh` вне песочницы: 92 passed, 5 failed, 1 skipped (98 тестов).
Песочница запрещает сетевые сокеты; её результаты не использовались для оценки протокола.
Промежуточный прогон: 96 passed, 1 failed, 1 skipped; оставался `test_chat_join_e2e`.
После KEY_REGISTER_ACK и исправления невыровненных чтений повторный полный
`make check -j4`: **97 passed, 0 failed, 1 skipped (98)**.
Лог: `/tmp/utun-ack-final-check.log`. Исходное падение отдельного root-теста и
последующая доработка описаны ниже. После исправления autosockets общий набор дал
97 PASS / 1 SKIP, а `check-proxy`, `check-burst`, `check-load` прошли:
`/tmp/root-fix-full-check.log`.
`test_etcp_lifecycle`, `test_etcp_link_stress`, `test_conn_mgr_phases` дополнительно
прошли ASan/UBSan/LeakSanitizer. Инструментированы тесты и изменённые транспортные/
маршрутные модули, остальные объекты библиотек взяты из обычной сборки.
Логи: `/tmp/ncd-late-asan.log`, `/tmp/link-role-asan.log`, `/tmp/cm-phases-asan.log`.
## Исправленные причины
- `test_conn_mgr_phases`: после TIMEOUT фазы DIRECT NCD навсегда подавлял UP,
хотя conn_mgr сохранял handle для REVERSE. В логе физический ETCP уже UP,
но conn_mgr продолжал DIRECT_REQ до своего таймаута. TIMEOUT теперь обозначает
истечение срока первой попытки, CLOSED — окончательное завершение. Удерживаемый
handle получает поздний UP. Отдельный lifecycle-регрессионный тест воспроизводит
TIMEOUT → INIT/UP (одно уведомление) → DOWN → UP; до исправления он падал.
- `test_etcp_link_stress`: ожидал reset всей сессии при коллизии дополнительного
линка. Проверка заменена на сохранение эпох, отсутствие reset и дублей линков,
доставку и ACK сообщений через новый линк. Прежний тест забирал сырые фрагменты
вместо normalizer и мог считать служебные пакеты полезным трафиком. Теперь он
использует отдельный ETCP service и проверяет каждый байт и порядок сообщений.
Эта проверка выявила ошибку реализации: при принятии встречного INIT сохранённый
исходящий линк не менял `is_server`. Роль обновляется при отправке INIT_RESPONSE.
- `test_bgp_route_exchange`: после разрыва B–C recovery успешно строил C–A,
нарушая предположение теста о фиксированной цепочке. Теперь A и C принимают
только ключ B: все прежние проверки withdraw/restore сохранены, обход запрещён
условиями тестовой сети. Автоматический обход отдельно проверяет `test_topo_recovery`.
- `test_tcp_io`: адрес 192.0.2.1:81 в окружении устанавливал TCP-соединение,
поэтому connect timeout закономерно не срабатывал. Теперь используется локальный
listener с заполненной accept-очередью, проверяется ETIMEDOUT. Перед уничтожением
uasync тест завершает отложенное освобождение TCP и проверяет баланс таймеров.
Диагностика тестов включается `UTUN_TEST_DEBUG=1`; обычный запуск не включает DEBUG.
Остаточные DATA после удаления соединения могут быть undecryptable и сами по себе
не означают ошибку транспорта.
## Исправлено: гонка регистрации приглашения
`test_chat_join_e2e`, happy path A != C, падал из-за отсутствия подтверждения KEY_REGISTER.
Раньше `chat_invite_build_link()` вызывал `chat_join_register_key()` и сразу возвращал ссылку.
Успех `etcp_route_send()` означает постановку в очередь. Он не подтверждает обработку
ключа посредником C. Параллельное подключение J–C может завершиться раньше доставки A–C.
Доказательство: `/tmp/join-e2e-debug.log`, один ключ `90c4b081833450a0`:
- 01:39:02.437851 — A пишет `join-key registered`, фактически ключ только поставлен в отправку.
- 01:39:02.446527 — C отвергает JOIN_INFO_REQ: ключ ещё неизвестен.
- 01:39:02.449724 — C сохраняет этот ключ, но J уже получил терминальный ERROR.
Теперь C отвечает KEY_REGISTER_ACK только после сохранения ключа. A сопоставляет
channel/target/key и выдаёт ссылку после успешного ACK и локального сохранения ключа.
Отрицательный ACK завершает запрос ошибкой. Чужие, неподписанные, незашифрованные,
повторные и запоздалые подтверждения не могут завершить другой запрос.
Контракт асинхронных API `chat_join_register_key` / `chat_invite_build_link`:
NULL означает ошибку запуска без callback; принятый запрос даёт ровно один callback.
Обычное завершение происходит после возврата API, включая self. Cancel/destroy
завершают запрос синхронно с CANCELLED. Handle недействителен после callback.
Для удалённого узла дедлайн 5 секунд; доставку и повтор потерянных пакетов обеспечивает
надёжный router. Повторная регистрация того же ключа тем же инвайтером обновляет TTL;
замена инвайтера для существующего ключа запрещена. Отмена может оставить ключ на C
до истечения TTL, но A не авторизует по нему вход, поскольку локально такой ключ
сохраняется только при успешном завершении.
GUI получает request_id в событии и игнорирует устаревшие результаты. Новый запрос
отменяет предыдущее ожидание GUI. Headless связывает ответ с JSON id и отменяет все
ожидания отключившегося клиента. Ни один из путей не запускает вложенный event loop.
Дополнительные исправления:
- Устранён double-free при ошибке `etcp_route_send`: пакет уже освобождён роутером.
- Headless прекращает разбор пакета после `quit`; последующая команда не создаёт
запрос на уже закрытом клиенте. При destroy отменяются cleanup_timer и запросы,
удаляются клиентские сокеты и освобождается контекст.
- UBSan обнаружил невыровненное чтение group_id из `d + 1` в chat_sync. Оно и три
аналогичных диагностических чтения заменены на memcpy.
- Тест chat_join теперь останавливает вручную запущенный member_sync;
проверяется баланс выделения/освобождения таймеров.
Проверки:
- `test_chat_join`: 31/31, включая задержанный/отсутствующий ACK, таймаут,
неверные channel/peer/key/flags/размер/status, дубликат, отказ хранения из-за
конфликта инвайтера, отмену, shutdown, параллельные ключи и ошибку отправки.
- `test_chat_join_e2e`: 3/3 (A≠C, A=C, неверный ключ). C принудительно откладывает
регистрацию на 200 мс; проверяется отсутствие ссылки до обработки регистрации.
Дополнительно проверяются подавление старого GUI-запроса и реальный headless
control socket: параллельные ответы, валидность ссылок, quit и команда после него.
- ASan/UBSan: оба теста проходят. LeakSanitizer и баланс таймеров проверены в
test_chat_join; E2E использует fork/_exit, поэтому сам по себе не проверяет утечки
при завершении дочерних процессов. Инструментированы изменённые chat-модули и
transport/routing, остальные объекты взяты из обычной библиотеки.
Логи: `/tmp/utun-ack-join-asan.log`, `/tmp/utun-ack-e2e-asan.log`.
## Отдельный root-тест auto_socket_dynamic
Запуск в `sudo unshare --net` с поднятым loopback закончился TIMEOUT через 30 секунд.
Лог: `/tmp/utun-ack-root-checks.log`. Это не PASS основного набора: в нём тест пропущен.
В конфиге теста включён auto_sockets, но не отключён стандартный фильтр
`auto_socket_skip_no_default_route=1`. Dummy-интерфейсы теста не имеют default route,
а `auto_socket.c:reconcile_iface()` исключает такие интерфейсы. Лог показывает
`no links created`, затем TIMEOUT. Исправленный тест создаёт собственный network
namespace, поднимает loopback и добавляет default route только клиентским dummy.
Фильтр остаётся включённым; отдельно проверяется исключение серверного dummy,
иначе его сокет скрывал бы полный обрыв клиентской сети.
Кроме того, тест сохраняет handle общего дедлайна, traffic_send и traffic_monitor
в одну переменную timeout_handle; фазовые таймеры вообще не сохраняются. При выходе
не отменялись все таймеры; зафиксирован остаток 9 таймеров (665/656). В обработчиках
трафика освобождался entry без queue_dgram_free. Теперь у каждого таймера свой slot,
waiters отменяются до teardown, payload освобождается, deferred cleanup завершается
до проверки баланса таймеров, сокетов и `u_get_allocated_count()`.
Новый генератор шлёт 1024-байтовые пакеты независимо в обе стороны, по одному через
threshold waiter, с бюджетом 64 пакета на tick. Проверяются содержимое, строгий seq,
ограниченность TX/RX очередей, реальное срабатывание backpressure, остановка
генераторов при полном обрыве и полное опустошение после восстановления. NCD handle
удерживает логическую сессию: смена интерфейса не является разрешением терять её данные.
Остатки после действительно удалённого соединения — отдельный допустимый случай.
Прогон нагрузки обнаружил ошибки, скрытые прежними проверками:
- `tcp_socket_add` не сохранял `netif_index`: монитор принимал interface-bound сокет
за сокет default route и менял его адрес при событиях другого интерфейса.
- Удаление TCP модели искало её по имени уже удалённого интерфейса и порту из БД.
Теперь запись интерфейса хранит имя и прямую ссылку на модель; один путь закрывает
listener, линки и модель. Портовые записи удаляются с сохранённым именем.
- `tcp_socket_remove` не закрывал линки, созданные вне NCD. Теперь закрывает все
связанные линки до освобождения модели; root-тест создаёт такой линк отдельно.
- Поиск UDP модели не исключал TCP. Добавлена проверка типа сокета.
- При selective ACK sender мог выдавать новые seq за границей `MAX_INFLIGHT_SIZE`
относительно cumulative ACK. Теперь генерация seq останавливается на границе,
cumulative ACK возобновляет очередь даже при отсутствии уже selectively-acked записей.
Тесты проверяют блокировку, возобновление и uint32 wrap. ACK не может подтвердить
ещё не назначенный seq; декодирование старшего байта seq/ACK не делает signed shift.
- Timestamp Q16 умножался как signed int и переполнялся на циклических/отрицательных
смещениях. Перевод выполняется в uint32. Jitter раньше всегда получал нулевую разницу:
предыдущий RTT читался после перезаписи; теперь берётся до неё, EWMA использует int64.
- UBSan выявил невыровненные canary/указатели в `mem.c` и `memory_pool.c`. Метаданные
читаются/пишутся через memcpy; покрыты размеры блоков 1..17 и повторное использование пула.
- Повторный общий прогон поймал `test_etcp_connect`: PID-порты пересекались с занятым
UDP-портом (`EADDRINUSE`). Теперь сервер bind-ится на порт 0, клиент получает фактический
порт через getsockname; собственного алгоритма поиска свободного порта нет.
Root ASan/UBSan/LSan: PASS, 153335 / 175912 пакетов, reinit=0, все очереди пусты,
баланс памяти/таймеров/сокетов совпал (`/tmp/root-fix-asan.log`). Инструментированы
тест, ETCP/session/router/NCD/autosocket/STCP/normalizer, очереди, event loop и аллокаторы;
прочие объекты архивов обычные. Unit-логи: `/tmp/root-session-asan.log`,
`/tmp/root-memory-asan.log`. LSan запускается вне sandbox: под ptrace он не работает.
Финальная чистая обычная сборка: `./check.sh` — **97 PASS / 0 FAIL / 1 SKIP**;
proxy, burst и load — PASS (`/tmp/root-fix-final-check.log`). Отдельный root-тест —
PASS, 188069 / 191552 пакета, все очереди пусты, reinit=0, баланс ресурсов совпал
(`/tmp/root-test-final.log`). После добавления drain отложенной очистки
`test_etcp_connect` повторно прошёл без остаточных таймеров (`/tmp/root-connect-final.log`).
Подробные socket/connection INFO включаются только через `UTUN_TEST_DEBUG=1`.
`ENETUNREACH` непосредственно при принудительном удалении IP/интерфейса ожидаем до
обработки netlink; тест требует последующей доставки всех данных. Ошибки выхода
за receive window недопустимы и после исправления sender gate не наблюдаются.
## Попутная проблема диагностики
`lib/debug_config.c:debug_parse_config()` требует `:`, хотя документация описывает `=`.
Кроме того, функция всё ещё вычисляет индекс через битовую маску, хотя категории
теперь являются индексами. Исправление в этот набор не включено; тесты используют
`debug_set_category_level()` напрямую.
File diff suppressed because it is too large. Load diff
+353 -101
View File
@@ -1,75 +1,193 @@
Формат кодограммы:
------------------
пакеты идут по следующему пути:
пакет кем-то помещается в входную очередь (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_send попадает в normalizer; его фрагменты поступают в
input_queue, затем в indexed input_send_q с назначенным seq.
Очереди, ACK/RTO, изменение окна и готовность линка вызывают etcp_conn_wake_sender.
Один call_soon обслуживает накопленные события без рекурсивного входа и задержки таймером.
При OOM выделения сетевого пакета очередь сохраняется; retry назначается через 10ms.
close/reset отменяет ожидающий запуск. Не подтверждённая сессия не передаёт DATA/ACK;
HELLO/CHALLENGE/CONFIRM и keepalive передаются своим независимым путём.
Формирование пакета:
1. Просмотреть input_send_q без извлечения: первый готовый повтор, иначе первый готовый новый DATA.
Заблокированная запись пропускается; порядок остальных записей не меняется. Поиск не резервирует окно,
не выдаёт номер попытки и не меняет историю/RR. Если кандидата нет, можно подгрузить один новый фрагмент
и повторить просмотр. Общий бюджет учитывает DATA-байты send_q+wait_ack; приёмное окно по till сохраняется.
После callback подгрузки проверяются reset_count/close/сессия, включая удаление прежнего burst-линка.
2. Выбрать инициализированный UP-линк с готовым shaper и местом для целого DATA:
inflight_bytes + длина DATA <= inflight_lim_bytes. Проверка выполняется без переполнения.
Среди допустимых выбирается минимум inflight_bytes, при равенстве — round-robin.
Для повтора обход начинается после last_link самого фрагмента: сначала неиспользованные допустимые пути.
История — битовая карта256 локальных слотов,32 байта на логический пакет; wire-формат не меняется.
Пока остался неиспользованный путь, занятый по cwnd/pacing, этот пакет ждёт, а send_q просматривается дальше.
Когда все допустимые пути использованы, разрешён следующий обход. Очистка истории и новый бит фиксируются
только после принятия send; отказ send не расходует обход. Перед удалением линка очищаются его курсор/бит
во всех логических записях до освобождения слота и callbacks, которые могут повторно выдать тот же local_link_id.
При наличии незастрявшего initialized+UP пути застрявшие исключены из повторов, даже если альтернатива занята.
Если застряли все, доступны все UP-пути. Прежний путь разрешён лишь при отсутствии других допустимых;
иначе ждём альтернативу. DOWN последней альтернативы пробуждает отправителя.
3. Наступивший по сроку или срочный ACK обслуживает тот же линк, который принял DATA,
без ограничения DATA-cwnd/pacing. Готовый DATA другого линка уступает этому ACK;
DATA того же линка забирает ACK piggyback. Если DATA не помещается, он остаётся в очереди.
Кумулятивный till можно отправить по любому рабочему пути. ACK-only не увеличивает нагрузку DATA-shaper;
физическая очередь канала остаётся общей, её накопление ограничивают DATA-cwnd/pacing BBR.
4. Выделить ETCP_DGRAM до удаления DATA из очереди и изменения счётчиков.
Выделить физическую попытку с новым attempt_no для этого seq и зарезервировать окно выбранного линка.
Только успешное принятие транспортом фиксирует время, last_link, историю/RR и перенос DATA в input_wait_ack.
Ошибка send снимает резерв; логический DATA остаётся готовым без сетевого loss sample.
Пакет содержит ACK, опциональные timestamp/измерительные секции и последнюю секцию PAYLOAD.
5. В активной ограниченной burst-серии pacing обходится; DATA всё равно должен
помещаться в окно, иначе серия отправляет FILLER. ACK/FILLER окно DATA не занимают.
Если готовый повтор относится к другому пути, серия идёт FILLER: новые DATA его не обгоняют.
При приёме пакета (от etcp_connections):
- последовательно сканируем секции и отдаём их на обработку нужным обработчикам:
- ack (0x01): помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также снимаем блокировку отправки
- timestamp (0x06): обновляем last RTT, пересчитываем RTT avg10 и jitter по каждому линку; обновляем recv_dt_avg для раздельного измерения времени передачи/приёма
- [0x00] payload: добавляем пакет в нужное место сборочного linked-list (сверяем по ID если дубликат - игнорируем). также добавляем ACK запись в ack_q для отправки.
- ack (0x01): пара seq+attempt_no подтверждает физическую попытку только принявшего ACK линка;
seq отдельно подтверждает логическую доставку. till освобождает логические записи, без BBR/снятия чужого flight.
- timestamp (0x06): обновляем RTT и EWMA |ΔRTT| пути; recv_dt_avg хранит поправки времени TX/RX в Q16.
У conn rtt_last — среднее RTT линков, rtt_avg_10 — их максимум (имя историческое).
BBR использует отдельные DATA RTT/jitter по ACK физических попыток; RTO также учитывает rtt_last своего линка.
- [0x00] payload: сохраняем фрагмент в recv_q либо распознаём уже сохранённый/доставленный seq.
Только после этого ставим ACK с seq+attempt_no в ack_q принявшего линка; OOM до сохранения не подтверждается.
после сканирования проверяем появились ли в linked-list собранные данные которые можно переместить в выходную очередь. и перемещаем если есть.
пересканирование inflight:
- запускается при добавлении пакета в очередь ожидания ACK (wait_ack_cb). устанавливается таймер на первый неистекший пакет.
- если время последней отправки > RTT_avg10*K1+jitter*K2 (коэффициенты K1=32, K2=32, деление на 16) то пакет заново отправляется
- очередь FIFO (сортирована по timestamp добавления = времени отправки), сканирование прекращается на первом неистекшем пакете
- по истечению таймера (ack_timeout_cb) снова вызывается проверка очереди
Таймер попыток каждого линка (etcp_tx.c):
- RTO = clamp(ceil(1.2*RTT_for_RTO + 1.5*data_jitter_us + 3000), 10000, 1000000) мкс,
RTT_for_RTO=max(data_rtt_us,100*rtt_last): DATA EWMA и последний timestamp/keepalive RTT своего линка.
Целочисленный расчёт использует uint64 и округляет вверх до целой микросекунды.
RTT использует собственный DATA ACK минус receiver_delay; EWMA RTT=1/8, jitter=1/4 |RTT-EWMA|.
Первый DATA RTT задаёт jitter=RTT/2. Timestamp-RTT задаёт нижнюю границу срока, чтобы после роста задержки
попытки доживали до ACK и DATA-измерения продолжались; BBR RTT/min_rtt по этому сигналу не обновляются.
Без обоих измерений RTO=1с.
- FIFO содержит физические попытки в порядке принятия транспортом. Таймер назначается по старейшей;
при изменении RTO проверяется новый срок. Timestamp handler пересчитывает таймер сразу после обновления RTT,
включая сокращение без новых DATA/ACK. Допускается оставить уже запланированную более раннюю проверку.
uasync округляет задержку вверх до миллисекунды; callback может выполниться позже расчётного срока.
- Истечение снимает только собственный физический flight и сообщает loss собственному BBR.
Если логический seq ещё не доставлен и других активных копий нет, он возвращается в input_send_q.
Уже доставленный seq не возвращается, даже если его физическая копия истекла позже.
Обычный ACK-only ждёт received_at_us старейшей записи своего пути + 2000мкс.
Piggyback на готовом DATA передаёт ACK сразу. Готовый ACK может включать более свежие записи:
срок проверяется по голове пакета, а не отдельно для каждой записи внутри него.
Среди готовых обычных ACK выбирается самый старый срок; срочный ответ на ACK_REQUEST имеет приоритет.
Короткая ограниченная измерительная burst-серия завершается до перехода к ACK другого пути.
Продвижение логической границы также задаёт срок 2мс от первого неподтверждённого продвижения;
следующие DATA его не отодвигают. Любой принятый пакет с актуальным till снимает этот запрос.
Один ack_resp_timer соединения обслуживает минимум сроков очередей рабочих путей и логической границы.
После piggyback/частичной отправки срок пересчитывается по оставшимся головам. Новый RX между
timer и отложенным sender не задерживает старые ACK: готовность проверяется по абсолютному времени.
При DOWN очередь пути не создаёт таймер; после UP её старый наступивший срок действует сразу.
OOM ACK-таймера логируется и включает существующий срочный ответ, сохраняя received_at_us для RTT.
Reset/close отменяют таймер и очищают сроки; DATA/RTO/BBR и wire-формат этим механизмом не меняются.
inflight - две очереди (ll_queue):
- input_send_q: список пакетов ожидающих отправку
- input_wait_ack: список неподтвержденных пакетов ожидающих ack
При отправке пакет перемещается из input_send_q в input_wait_ack.
При ретрансмиссии пакет перемещается из input_wait_ack обратно в input_send_q.
При принятии отправки транспортом пакет перемещается из 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) зарезервированы, но на данный момент не реализованы.
- send_hist[8] — local_link_id последних попыток, только диагностика
- last_attempt_id — внутренний составной ключ (seq<<16)|attempt_no последней принятой отправки
- attempt_count — выданные номера попыток данного seq, общие для всех каналов, включая неудачные send
- active_attempts — число ещё учитываемых физических копий
- Payload принадлежит только логической записи. Попытка ETCP_TX_ATTEMPT хранит seq и неизменные снимки, без payload.
inflight_bytes/inflight_packets линка равны сумме DATA-длин/числу записей в его attempts, включая резерв send.
ACK/till может уже удалить логический объект, пока другая физическая копия продолжает занимать своё окно.
Повторный ACK неизвестной/истёкшей попытки не вычитает flight и не создаёт BBR sample.
ACK известного ID с другим seq отклоняется целиком; неизвестный ID допускает только логическое подтверждение seq.
Удаление линка обнуляет last_link в обеих очередях до освобождения его памяти.
Общий optimal_inflight равен сумме лимитов всех линков, с насыщением UINT32_MAX;
это дополнительный backpressure подачи новых фрагментов, не замена индивидуальных окон.
Отдельно ограничиваем размах sequence относительно кумулятивного ACK: новый seq выдаётся
только если (uint32_t)(next_tx_id - rx_ack_till) <= MAX_INFLIGHT_SIZE (16384).
Selective ACK освобождает отдельную inflight-запись, но не продвигает эту границу:
приёмник пока не может освободить пакеты за дыркой. Кумулятивный ACK возобновляет
input_queue даже если подтверждённые записи уже удалены по selective ACK.
При заполненном sequence-окне собственный таймер запрашивает актуальный till секцией ACK_REQUEST.
Получатель отвечает через запрашивающий путь, даже без ожидающих DATA/ACK-элементов.
При продвижении frontier, reset/close таймер отменяется. Это восстанавливает поток при потере последнего till.
Память физических попыток ограничена: 16384 на линк, 32768 на соединение; исчерпание блокирует DATA до освобождения.
Очередь ACK каждого линка ограничена 16384 записями; переполнение логируется, DATA остаётся сохранённым для повтора/till.
ACK ограничивается последним назначенным seq (next_tx_id - 1); сравнения учитывают uint32 wrap.
BBR каждого линка получает ACK/loss samples и возвращает cwnd и pacing rate.
Скорость — доставленные байты за max(интервал отправки, интервал доставки), по снимкам при отправке.
Интервал между соседними ACK не используется: ACK одной пачки могут обрабатываться в один tick.
ACK с подтверждениями также возвращает точку приёма своего пути: накопленные DATA-байты и монотонное время
последнего увеличения счётчика. В счётчик входят допустимые сохранённые/уже доставленные DATA, включая копии;
OOM до сохранения и отвергнутые DATA его не меняют. Считаются байты полезной нагрузки секции, без заголовков.
При отправке попытка запоминает известную точку. ACK действующей попытки продвигает точку пути;
повторный/чужой ID её не меняет, переставленные ACK не откатывают. Противоречивая пара логируется и игнорируется.
При положительных дельтах interval=max(send_us,ceil(delivered*delta_rx_us/delta_rx_bytes)).
Приёмный интервал заменяет ACK clock: обратная очередь больше не завышает BW сжатием ACK
и не занижает его растяжением. Send-интервал сохраняет защиту от сжатия приёмного времени.
Без достоверной приёмной базы/положительных дельт используется max(send_us,ack_us).
Счётчики и времена — uint64, часы двух сторон не сравниваются; нужны только дельты одних часов.
Если интервал не представим в uint32, sample не используется для BW вместо насыщения, завышающего скорость.
После пустого физического flight база начинается заново, чтобы простой не занижал скорость следующей передачи.
Reset потока обнуляет точки приёма; исчерпание локального 64-битного счётчика закрывает соединение с ERROR.
RTT/min_rtt, формула RTO, политика окна и дедлайн ACK не корректируются приёмной точкой.
Внутренняя полоса — байты/мкс Q16; pacing — байты/с, BDP — байты, без повторного множителя MSS.
Минимальное окно BBR — четыре DATA-фрагмента normalizer; максимальное — bbr_max_cwnd (по умолчанию 1048576).
8192 — нижняя допустимая граница настройки bbr_max_cwnd; транспорт не повышает до неё текущее окно.
PROBE_RTT осушает flight до max(4*MSS, 0.5*BDP), выдерживает >=200мс и раунд ACK, затем восстанавливает окно.
RTT-фильтры обновляются при обработке ACK. Короткий минимум принимает меньший достоверный RTT
либо текущее измерение после истечения 5с; RTT=0 означает отсутствие измерения.
Долгий min_rtt принимает короткий минимум, если он не больше прежнего, либо истекли его 10с.
Вместе со значением копируется метка короткого фильтра: повторное использование кеша не задаёт текущее время.
Завершение PROBE_RTT возобновляет короткое окно. Окна истекают строго после своей границы;
hold завершается при достижении срока и завершённом ACK-раунде. Метки RTT и срок hold — полные uint64 TB (100мкс).
MSS — размер DATA-фрагмента normalizer без заголовков; начальное окно UDP и STCP равно 4*MSS с верхним лимитом конфига.
В PROBE_BW действует дополнительный целевой бюджет очереди 20мс: всё окно, включая резервы ACK и PROBE_UP,
ограничивается max(4*MSS, BDP + BW*20мс) по min_rtt. STARTUP/DRAIN не ограничивают окно этой формулой,
но целый раунд RTT выше min_rtt+бюджет завершает STARTUP и переводит его в DRAIN.
Shaper списывает только DATA-байты: единицы совпадают с delivery sample и pacing BBR.
Это цель по оценкам BBR; минимум окна, изменение полосы и очереди обратного пути могут давать большую задержку.
Отказ из-за нехватки окна сохраняет cwnd_limited до следующего ACK: вычитание ACK
не должно скрывать этот факт от BBR. app_limited учитывает ожидающие очереди и
pending/частичный буфер normalizer; ограничение окном не означает отсутствие данных.
Потеря для BBR учитывается при RTO, фактическая повторная передача — при новой попытке.
Три RTO физических DATA-попыток без ACK-прогресса ставят локальный флаг tx_stalled, в том числе в одном вызове таймера.
До установки флага действующий ACK своего пути сбрасывает серию RTO. После установки сброс возможен только по ACK
попытки, принятой транспортом после момента установки флага; старый/чужой/уже снятый attempt не доказывает восстановление.
tx_stalled ограничивает выбор повторов при наличии альтернативы; UP, новые DATA, ACK/keepalive и формула RTO сохраняются.
Reset линка снимает флаг и серию RTO. Новых очередей, копий payload и таймеров детектор не создаёт.
При уменьшении окна уже отправленные пакеты остаются в учёте; новые DATA ждут ACK/RTO.
Измерительные секции burst 0x07/0x08 и FILLER поддерживаются реализацией.
**** Формат кодограмм для etcp.c/h ****
Каждая кодограмма состоит из обязательного заголовка и опциональных секций:
1. Обязательный заголовок всего пакета (3 байта) добавляется при передаче, есть во всех пакетах:
[Timestamp high][Timestamp low][flags/flag_up]
- Timestamp: uint16_t, время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется
[Timestamp:2][flags/flag_up:1]
- Timestamp: uint16_t, время отправки в единицах 0.1 мс (циклическое), при повторе обновляется.
Копируется из памяти в порядке байтов хоста; на используемых little-endian платформах передаётся LE.
- flag_up: uint8_t, bit0 = up/down (recv_keepalive) — признак живой приёмной стороны
шифруется строго ВСЁ включая ВСЕ заголовки (кроме обфусцированного publickey в INIT-пакетах).
timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ (скрыто в шифрованном заголовке).
Итого шифруется: 3 байта заголовка + (data_len - noencrypt_len) байт данных.
2. Опциональные секции (одна или несколько) добавляются в etcp.c (внутри data[], без доп. заголовка):
Для DATA/ACK после этих 3 байт добавляется обязательный заголовок сессии:
[0x09][sender_epoch(8 BE)][receiver_epoch(8 BE)], затем идут секции из etcp.c.
Итого шифруется: 3 + 17 + data_len байт для DATA/ACK;
для остальных кодограмм — 3 + (data_len - noencrypt_len) байт.
Формат и подтверждение эпох описаны в разделе «Подтверждение транспортной сессии» ниже.
2. Опциональные секции (одна или несколько) добавляются в etcp.c внутри DATA/ACK после заголовка сессии:
а) Подтверждения (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 ограничено размером пакета)
[0x01][count][last_delivered_id(4 байта LE)][rx_dup_count(2 байта LE)][[seq(4 LE)][attempt_no(2 LE)][delay_us(4 LE)] × count]
- count: количество ACK записей (1 байт; до 255, дополнительно ограничено MTU и оставшимся местом)
- 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 байт.
- attempt_no: uint16_t — номер физической попытки данного seq, 1..65535; возвращается только тем же путём
- delay_us: uint32_t — задержка между приёмом DATA и формированием ACK, мкс; UINT32_MAX исключает RTT sample
При count>0 после всех записей обязательно добавляется [rx_data_bytes(8 LE)][rx_data_time_us(8 LE)].
Это точка приёма всего пути на момент формирования ACK, а не отдельной подтверждаемой попытки.
Время — монотонные микросекунды получателя при последнем положительном приросте DATA-байтов; 0/0 — точки нет.
Каждая запись = 10 байт, база ACK = 8 байт; полный размер = 8+10*count+(count>0 ? 16 : 0).
count=0 допустим для передачи till, точки приёма не содержит. До обработки проверяется вся длина ACK.
ACK_REQUEST: [0x0a], одна секция длиной 1 байт, запрос повторной отправки актуального till.
в) channel timestamp (добавляется после выбора канала передачи, если есть свежие данные):
[0x06] [RET_Timestamp(2 байта LE)] [RECV_Rel_Timestamp(2 байта LE)]
- RET_Timestamp: uint16_t = last_recv_timestamp + (now - last_recv_local_time)
@@ -77,12 +195,20 @@ Burst-измерения bandwidth (0x07, 0x08) зарезервированы,
- RECV_Rel_Timestamp: uint16_t = last_recv_local_time - last_recv_timestamp
(относительное смещение между локальным и удалённым временем)
Всего 5 байт. Добавляется только если last_recv_updated=1 (был принят пакет с момента последней отправки)
и если прошло не более ~100ms (dt < 1000000 таймбаз).
и если прошло менее 100с (dt < 1000000 таймбаз). Результат rtt_last задаёт нижнюю границу RTT для RTO;
DATA RTT и min_rtt BBR обновляются только ACK действующих физических попыток.
3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя):
[0x00] [ID 4 байта LE] [данные...]
- ID: uint32_t, 32-битный циклический номер пакета
[0x00] [ID 4 байта LE] [attempt_no 2 байта LE] [данные...]
- ID: uint32_t, номер фрагмента 1..UINT32_MAX в пределах одной эпохи потока.
- attempt_no: uint16_t, номер 1..65535, общий для всех каналов одного ID.
Номер выдаётся при создании попытки и не возвращается при отказе send; исходный seq при повторе сохраняется.
После номера65535 следующая попытка аварийно меняет эпоху через REINIT; счётчик не оборачивается.
Перед выдачей seq=0 после UINT32_MAX поток также меняет эпоху: пара seq/attempt_no не повторяется внутри эпохи.
Смена эпохи отбрасывает прежний поток, владельцы восстанавливают свои операции по REINIT, как при фатальном OOM.
Старые DATA/ACK отклоняются заголовком сессии до разбора. Заголовок PAYLOAD занимает7 байт.
Внутренние хеш-очереди сохраняют64-битный ключ (seq<<16)|attempt_no; он не передаётся по сети целиком.
Пакет может содержать несколько опциональных секций (например, ACK + channel_timestamp + данные),
всегда в порядке: ACK, channel_timestamp, payload; payload всегда последний.
Порядок: ACK, возможный ACK_REQUEST, измерительные/временные секции и FILLER, затем payload последним.
Разбор секций — последовательное сканирование с известными размерами (нет явного поля длины секции).
цель протокола:
@@ -93,97 +219,223 @@ Burst-измерения bandwidth (0x07, 0x08) зарезервированы,
При прохождении трафика через этот модуль подсчитываются метрики (для каждого канала отдельно):
- RTT последнего пакета и время отправки последнего пакета
- общее количество отправленных/принятых байт
- количество перезапросов (по каждому каналу отдельно) в пакетах. когда etcp модуль принял запрос ретрансмиссии он смотрит с какого интерфейса был отправлен этот пакет (это запоминается при отправке) и увеличивает счетчики.
- число фактических повторных отправок; потери для BBR учитываются отдельно при истечении физических попыток.
- обновление текущего количества неподтвержденных данных 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.
Окно и pacing проверяются до извлечения DATA из очереди; без допустимого UP-линка фрагмент остаётся в очереди.
У каждого линка собственный shaper по точной bbr_pacing_rate в байтах/с:
- После успешного send списываются только payload_len DATA-байтов, без заголовков и ACK.
- Основной счётчик времени имеет единицу 0.1 мс; sub-разряды — 0.1 нс, модуль 1000000.
Прибавка равна ceil(payload_len * 10000000000 / bbr_pacing_rate), вычисляется целочисленно в uint64.
- Накопленный кредит после простоя ограничивается 1 мс. При смене ставки пересчитывается оставшийся долг;
таймер снимается и при необходимости назначается заново. При нулевой ставке pacing не ограничивает отправку.
- Положительный долг должен быть строго меньше 1 мс. Таймер ждёт первого допустимого TB:
shaper_load_time_tb - SHAPER_BURST_DELAY_TB + 1; дробь меньше TB не меняет эту строгую границу.
uasync округляет срок вверх до миллисекунды; callback повторно проверяет готовность перед уведомлением.
- ACK/control и измерительная burst-серия не списывают DATA-pacing. Окно DATA проверяется и в burst.
Конструктор исходящего UDP-линка запускает INIT сразу; TCP использует отдельный STCP connect/handshake.
процесс установки соединения (рукопожатие):
- клиент отправляет init запрос и выставляет таймаут
- по таймауту повторяет init запросы с экспоненциально растущим интервалом (начальный 500ms, макс 50s, +25% каждые 10 попыток), ведется счетчик запросов init.
- по таймауту повторяет INIT: начальный интервал 500 TB = 50 мс, максимум 50000 TB = 5 с;
каждые 10 отправок интервал увеличивается на interval/4 + 1 TB. TB здесь равен 0.1 мс.
- когда клиент получает 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).
- каждая сторона сообщает в INIT собственный reset_id (64 бита, BE); ответ не повторяет эпоху запроса.
- первая неизвестная эпоха пира сохраняется из handshake. Смена уже известной эпохи запускает challenge;
только подходящий CONFIRM сбрасывает поток и заменяет эпоху пира, сохраняя собственную.
- повтор INIT и коллизия встречных подключений сами по себе не сбрасывают поток.
При добавлении нового канала: если link_state=1 (handshake) — посылаем INIT_REQUEST (0x02);
если link_state=2 (reconnect) — INIT_REQUEST_NOINIT (0x04). Коды INIT не заменяют подтверждение смены эпохи.
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 (подтверждение без сброса)
0x02 — ETCP_INIT_REQUEST (начальный handshake линка)
0x03 — ETCP_INIT_RESPONSE (вариант ответа INIT)
0x04 — ETCP_INIT_REQUEST_NOINIT (восстановление линка, также разрешение коллизии)
0x05 — ETCP_INIT_RESPONSE_NOINIT (вариант ответа INIT)
0x06 — ETCP_PING (проверка доступности)
0x07 — ETCP_PONG (ответ на пинг)
0x08 — ETCP_KEEPALIVE
0x09 — ETCP_SESSION_DATA (обёртка секций DATA/ACK с эпохами)
0x0a — ETCP_SESSION_HELLO
0x0b — ETCP_SESSION_CHALLENGE
0x0c — ETCP_SESSION_CONFIRM
0x0d — ETCP_TCP_ADDRESS (STCP: code + family(4/6) + observed IP(16), без порта)
Варианты ответа 0x03/0x05 не определяют смену эпохи: клиент в обоих случаях обрабатывает reset_id одинаково.
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)]
[code(1)] [node_id(8 BE)] [reset_id(8 BE)] [mtu(2 BE)] [keepalive(2 BE)]
[recovery(2 BE)] [link_id(1)] [socket_id(1)] [only_local(1)] [type(1)]
[src_ipv4(4)] [src_port(2 BE)] [collision(1)] [ed25519_pubkey(32)] [device_type(1)]
[random_padding...] [salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
- code: 0x02 (со сбросом) или 0x04 (без сброса)
Фиксированное тело до padding — 67 байт (ETCP_INIT_REQUEST_PKT).
- code: 0x02 или 0x04, вариант handshake линка
- 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 сокета отправителя
- reset_id: uint64_t, собственная локальная эпоха потока клиента (big-endian)
- mtu: uint16_t локальный MTU
- keepalive: uint16_t интервал keepalive в мс
- recovery: uint16_t = link->recovery_interval/100 (исходная единица — 0.1 мс)
- link_id: uint8_t (1-255), назначается отправителем; ноль запрещён
- socket_id: uint8_t, уникальный ID сокета отправителя
- only_local: uint8_t, флаг "только локальные подключения"
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_UNKNOWN=0/PUBLIC=1/NAT=2/PRIVATE=3/LOCAL=4)
- type: uint8_t, конфигурационный тип сокета CFG_SERVER_TYPE_*
- src_ipv4/src_port: адрес интерфейса отправителя; для IPv6 src_ipv4=0, порт сохраняется;
при SOCKS5 оба поля нулевые. Это заявленный адрес, а не наблюдение внешнего NAT-адреса.
- collision: 1 — отправитель разрешает встречное подключение, заявляя роль master для линка
- ed25519_pubkey: публичный ключ Ed25519 отправителя, 32 байта
- device_type: CLIENT_TYPE_SERVER/DESKTOP/MOBILE
- random_padding: случайные байты до размера handshake (защита от fingerprinting)
- salt + obfuscated_pubkey: публичный ключ с обфускацией (не шифруется AES,
но скрывается XOR с salt и собственным публичным ключом). Передаётся в конце пакета
- salt + obfuscated_pubkey: ключ отправителя XOR SHA256(salt || публичный ключ получателя).
Этот блок не шифруется AES, передаётся в конце пакета
в noencrypt_len байтах (не попадает под AES-шифрование)
Инициирует новый connection для tcp instance. Если tcp instance нет (первое подключение) - создаёт.
Между нодами только одно подключение, но можно добавлять каналы.
INIT устанавливает UDP-линк: создаёт или переиспользует общий ETCP_CONN к пиру.
Между узлами одно ETCP-соединение с несколькими линками; TCP-линк использует STCP handshake.
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 (без сброса)
[code(1)] [node_id(8 BE)] [reset_id(8 BE)] [mtu(2 BE)] [link_id(1)]
[remote_socket_id(1)] [only_local(1)] [type(1)] [peer_ipv4(4)] [peer_port(2 BE)]
[ed25519_pubkey(32)] [device_type(1)] [keepalive(2 BE)] [random_padding...]
Фиксированное тело до padding — 64 байта (ETCP_INIT_RESPONSE_PKT).
- code: 0x03 или 0x05, вариант ответа INIT; сам по себе не сбрасывает поток клиента
- 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 (копируется из запроса)
- reset_id: uint64_t, собственная локальная эпоха потока сервера (big-endian), не эхо запроса
- mtu: uint16_t локальный MTU сервера
- link_id: uint8_t идентификатор канала сервера, 1..255
- remote_socket_id: uint8_t (ID серверного сокета; ID клиентского сокета известен серверу из запроса)
- only_local: uint8_t флаг сервера
- type: uint8_t тип сокета сервера
- peer_ipv4: 4 байта (IPv4 адрес клиента как его видит сервер — для NAT traversal)
- peer_port: 2 байта (порт клиента как его видит сервер)
- peer_ipv4: 4 байта (IPv4 адрес клиента как его видит сервер, в сетевом порядке)
- peer_port: uint16_t (порт клиента как его видит сервер, big-endian)
При IPv6 оба поля нулевые.
- ed25519_pubkey: публичный ключ Ed25519 сервера, 32 байта
- device_type: CLIENT_TYPE_SERVER/DESKTOP/MOBILE
- keepalive: uint16_t интервал keepalive сервера в мс
- random_padding: случайные байты
3) PING (0x06):
[0x06] [node_id(8 BE)] [nonce(8 BE)] [user_data_len(2 BE)] [user_data...]
[0x06] [flags(1)] [node_id(8 BE)] [nonce(8 BE)] [user_data_len(2 BE)] [user_data...] [padding...]
[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...]
[0x07] [flags(1)] [node_id(8 BE)] [nonce(8 BE)] [resp_data_len(2 BE)] [response_data...] [padding...]
[salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
Ответ на PING. nonce копируется из запроса для сопоставления.
Nonce генерируется CSPRNG. Проверяются также ключ отправителя, source IP:port,
ID и поколение принимающего сокета. Флаг OBSERVE (0x04) запрашивает ответ
address(19) || tag(25): family(4/6), IP(16), port(2, BE), NAT2UDP!(8),
socket_id(1), generation(8, BE), round(8, BE).
ADDRESS_CHECK (0x08) — аутентифицированное входящее испытание endpoint.
Модель NAT и алгоритм владельца: doc/nat_detection.txt.
При получении 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
До изменения транспорта INIT_REQUEST проверяет полное 67-байтовое тело и соответствие node_id
аутентифицированному X25519-ключу. Оба направления INIT отклоняют link_id=0 и MTU меньше
ETCP_MIN_UDP_MTU(family): 191 байт для IPv4, 211 для IPv6. MTU больше 1500 ограничивается сверху.
Получатель создаёт или переиспользует ETCP_CONN и подходящий линк;
для существующего соединения проверяет совпадение ключа пира. Эпоха из INIT учитывается
через etcp_conn_apply_peer_reset_id, без немедленного сброса потока при смене известной эпохи.
Принятый INIT обновляет recv_keepalive, время последнего RX и входной remote_keepalive=flag_up
до переиспользования буфера для ответа. INIT с flag_up=0 снимает прежний UP UDP-линка:
получение запроса доказывает только входящую достижимость, обратный путь ещё не подтверждён.
Ответ и keepalive отправляются и при DOWN, чтобы холодное подключение и восстановление
могли завершить двустороннюю проверку. При DOWN одного линка соединение остаётся UP,
если есть другой работоспособный линк. Повтор неизменного состояния не создаёт нового DOWN/UP.
INIT_RESPONSE должен содержать полное обязательное тело (64 байта без timestamp/flag_up)
и node_id, соответствующий аутентифицированному ключу пира. Усечённый ответ или чужой node_id
отклоняются до изменения живости, эпохи и параметров линка. Дополнительный padding разрешён.
Входящий conn с известным ключом индексируется по node_id до callback создания линка.
Отмена входящего линка из callback не закрывает conn, если у него появился другой линк или NCD-владелец.
Close немедленно отключает линк и ресурсы, но память удерживается active_users до выхода внутренних стеков RX/переходов/callbacks.
Приём UDP: uasync -> etcp_connections_read_callback_socket -> etcp_process_packet ->
etcp_packet_decrypted (проверки и снятие SESSION_DATA) -> etcp_conn_input -> normalizer/output_queue.
Keepalive: пакет без секций (только timestamp+flag_up в шифрованном заголовке, data_len=0).
keepalive пакеты шлют и клиент и сервер с заданным интервалом если нет полезного трафика.
Keepalive: [0x08] [flags(1)] [period_ms(2 LE)] [sleep_fields?:4] [padding...].
При KA_FLAG_SLEEP добавляются active_remaining(2 LE) и sleep_duration(2 LE), оба в единицах 0.1 с.
Полезное тело занимает 4 байта без SLEEP или 8 байт с SLEEP; остальные байты — padding.
Обычные keepalive периодически шлют обе стороны; полезный TX сбрасывает адаптивный период к базовому.
Сон одной из сторон останавливает обычный таймер. UDP-анонс сна подтверждается флагом SLEEP_ACK,
повторяется до ACK/исчерпания попыток; TCP отдельного подтверждения сна не требует.
RX watchdog работает отдельно в normal и standby и не продлевается собственными отправками.
Сервер прекращает слать keepalive если линк потерян (ждёт keepalive от клиента).
flag_up в заголовке устанавливается в значение recv_keepalive (признак что локальная сторона принимает пакеты).
Padding keepalive, PING/PONG и STCP-handshake:
- ETCP_CONN хранит собственный диапазон min/max и независимый таймер для всех своих линков.
- UTUN_INSTANCE хранит отдельный диапазон и таймер для PING/PONG без существующего ETCP_CONN.
- При создании владельца и каждом обновлении: min=rand(0,30), max=min+rand(32,128), границы включительные.
- Следующее обновление назначается через rand(50,300) секунд. При закрытии владельца таймер отменяется.
- Каждый пакет независимо выбирает длину rand(min,max); padding заполняется нулями ДО шифрования.
- UDP PING/PONG: padding шифруется перед CRC/tag; открытый salt+pubkey остаётся в конце датаграммы.
user_data_len/resp_data_len обозначают только полезные данные. PONG не копирует padding запроса.
- STCP: salt(8)+pubkey(32) открыты, затем 51 байт зашифрованного handshake и padding_size байт
нулевого padding, зашифрованного продолжением того же CTR. Приём продвигает CTR через padding;
padding_size=0 сразу завершает handshake. При ETCP_CONN клиент читает актуальный диапазон при отправке;
без ETCP_CONN сохраняет выбранную длину при запуске. Сервер выбирает длину независимо по пиру/instance.
- Каждый STCP-поток использует соль своего отправителя: клиентскую для client→server, серверную для server→client.
CTR IV = первые 12 байт SHA256(session_key || stream_id_LE || salt(8) || "uTun3-stream") + четыре нулевых байта.
Отправка инициализируется после генерации своей соли, приём — после чтения соли пира, до расшифровки тела.
После handshake CTR непрерывно продолжается через padding и DATA, без повторной инициализации на кадр.
- Padding не должен превышать 158 байт. Для UDP длина ограничивается оставшимся MTU;
если не помещается даже min, отправка завершается ошибкой без обрезания пользовательских данных.
- Диагностика диапазонов/обновлений/PING/PONG: etcp=debug; keepalive: keepalive=debug.
Клиент: если все линки =down то начинается процедура восстановления связи:
- клиент посылает init без сброса (0x04), link_state=2
- сервер как получает init проверяет session_id:
- если session_id совпадает — шлёт подтверждение без сброса (0x05)
- если session_id изменился (клиент перезапустился) — шлёт подтверждение со сбросом (0x03)
- сервер восстанавливает физический линк и отвечает INIT_RESPONSE/INIT_RESPONSE_NOINIT со своей эпохой.
- прежняя эпоха пира не меняет поток; смена известной эпохи подтверждается через CHALLENGE/CONFIRM.
Код ответа 0x03/0x05 не является подтверждением смены эпохи.
**** Подтверждение транспортной сессии (etcp_session.c/h) ****
Physical UDP INIT / STCP handshake establishes an authenticated link. A stream reset
uses the same control protocol on every initialized UDP or TCP link, bypassing the
normalizer and reliable DATA/ACK queues. A stream reset does not generate physical DOWN/UP.
Encrypted DATA/ACK plaintext after timestamp(2) + flag_up(1):
09 | sender_epoch:u64be | receiver_epoch:u64be | existing ACK/DATA sections
The extra 17 bytes participate in the MTU budget. Unwrapped DATA/ACK and frames with
an epoch mismatch are discarded before stream state or link liveness is updated.
Control frames (all 25 bytes after timestamp/flag):
0a HELLO | sender_epoch | receiver_epoch | 0
0b CHALLENGE | sender_epoch | receiver_epoch | random_cookie:u64be
0c CONFIRM | sender_epoch | receiver_epoch | echoed_cookie:u64be
Each connection owns one pending peer epoch, fresh challenge, and retry timer.
The first nonzero peer epoch from a physical handshake is stored directly if unknown.
HELLO and a change to an already known epoch advertised by INIT only request a challenge. Only a
CONFIRM for the current local epoch and pending random challenge commits a peer
epoch. Changing the peer epoch resets the stream but preserves our own epoch.
A local fatal reset generates a new local epoch and blocks DATA until confirmation.
Both sides may reset concurrently. Loss of any control frame is recovered by a
500 ms retry on all initialized links. After 10 s an unconfirmed local reset closes
the connection; an unconfirmed candidate alone is discarded without closing a
working session. Close cancels the timer before destroying links.
TCP may initially receive peer epoch zero because the server creates ETCP_CONN
only after STCP handshake. It completes the same session confirmation before INIT/UP.
REINIT notifies stream consumers without tearing down physical ownership. The
router retains end-to-end inflight; BGP repeats JOIN_GROUP and TABLE_REQUEST because
its previous control messages may have been discarded by the stream reset.
Отказы памяти и прерывание потока:
- OOM до сохранения RX DATA в recv_q не создаёт ACK; отправитель повторяет DATA.
- ACK означает принятие в текущий транспортный поток, а успешный etcp_send —
передачу владения транспорту. Оба не гарантируют завершение операции сервиса
после прерывания сессии. Надёжность операции через REINIT обеспечивает её владелец.
- OOM после принятия данных в normalizer или при подготовке TX inflight вызывает
fatal REINIT: новая локальная эпоха, блокировка DATA, отбрасывание старых очередей,
частичных кодограмм и физических попыток. Старые payload не воспроизводятся.
Физические линки сохраняются; очистка очередей/reset не выделяет память.
- Активный обработчик освобождает собственный извлечённый фрагмент ровно один раз;
после reset/close из callback он прекращает разбор старой кодограммы/пакета.
- Согласование новой эпохи выполняется после очистки. OOM control-пакета оставляет
повтор активным, без продления исходного срока 10с. Отказ обязательного session-
таймера или генерации локальной эпохи закрывает соединение с диагностикой.
Если невозможно выделить даже обязательный deferred-close token внутри callback,
процесс завершается через abort: соединение не оставляется в зависшем состоянии.
- После подтверждения эпох владельцы получают REINIT и восстанавливают операции
своего протокола; при невозможности согласования соединение закрывается.
+126 -301
View File
@@ -1,324 +1,149 @@
# ETCP Router Architecture
# Архитектура ETCP Router
## Структуры данных
Маршрутизатор связывает сервис с удалённым узлом через группу, не привязывая логическую
сессию к одному физическому ETCP. Ключ канала — `(group_id, remote_node_id, svc_id)`.
Wire-формат и сервисный API подробно описаны в [etcp_router_doc.md](../src/routing_layer/etcp_router_doc.md),
объявления — в [etcp_router.h](../src/routing_layer/etcp_router.h).
```
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
```mermaid
flowchart TD
Producer["Сервис: payload + mode"] --> Send["send_q: plaintext, seq, mode"]
Send --> Session{"peer_reset_id подтверждён?"}
Session -- нет --> Hello["HELLO / challenge / confirm"]
Hello --> Session
Session -- да --> Route["Текущий маршрут: conn_mgr → BGP, либо прямой глобальный ETCP"]
Route --> Wait["Waiter физической send_input_q"]
Wait --> Encode["route_crypto_encode: encrypt → sign"]
Encode --> Inflight["inflight_q: неизменяемое тело без visited-list"]
Inflight --> Path["Добавить source ID"]
Path --> ETCP["etcp_send / normalizer"]
Inflight -- RTO 300 ms --> Wait
ETCP --> Network["Сеть / транзитные узлы"]
Network --> Check["Проверить visited-list"]
Check --> Destination{"dst=self?"}
Destination -- нет --> Transit["Добавить self один раз, BGP next hop / transit FIFO"]
Transit --> Network
Destination -- да --> Decode["Crypto policy + verify/decrypt + проверка эпох"]
Decode --> Incoming["incoming_q: plaintext"]
Incoming --> Reorder["recv_q: reorder по seq"]
Reorder --> Ready{"receive_ready разрешает?"}
Ready -- нет --> Busy["Сохранить пакет, ACK RECEIVE_BUSY"]
Busy --> Resume["resume_receive: отложенное продолжение"]
Resume --> Reorder
Ready -- да --> Callback["Callback сервиса получает entry"]
Callback --> Ack["rx_seq++ → ACK текущей границы"]
Ack --> Release["Отправитель снимает подтверждённый inflight"]
```
## Send Path (отправка данных)
Каждая кодограмма имеет самостоятельный seq; это не байтовый поток.
send_q ограничена 64 пакетами при обычной отправке, `force` обходит этот порог.
Inflight по умолчанию — 256, при minRTT probe — 4. Получатель ограничивает reorder
окном max_inflight и суммарным размером очередей max_inflight+1 записей.
```
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 │
└────────────────────┘
Все события выполняются в uasync-потоке. Штатный normalizer откладывает physical waiters,
а callbacks производителей send_q могут выполняться синхронно при освобождении места.
Поэтому код отправки повторно проверяет состояние/эпохи после callback.
## Эпохи логического канала
Полный сетевой пакет:
`[57-byte SVC_ROUTE_HDR][payload][signature?][visited_ids:8*N][count:1]`
Числа в заголовке — native endian. flags — отдельный байт; биты seq не заняты флагами.
Заголовок включает group/src/dst/service, seq/timestamp и поля reset_id/peer_reset_id/challenge.
visited-list содержит 1..64 ID, находится вне подписи/шифрования и проверяется на каждом переходе.
Первичное согласование двух сторон имеет вид:
```mermaid
sequenceDiagram
participant A as Узел A, local=a
participant B as Узел B, local=b
A->>B: HELLO(reset=a, peer=0)
B->>A: CHALLENGE(reset=b, peer=a, cB)
A->>B: CONFIRM(reset=a, peer=b, cB)
A->>B: CHALLENGE(reset=a, peer=b, cA)
B->>A: CONFIRM(reset=b, peer=a, cA)
Note over A,B: Каждый узел принимает peer ID только после ответа на свой pending challenge
```
## Receive Path (приём данных)
Получение HELLO/CHALLENGE с неизвестной эпохой само по себе не сбрасывает работающий канал.
Переставленные/старые CONFIRM не совпадают с pending ID/challenge и игнорируются.
Новые challenge ограничены интервалом 300 ms; первичный HELLO повторяется без отдельного timeout.
```
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) если │
│ ещё не запущен │
└────────────────────────────────────┘
```
| Событие | Что сохраняется | Что меняется |
| --- | --- | --- |
| Смена ETCP/next hop | Логические эпохи, seq, очереди | Физический waiter/маршрут |
| Первое подтверждение peer | Накопленная send_q | Заполняется peer_reset_id, начинается отправка |
| Подтверждённый рестарт peer | Собственный reset_id | Очереди, seq, RTT/метрики; уведомление сервиса |
| Явный conn_restart | Ключ group/peer/service и адрес rconn | Новый reset_id, пустое состояние; затем callback и HELLO |
| conn_close | Только данные, переданные во владение callback | Запись снимается с реестра, ресурсы освобождаются |
```
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 │
└────────────────────────────────────────┘
```
Сервис возобновляет прикладную синхронизацию после сброса; старые гарантии доставки через reset не продолжаются.
Готовность router означает наличие подтверждённого peer ID. Это отдельное состояние
от физического UP и READY BGP-группы.
## 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 │
└─────────────────────────────────────────┘
DATA проходят crypto policy и verify/decrypt до изменения reorder/rx_seq.
Сервис получает `[svc:1][src:8][dst:8][rx_flags:1][group:8][payload]`, всего 26 байт заголовка.
Его callback владеет entry, ACK продвигается после успешной передачи callback.
router_send_ack(rconn):
hdr.seq = rx_seq
hdr.svc_id = svc_id
pl_len = 0 → это ACK-пакет
etcp_send(conn, entry)
```
ACK подтверждает только диапазон до tx_sent, в модульном порядке uint32.
При продвижении снимаются существующие inflight-записи и один раз учитываются исходные payload bytes.
```
Обработка входящего 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) │
└────────────────────────────────────────┘
```
RECEIVE_BUSY не подтверждает отложенные DATA, но поддерживает живость ожидающего диска/буфера сервиса.
Даже при прежнем seq допустимый BUSY обновляет отсчёт RTO и останавливает новую отправку/проход ретрансмитов.
Обычный повторный ACK без продвижения этого не делает. Снятие BUSY возобновляет send_q.
## Inflight Control
receive_ready — предикат без I/O и изменения/закрытия роутера. При 0 пакет сохраняется в recv_q;
resume_receive планирует продолжение. Управляющий CLOSE не проходит через этот предикат.
```
ROUTER_MAX_INFLIGHT = 256
| Механизм | Интервал/условие |
| --- | --- |
| ACK | Не чаще 10 ms; текущая граница повторяется при дубликате DATA после потери ACK |
| RECEIVE_BUSY | Около 100 ms, пока сервис остановлен |
| Idle ACK | 500 ms, только если rx_seq ещё не был отправлен в ACK |
| Handshake/RTO | 300 ms |
| Нет маршрута после handshake | Повторный поиск через 20 ms |
| Транзит при backpressure | Waiter + повтор выбора маршрута через 20 ms |
| Watchdog send_q | 500 ms, пока очередь непуста |
| Нет прогресса/BUSY | Закрытие после 17 циклов, для сервиса CALL — после 67 |
Отправка возможна когда: tx_seq - tx_acked < 256
Отправка заблокирована: tx_seq - tx_acked >= 256
Транзитные очереди принадлежат физическому ETCP, сгруппированы по group/src/dst и не имеют своих seq.
При смене маршрута пакет переносится на актуальный next hop без ожидания освобождения старой очереди.
Отсутствие пути приводит к потере транзитной копии; её восстанавливает end-to-end inflight источника.
При блокировке:
данные → 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()
| Объект | Владение |
| --- | --- |
| Входной entry etcp_route_send | Всегда поглощается, включая ошибку |
| Буфер etcp_router_conn_send | Заимствован на время копирования |
| ETCP_ROUTER_CONN из conn_get | Заимствован до close/destroy |
| Сервисный entry | Передаётся callback; освободить dgram и entry |
| Физический conn в callback | Заимствован, может обозначать транзитного пира |
| Синтетический conn | Только на время callback, instance/peer=self; без сетевого send/ref_take |
| Send-ready handle/arg | У производителя до callback/отмены; перед освобождением отменить |
send_q полностью разобран → send_blocked = 0
```
Self-доставка через etcp_route_send синхронна и не создаёт канал. Она обходит сетевые crypto policy
и receive_ready, выдаёт rx_flags=0; режим проверяется на допустимые биты.
Payload должен быть непустым и помещаться вместе с 26-байтным заголовком в uint16 длину.
Нет обработчика, переполнение размера или отказ памяти возвращают -1.
Отдельный синтетический conn каждого вызова сохраняет контекст при вложенных доставках разных ядер.
## Полная схема потоков
Закрываемый канал удаляется из реестра до callback. Callback может закрывать группу/другие каналы
или открыть замену с тем же ключом; старая запись не возвращается повторным поиском.
Память канала и send_q освобождается отложенно за пределами текущего callback.
До массовой остановки сервисы отменяют producers/waiters; уничтожение ядра из callback останавливаемого сервиса запрещено.
```
┌──────────┐
│ Сервис │ (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 │──▶ сеть
└─────────────┘
close_group удаляет логические каналы и pending transit указанной группы.
destroy снимает wire callback, закрывает все логические каналы и очищает транзитные очереди,
таймеры и waiters на всех живых ETCP. Уничтожать сам ETCP для очистки роутера не требуется.
──▶ 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 │
└──────────────────────────────────────────────────┘
```
Метрики delivery считают первые подтверждения исходного payload, без заголовков/crypto/ретрансмитов.
RTT — 0.1 ms, snapshot API выдаёт микросекунды; jitter — unsigned 16.16 EWMA,
монитор публикует удвоенный EWMA. Очереди, метрики и параметры minRTT сбрасываются вместе с логической сессией.
+81
View File
@@ -0,0 +1,81 @@
Аватарка CHAT-группы
===================
Модуль src/chat/group_avatar.c. Картинка — бинарный BLOB до 16384 байт в общей
SQLite каждого участника: channels.avatar_data/version/hash/signature.
Storage-сервер, каталоги файлов и пользовательские квоты не используются.
При старте добавляются отсутствующие колонки одной транзакцией, записи/ключи
каналов сохраняются. channel_put использует UPSERT и сохраняет аватарку,
UI-состояние и локальные приватные ключи; смена публичных ключей существующего
channel_id запрещена. Удаление группы удаляет и её аватарку.
Право публикации — приватный Ed25519 ключ группы плюс собственное действующее
членство. creator UID/admin-флаг не заменяют ключ. group_avatar_set подписывает:
ASCII "group-avatar-v1" без NUL, group:u64 LE, version:u64 LE, size:u16 LE, bytes.
Версия — max(NTP microseconds/1000, previous+1), ограничена INT64_MAX.
SHA256 считается по картинке; при одинаковой версии побеждает больший SHA256.
Обе конкурирующие записи должны иметь правильную подпись группы; конфликт — WARN.
Публикация одинаковой картинки не создаёт новую версию. Удаление — подписанная
запись с size=0 и новой версией; подпись/версия сохраняются, старый пир не может
восстановить удалённую картинку. Чтение проверяет сохранённый хеш и подпись.
Транспорт
---------
ETCP_RT_ID_GROUP_AVATAR=0x3A, router wire version=1. Отправитель/группа берутся
из проверенного router-заголовка. Все пакеты подписаны ключом узла и зашифрованы
end-to-end; в DATA дополнительно передаётся подпись группы над всей картинкой.
Обмен только между READY-соседями CHAT-группы с действующим подписанным членством.
Любой участник пересылает сохранённую запись, владелец ключа может быть offline.
Все числа протокола LE. Общая часть, 44 байта:
version:u8=1, command:u8, avatar_version:u64, total_size:u16, SHA256:32.
STATE=1 объявляет локальную версию/хеш; начальное отсутствие — version=0, hash=0.
GET=2 запрашивает актуальную запись. Объявление не меняет БД без полной DATA.
DATA=3 добавляет group_signature:64, offset:u16, block_size:u16, bytes.
DATA header=112 байт. Картинка разбивается на блоки по 4096 байт; последний
короче. Полная картинка 16 КиБ — ровно четыре DATA. Удаление — один DATA без bytes.
Порядок блоков гарантирует router. Сборка проверяет offset, постоянные version,
total_size/hash/signature и допустимые размеры. До полной сборки прежняя картинка
остаётся в БД. После SHA256/Ed25519 вся запись заменяется одним UPDATE, затем
CHANNEL_UPDATED доставляет изменение GUI и STATE распространяется соседям.
Промежуточные блоки не публикуются, ACK/retransmit каждого блока — только router.
Отправка заполняет router до нижнего порога send_q count<=0 и ждёт send_ready;
планировщик отправляет максимум четыре пакета за callback. Snapshot отправителя
не меняется при новом локальном обновлении. Максимум 16 tx и отдельно 16 rx
сборок на экземпляр, каждая до 16 КиБ. receive_ready при заполнении rx сохраняет
пакет в router recv_q без продвижения ACK; освобождение слота возобновляет приём.
Несовпадение эпох/закрытие router отменяет незавершённую сборку и начинает сверку.
Эпоха нового контекста берётся из текущей READY-сессии, не сбрасывает первый приём.
Обнаружение соседей/таймауты — таймер 1 с; STATE также раз в 30 с. Нет прогресса
15 с — отмена незавершённого snapshot, повтор запроса/сверки. Остановка сервиса
отменяет таймеры/waiters/call_soon до удаления группы/ядра.
ChatGUI
-------
Настройки группы: Upload group avatar/Remove group avatar, предпросмотр и статус.
Кнопки доступны только при локальном приватном ключе. Применение сразу, отдельно
от Save локальных настроек. Worker обрезает центральный квадрат, создаёт JPEG
128×128, снижает качество/размер до лимита 16 КиБ. Вход ограничен 16 мегапикселями.
GUI отправляет байты в uasync; ядро повторно проверяет размер/членство/ключ.
SQLite — постоянный кеш. GroupAvatarManager хранит QImage до 32 МиБ, ключ кеша:
group/version/hash. Декодирование в отдельном worker, проверка SHA256, JPEG и
квадратных размеров до 128×128. Поздний результат старой версии не заменяет
новую. Готовая картинка обновляет только строку группы; без аватарки — инициалы.
Приватные ключи/байты картинки не выводятся в диагностику.
Проверки
--------
test_group_avatar: настоящие два ядра/UDP/router, 16 КиБ четырьмя блоками,
атомарность сборки, hash/signature/cross-group отказ, приостановка receive,
перезапуск канала, stale/conflict, редактор с ключом вне creator UID, подписанное
удаление, повторное описание группы и restart БД. Schema failure откатывает
частичные ALTER, сохраняет прежние данные/ключи, повторное добавление идемпотентно.
test_group_avatars (Qt): нормализация, лимит/отображение, кеш, права кнопок и отказ
ядра без ключа, удаление. chat=debug показывает состояния и каждый 4КиБ блок;
chat=info — публикации и жизненный цикл. Ошибки содержат group/peer/version/offset.
+528
View File
@@ -0,0 +1,528 @@
# Linux: диагностика и восстановление аудио
Проверка 2026-10-02: desktop chatgui, miniaudio/PulseAudio, общий голосовой стек,
звонки, рация и запись сообщений. Исправления обработки PCM, владения устройствами
и автоматическое восстановление реализованы. Ниже сохранены исходные результаты
аудита; раздел «Реализация» описывает действующие контракты и проверки.
## Вывод
До исправлений тракт был некорректен при сбоях. Самое существенное: воспроизведение
звонка зависит от наличия микрофонных кадров в duplex-ring. Его опустошение
останавливает вызов пользовательского callback, оставляя хвост выходного буфера
незаполненным. Восстановление jitter-буфера не исправляет остановившееся устройство.
Отдельного управляющего механизма восстановления устройств не было.
В реализации восстановлены три гарантии: каждый запрос output полностью заполнен; обработка
одного callback всегда ограничена; остановка/пересоздание устройства выполняются
вне аудиопотока и не уничтожают сетевую сессию звонка.
## Что установлено по работающим клиентам
В локальном старом логе звонка `132e4a9698f47bdf` с Samsung SM_A525F наблюдались
паузы callback примерно 0,5–2,6 секунды при зарегистрированной обработке 0–2 мс.
Входящий буфер desktop достигал 15 секунд, росли потери, включалось ускорение 160%.
На Samsung захват продолжал давать около 50 кадров/с, playback регистрировал
underrun и тоны разрыва. RTT порядка 1–4 мс не объясняет эти паузы сам по себе.
Это признаки проблемы обслуживания локального аудио, но ещё не доказательство
конкретной причины зависания PulseAudio. Старое измерение исключало время
синхронных DEBUG-вызовов между концом обработки и обновлением timestamp.
Новая диагностика охватывает весь callback, включая эти логи.
Работающий GUI не перезапускался, поэтому новый инструмент ещё не записал этот
сбой на реальном звонке. Лог Samsung сохранён в `/tmp/sm-audio-current.log`.
## Найденные дефекты и ограничения до исправлений
| Место | Поведение и последствия | Необходимая доработка |
| --- | --- | --- |
| `lib/miniaudio.h`, `ma_device__handle_duplex_callback_playback()` | При пустом capture-ring `acquire_read` возвращает success с нулём кадров. Выполняется `break`, output остаётся частично незаполненным, call callback не вызывается. PulseAudio затем отправляет весь mapped buffer. | Обеспечить полный output и продолжение playback при отсутствии capture. Нельзя делать commit_read для синтетических кадров. |
| `ma_device_on_write__pulse()` | Успешная запись с нулём обработанных кадров не уменьшает остаток цикла. Возможно бесконечное вращение с блокированием того же PA mainloop, включая capture. | Немедленно выходить при отсутствии продвижения, возвращать управление mainloop. Логировать состояние и запрос, не пытаться записывать до успеха в этом callback. |
| `ma_device_on_read__pulse()` | PA hole отбрасывается без подачи соответствующего количества нулевых кадров. Из capture timeline исчезает интервал, duplex-ring может опустеть. | Сохранять длительность hole тишиной либо явно обозначать discontinuity независимому capture-тракту. |
| `lib/speex_aec.c`, `speex_aec_feed_playback()` | Обрабатывает только остаток одного 960-sample кадра, отбрасывая хвост входного блока. Контракт разрешает произвольный count. | Циклом потреблять весь count, складывать все полные кадры, сохранять остаток. |
| `CallAudioEngine::setPlaybackDeviceIndex()` | После неудачного пересоздания `m_active` остаётся true при отсутствии устройства; `start` того же звонка возвращает успех. | Разделить желание продолжать звонок и работоспособность устройства. Ошибка запуска переводит устройство в recovering. |
| `RadioAudioEngine::teardownDevice()` | Если playback init успешен, а start нет, uninit пропускается: он зависит от флага started. Объект удаляется вместе с незавершёнными ресурсами. | Разделить initialized и started; каждый успешный init обязательно завершать uninit. |
| `MainWindow::~MainWindow()` / `SoundManager::shutdown()` | Общий context освобождается вместе с engine, но звонок и рация здесь явно не останавливаются. Их singleton-объекты могут оставлять живые устройства. `m_context` также не обнуляется. | Сначала остановить все зависимые устройства и дождаться callbacks, затем освобождать общий context. |
| `AudioRecorder` | Callback выбирает глобальный `g_recorder`, игнорируя `pUserData`. Второй recorder, в том числе тест микрофона, заменяет получателя. Флаги/уровень/вектор waveform используются из GUI и аудиопотока без согласованной синхронизации. | Использовать `pUserData`; передавать уровни атомарно, waveform — безопасным snapshot; буфер записи должен иметь однозначного владельца. |
До исправлений первые два дефекта воспроизводились стендом без аппаратуры:
duplex оставлял sentinel в output при starvation, а PA-write без продвижения
требовал внешней остановки. AEC из 1200 + 720 samples сохранял один кадр вместо
двух. Теперь эти проверки требуют полного output, самостоятельного возврата
из callback и двух сохранённых AEC-кадров; результаты приведены ниже.
Capture-AEC также обрабатывает не более одного кадра за вызов. В звонке его
вызывают ровно с 960 samples, поэтому текущий call feed этот случай не нарушает.
При расширении API нельзя объявлять произвольный count без согласованного размера
выходного буфера. `speex_aec_reset()` уже существует: новый API для сброса самого
AEC не нужен, но нужен согласованный сброс всего локального voice-тракта.
## Чистота реализации
Этот раздел фиксирует исходный аудит; изменения владения и ограничение работы
callback описаны в разделе «Реализация».
Нормальная структура уже есть: GUI владеет устройствами, сервисный слой кодирует
Opus и обрабатывает jitter, сетевой поток доставляет пакеты. Нет необходимости
переписывать весь стек или заводить новый backend.
Однако границы владения не завершены: context фактически принадлежит engine
звуковых уведомлений, а используется независимыми устройствами звонка/рации.
Флаги active/started/initialized местами смешивают три разных состояния.
Выбор input применяется к recorder, звонок использует default input, рация —
default устройства. Output звонка и SoundManager тоже выбираются отдельно.
Индекс перечисления не является устойчивой идентичностью устройства после hotplug.
В radio capture fallback `useDefault` фактически повторяет ту же попытку:
`pDeviceID` и так NULL в обеих ветках. Callbacks рации запускаются раньше
подготовки capture-буфера и установки VAD; порядок запуска нужно привести к
«сначала состояние и буферы, затем устройства», чтобы исключить гонки при старте.
Callback не является строго realtime-safe. Capture удерживает `g_mtx` во время
AEC/AGC/Opus; отправка выделяет память и вызывает `uasync_post`. Playback вызывает
SoundTouch и код с возможным расширением контейнеров, mutex и синхронными логами.
Приём пакетов тоже может держать mutex при работе с jitter и логировании.
Плохие Opus-кадры могут последовательно выбираться из большой очереди в одном
pull: нужен предел работы на callback. Новая диагностика покажет, где это
действительно нарушает deadline. Перенос всего DSP в ещё один worker заранее
увеличит число очередей и точек синхронизации без установленной необходимости.
AEC получает reference только звонка. Звуки отдельного SoundManager в него не
попадают; delay по умолчанию фиксирован, не вычисляется по фактической задержке
устройств. Это ограничение качества эхоподавления, а не установленная причина
текущего зависания.
Jitter умеет повторно накопить резерв после сетевого underrun, но диапазон tempo
ограничен 1,0–1,6. Ускорение убирает избыток, а замедления для компенсации более
медленных часов отправителя нет. При устойчивом отрицательном дрейфе резерв будет
снова опустошаться. Это отдельная задача длительных звонков: после восстановления
устройств проверить наклон глубины буфера и при необходимости добавить небольшую
компенсацию дрейфа в обе стороны, не смешивая её с быстрым catch-up после паузы.
Принудительные 480 frames задают период 10 мс при 48 кГц. Собственный PA default
miniaudio — 25 мс с комментарием о проблемах PipeWire при периодах меньше ~20 мс.
Период backend и Opus-кадр 20 мс должны оставаться независимыми. Увеличивать
период до исправления AEC нельзя: 1200-sample блоки уже воспроизводят потерю хвоста.
После исправления разумно сначала проверить default backend и измерить задержку.
## Предлагаемое восстановление без большого усложнения
### 1. Сначала восстановить инварианты обработки и владения
Исправить полный output, zero-progress, AEC accumulation и uninit при неудачном
start. Перед освобождением context явно остановить recorder, звонок, рацию и
прочие его устройства. До остановки отменять recovery timer; после join можно
сбрасывать voice-ресурсы. Проверять возвраты feed/start/init и логировать причину.
Самый небольшой вариант — оставить один duplex-device: при нехватке capture
подать в call callback нулевой input, полностью сформировать playback и не
продвигать реальный capture-ring за синтетические samples. Это чинит непосредственный
дефект, но сохраняет общий жизненный цикл input/output и синтетический TX по
playback-clock.
Для устойчивости к исчезновению микрофона предпочтительнее два устройства
capture/playback внутри существующего `CallAudioEngine`, как уже сделано в рации.
Capture только накапливает 960 samples и вызывает feed; playback независимо
вызывает pull и всегда заполняет output. Это два handle с одним владельцем,
без нового DSP-worker или дополнительной PCM-очереди. Потребуется уточнить
потоковый контракт voice-слоя: один capture-writer и один playback-writer,
согласованный доступ к AEC, остановка обоих до release. Текущий `g_mtx` уже
защищает AEC, но остальные общие поля нужно проверить перед разделением.
### 2. Разделить краткий underrun и неисправность устройства
| Событие | Действие |
| --- | --- |
| Нет сетевого PCM, callback продолжает приходить | Тишина/существующий gap tone; jitter автоматически накапливает данные. Устройство не пересоздавать. |
| Один backend underrun, новые кадры принимаются | Подать следующий полностью заполненный буфер; собрать счётчик. Не сбрасывать AEC и не запускать restart на каждый xrun. |
| Hole/переполнение capture | Учесть потерянную длительность; краткую потерю пережить, длительную отметить discontinuity для AEC. |
| Поток failed/stopped или нет продвижения кадров длительное время | Перейти в recovering и пересоздать затронутое устройство вне callback. |
| Suspend/resume ОС | Учитывать suspend как отдельную причину, восстановление проверять после resume; не запускать частый restart-loop во время сна. |
| Потерян общий backend/context | Однократно остановить всех его пользователей, восстановить владельца, затем требуемые устройства. |
Один owner timer и состояния `Stopped / Running / Recovering` достаточны.
`wanted` хранит желание пользователя продолжать; generation меняется при stop,
новом звонке и смене устройства. Каждая отложенная попытка проверяет generation
и wanted — законченный звонок никогда не возрождается.
Callbacks только обновляют атомарные счётчики успешно обработанных кадров и
признак backend failure. Watchdog проверяет фактический прогресс отдельно по
направлениям. Тишина в PCM не означает остановку устройства. Reporter диагностической
очереди не должен становиться watchdog: его события могут теряться.
Начальные значения для проверки: timer 200 мс, подозрение при отсутствии прогресса
500 мс после startup grace; повторные попытки через 250/500/1000 мс с потолком
2 секунды. Это предлагаемые настройки, не доказанные универсальные пределы.
Suspend и фактический согласованный backend period должны учитываться отдельно.
Нельзя обещать bounded recovery, пока init/stop/uninit сами могут зависнуть:
сначала устранить spin-loop и проверить время backend-операций. Если они реально
блокируют GUI, вынести только управление устройствами в один owner-thread,
сохранив тот же автомат состояний.
На пересоздании сбросить capture-accumulator, AEC delay/filter и локальный PCM/
stretch, сохранив call ID и сетевую сессию. После длительной аппаратной паузы
устаревшую речь нужно явно отбросить до актуального резерва jitter, иначе
15 секунд накопленного звука при скорости 160% разгружаются примерно 25 секунд.
Этот сброс применять только при аппаратной discontinuity; обычный сетевой jitter
не должен автоматически приводить к отбрасыванию очереди.
Устройство хранить по backend ID либо как default, не по индексу. При его
исчезновении попробовать default и явно сообщить о смене; выбранное устройство
можно вернуть при появлении. Нужны ошибки с ID/именем/направлением и результатом
попытки. Для PA-ошибок следующий уровень диагностики — stream/context state и
`pa_context_errno`, потому что один `-1` от API не объясняет причину.
### 3. Проверить сценарии, а не только успешный старт
После реализации добавить проверки: capture starvation 20/500 мс и возврат;
hole; AEC блоки, пересекающие границу кадра; zero writable; ошибка записи;
исчезновение выбранного устройства и появление default; suspend/resume;
перезапуск звукового сервера; stop во время backoff; закрытие GUI при активном
звонке/рации. Проверять полный output, bounded очереди и работу callback,
возобновление прогресса, отсутствие callbacks после release и отсутствие
повторного старта после пользовательского stop.
## Как читать добавленную диагностику
В GUI-категориях включить `call=debug` либо задать отдельную строку в INI:
```ini
[debug]
call=debug
```
Обычные сводки идут на DEBUG, аномалии — WARN/ERROR; miniaudio info перенесён
на DEBUG, подробный backend log — TRACE. Все устройства используют категорию
call, включая recorder, radio и sound-engine; role и ID различают их в строках.
Не включать глобальный trace для всего проекта при измерении аудио.
Reporter раз в секунду печатает stage, tid, callbacks/s, requested frames/s,
максимальный интервал между входами, полное wall-time обработки и thread CPU-time,
незавершённый вход и время с последнего входа. `work_max` и `cpu_max` — отдельные
максимумы за окно, они не обязательно относятся к одному вызову.
| Stage | Что измеряется |
| --- | --- |
| loop | Один `pa_mainloop_iterate`, включая ожидание и вложенные callbacks. Долгое время само по себе не доказывает тяжёлый DSP. |
| read / write | Полный backend read/write callback. Frames/s здесь — запрошенные, не гарантированно обработанные кадры. |
| map | `writable_size` и `begin_write`; пустой/ошибочный выход тоже закрывает измерение. |
| submit | Вызов `pa_stream_write`. |
| duplex | Перенос capture-ring в duplex-output; detail содержит глубину capture-ring. |
| callback | Полный miniaudio data callback, включая код приложения и его синхронные логи. |
| feed / pull | Capture DSP/encode/send и playback jitter/stretch/tone в звонке. |
`unfilled_frames` — незаполненный output, `hole_frames` — потеря capture timeline,
`capture_drop_frames` — переполнение capture-ring, `no_progress` — PA-write без
продвижения. `backend_underruns` — отдельный callback самого PA-stream; это не
то же самое, что пустой сетевой jitter. `backend playback started/resumed`
показывает фактическое начало/возобновление playback. Notification содержит
числовые miniaudio type/state.
Первое событие каждого типа за окно имеет tid и CLOCK_MONOTONIC timestamp;
повторения суммируются. Для задержавшихся потоков reporter читает `/proc` wchan
и накопленные schedstat CPU/ready-wait/slices. Сравнивать соседние snapshots:
сами totals не являются длительностью текущей паузы. CPU-time намного меньше
wall-time означает ожидание/вытеснение; wchan помогает отличить poll от mutex/
I/O wait. Это снимок в момент отчёта, не исторический стек зависшего вызова.
Producer не выделяет память, не ждёт mutex reporter и не пишет диагностические
логи: ограниченная MPSC-очередь 8192 события, не более четырёх CAS-попыток.
Reporter не разыменовывает ma_device/pa_stream. Переполнение логируется как
`diagnostic events lost`; такое окно нельзя считать полным. Инструментация
добавляет получение часов/TID и атомарные операции, поэтому overhead ненулевой.
Частые события call jitter/AEC/TX теперь публикуются счётчиками и выводятся из
uasync вне аудиомьютекса. Часть существующих логов рации остаётся синхронной;
тракт не объявляется строго realtime-safe.
После close устройство удаляется из reporter: для устройств короче секунды
доступны lifecycle и первые аномалии, но не полная секундная сводка.
Новый binary собран в `tools/chatgui/build/vibechat`; для нового измерения нужен
его обычный перезапуск. Изменение файла binary не обновляет уже запущенный процесс.
## Реализация (2026-10-02)
`SoundManager` владеет общим контекстом и конечными выходами. На каждый фактический
выход приходится один микшер: PCM сообщений/видео, заранее декодированные сигналы,
готовый голос звонка и рации. После суммирования float PCM ограничивается, и один
и тот же stereo буфер отправляется устройству и AEC соответствующего микрофона.
Звук другого выхода в референс не входит. Default/явный backend ID объединяются;
PulseAudio также публикует идентификатор фактического маршрута. После server move
совпавшие выходы объединяются в GUI только после подтверждения фактического
маршрута; предположение о default не используется вместо PA snapshot. При временной коллизии дополнительный выход
отдаёт тишину до объединения. Изменение списка источников выполняется после stop
callback, с сохранением backend stream и выбранного сервером маршрута.
Устройства сохраняются как backend ID вместо индекса. При init/start failure
явного выбора пробуется default, причина логируется. Выбранный ID сохраняется;
вернуться к нему можно повторным выбором или при следующем recovery. Автоматический
опрос hotplug для возврата на первоначальный выбор не добавлен.
`AudioDevice` — владелец одного miniaudio device в GUI. Успешный init всегда имеет
парный uninit, включая ошибку start. Callback и его состояние готовятся до start.
Close останавливает/join-ит device worker перед освобождением callback. Отдельный
атомарный счётчик кадров контролирует прогресс. Таймер 200 мс проверяет stopped
и отсутствие продвижения 1 с; suspend продлевает grace, отдельный xrun не вызывает
restart. Backoff: 250/500/1000/2000 мс, сброс после продвижения.
Miniaudio не добавляет накопитель фиксированного периода: `noFixedSizedCallback`
включён, hardware chunks могут иметь любую длину. В PulseAudio физическое начало
блока определяется по timing snapshot с AUTO_TIMING_UPDATE/INTERPOLATE_TIMING.
Capture: monotonic now минус latency read index, без повторного вычитания длины
блока. Playback: now плюс latency write index. Учитываются задержка converter и
остающийся input cache. Один backend dispatch имеет один snapshot, позиции
последующих client callbacks продвигаются по числу кадров. В отсутствие snapshot
используется оценка по callback; источник времени и physical route логируются.
PA hole отдельно помечен как отсутствующий capture, а не как реальная тишина.
`VoiceAudioIo` создаётся только для звонка и рации. Capture и RX обрабатывают
независимые потоки. Hardware capture только копирует PCM в фиксированную MPSC-очередь;
RX worker выполняет decode/SoundTouch/тоны и поддерживает до 40 мс готового голоса.
В сервисном слое рации вычисление Silero держит только TX mutex: общий mutex
отпущен, поэтому VAD не блокирует RX; lifetime и порядок PCM/PTT сохранены.
Hardware output смешивает готовый PCM. Opus, AEC, VAD, AGC и растущие буферы записи
не исполняются в аппаратных callbacks. Обработка самого backend, PCM/resampler
узлов miniaudio и атомарные уведомления остаются в callback; строгая hard realtime
гарантия не заявляется.
Capture worker — единственный владелец AEC и порядка PCM/PTT/mute. Кадр AEC —
960 отсчётов на канал; 1/2 канала микрофона и 2 независимых канала референса.
Противофазное стерео не усредняется. История рендера содержит до 1,92 с PCM и
физические timestamps. Reference выбирается по времени микрофона; небольшое
различие часов компенсируется интерполяцией только референса. Реальная тишина
сохраняется как валидный PCM; при отсутствующем reference AEC пропускает микрофон
и не адаптируется. Потеря reference, смена маршрута/поколения и скачок часов
сбрасывают фильтр. Возраст capture-очереди ограничен 100 мс от enqueue, отдельно
от собственной задержки backend. Потери и очередь видны в сводках категории `aec`.
Mute применяется после AEC/AGC: фильтр продолжает получать настоящий микрофон,
а Opus получает нули только в отмеченной части кадра. Desktop запускает core
через `call_audio_start_prepared`: повторного core AEC нет. Raw API с core AEC
сохранён для Android/headless; смешивать политики в активном звонке нельзя.
У рации один автомат передачи: manual PTT или VAD. VAD читает непрерывный очищенный
сигнал до AGC, включая время ручного PTT. Передача использует отдельный накопитель
Opus-кадра; границы PTT внутри AEC-кадра сохраняются. При закрытии серии хвост
дополняется нулями только перед Opus и помещается в очередь перед FIN. Короткий
capture tail при stop проходит без AEC, а не дополняет непрерывный фильтр. Разрыв
захвата завершает старый сетевой хвост и сбрасывает VAD, исключая склейку через
потерянные отсчёты. Capture restart завершает принятую серию; новое прослушивание
того же канала отменяет старые BEGIN/media. FIN проверяет токен своего capture/burst
и поколение экземпляра ядра, поэтому не закрывает более новую серию и не обращается
к освобождённому ядру даже при повторном использовании его адреса.
TX ограничен 8 media-задачами, возраст — 200 мс. У рации дополнительно ограничено
16 control-задач; FIN выделяется заранее вместе с BEGIN. Если control-очередь
занята, новый BEGIN откладывается до освобождения; это логируется. Очередь PCM
резервирует 8 слотов для управляющих сообщений; producer не меняет consumer и
публикует слот только после копирования. Capture callback никогда не ждёт слот.
Отказ постановки GUI-команды завершает capture; таймер запускает recovery.
`AudioRecorder` получает raw microphone без AEC. Его worker обрабатывает AGC,
накапливает запись и публикует атомарный уровень/длительность. Переполнение или
ошибка обработки отклоняют запись с логом. Meter-only использует тот же capture,
но не сохраняет PCM и не создаёт голосовое сообщение. Stop: close device, drain
и join worker, затем передача владельца записи задаче attachment. Настройки
компрессора в ходе записи применяются к следующей.
PCM воспроизводится через независимые handles. Сообщение, видео и тест динамика
не останавливают чужой PCM; sample rate принадлежит источнику. Seek пересоздаёт
узел и очищает его read/resampler cache, сохраняя handle и владельца PCM. Cursor
вычисляется по реально обработанным графом кадрам, а не по prefetched datasource.
Pause отсоединяет узел и ждёт начатое чтение графа; resume подключает тот же узел,
сохраняя его PCM и cache. Соседние проигрыватели продолжают работать.
У асинхронного открытия видео/голоса проверяется generation.
Потоковый AGC использует фиксированные кольца истории и PCM, нулевой lookahead
и непосредственный process_frame без растущего output. У записи lookahead явно
100 мс; растёт только сохраняемый результат. Push/flush/configure возвращают
ошибки; flush проверяется перед кодированием вложения.
Порядок остановки голосового направления: close capture → join capture worker →
detach output → join RX worker → release core. При failed context синхронный сигнал
сначала закрывает всех клиентов, затем освобождает context. Mixer, PCM handles,
call ID и группа переживают recovery. Stop выключает recovery; таймер завершения
звонка проверяет generation. MainWindow останавливает клиентов до SoundManager
и сетевого ядра. Сетевые операции и регистрация audio ops выполняются в uasync.
PulseAudio control-waits ограничены 1 с на операцию; при timeout operation
отменяется до возврата, исключая callback с просроченным stack pointer. Partial
init освобождает контекст/mainloop/события. Failed stream/context содержит PA
errno в логе. Zero writable/zero converter progress возвращают управление.
Последовательность нескольких операций init/uninit всё ещё может задержать GUI
дольше секунды; отдельный device owner-thread не добавлен.
## Проверки реализации
`vibechat` и аудиотесты собираются через CMake. Набор проверки:
```sh
ctest --test-dir tools/chatgui/build --output-on-failure \
-R 'test_(linux_audio|audio_timing|audio_diagnostics|audio_recovery|audio_event_queue|voice_audio_io|voice_tx|call_jitter|radio_audio|radio_jitter|voice_file)$'
make -C tests test_audio_compressor test_speex_aec
./tests/test_audio_compressor
./tests/test_speex_aec
```
Добавлены отдельные проверки MPSC (80000 сообщений, reserve/control, слот ещё не
опубликован), точных PCM/PTT/mute границ, короткого хвоста, независимого RX при
задержке capture worker, противофазного stereo AEC, старого физического timestamp
при свежем enqueue, совпадения реальных Opus-пакетов с правильно дополненным
хвостом, rapid PTT, capture restart, отмены старого прослушивания и bounded control.
Проверяются manual/VAD handover
без второй серии и продвижение настоящего сервисного RX при заблокированном VAD.
Timing test проверяет read-index timestamp, несколько converter callbacks одного
блока и метку hole. Recorder тестирует raw PCM и отсутствие вложения в meter-only.
Speex проверяет подавление эха и сохранение double-talk также для двух микрофонов
с двумя независимыми каналами reference. В детерминированном синтетическом тесте
остаточная энергия эха — около 0,009 исходной; корреляция near-end — около 0,95.
Эти величины относятся только к тесту, а не к реальным акустическим условиям.
В `/tmp/utun-audio-asan` проверяются пять тестов нового тракта с ASAN/UBSAN и
LeakSanitizer; LeakSanitizer запускается вне песочницы. MPSC отдельно проверяется
ThreadSanitizer. Изолированный интеграционный запуск:
```sh
dbus-run-session -- python3 tools/chatgui/tests/run_pulse_recovery.py
```
Итоговый прогон 2026-10-02: сборка `vibechat` успешна, CTest — 11/11,
ASAN/UBSAN/LeakSanitizer — 5/5, компрессор — 479/479, Speex — PASS,
MPSC под ThreadSanitizer — PASS. Timing-стенд прошёл ещё 100 последовательных
повторов. Пауза PCM проверяется также серией pause/resume при работающем соседнем
проигрывателе; она ждёт чтение узла, а не только меняет его флаг.
Тест создаёт собственные PipeWire/PulseAudio/WirePlumber в `/tmp`, отключает
аппаратные monitors и использует два виртуальных sink. Проверяет объединение
voice с общим выходом после `move-sink-input`, сохранение desired выхода звонка
при recovery общего выхода (включая server stream-restore), затем восстановление capture/PCM
после остановки и запуска своего PulseAudio. Звонок и рация сохраняют идентичность;
recorder и медиа продолжают работу. Системный аудиосервер не затрагивается.
Исторические проверки исходного recovery: полный `check.sh` — 119 passed,
0 failed, 1 skipped; proxy/burst/load интеграции прошли. По указанию пользователя
async-тесты исключены из дальнейших запусков. APK ранее собран offline; установка
на телефон не выполнялась.
Физический USB/Bluetooth hotplug, сон ОС, внешние звуки других приложений и качество
AEC в длительном реальном звонке этими проверками не подтверждены. Reference
охватывает звук этого приложения на том же выходе, согласно выбранному контракту.
## Период Linux/PulseAudio и сравнительный замер
`AudioDevice::requestLowLatency()` задаёт запрос 10 мс для capture звонка/рации.
Он применяется при каждом открытии, включая recovery и fallback.
Остальные backends сохраняют прежние параметры.
Общий и отдельный voice output сохраняют прежний период: запрос 10 мс
на проверенной системе не снизил измеренную задержку воспроизведения.
Кадры AEC/кодека остаются 20 мс; `noFixedSizedCallback` позволяет обслуживать
фактические порции backend без дополнительного накопления в miniaudio.
Это запрос задержки, а не гарантия размера callback. PulseAudio согласует
`tlength`, `fragsize`, `minreq`, `maxlength`, `prebuf`; PipeWire также ограничивает
quantum и минимальные порции. `AudioDiag` при init выводит внутренний PCM-формат
и согласованные параметры в миллисекундах (`-1` — значение по умолчанию).
Пересчёт использует внутренние rate/channels/format, а не формат приложения.
Секундные clock-отчёты содержат `latency_us`/`backend_us` и `block_us` в виде
`min/avg/max`. В задержку включены только измеренные timing snapshots;
`estimated_blocks`/`measured` позволяют отличить их отсутствие от нулевой задержки.
Для времени исполнения callback включать `call=trace`, для состояния AEC —
`aec=debug`. Логи агрегируются вне аппаратного callback.
Ручной режим существующего стенда использует настоящие устройства и голосовые
workers, не сохраняет микрофонный PCM и не отправляет его по сети. Выход звонка
без удалённого собеседника обслуживается тишиной; акустическое подавление эха
этот режим не измеряет. Аргументы: capture frames, playback frames, секунды,
необязательный режим `call` (по умолчанию), `radio` или `output`, затем путь лога.
Ноль frames оставляет стандартный период backend; override применяется только
в тестовом executable, до настоящего `ma_device_init`.
```sh
cmake --build tools/chatgui/build --target test_audio_recovery -j4
tools/chatgui/build/test_audio_recovery --latency 960 0 60 > /tmp/audio-baseline.log 2>&1
tools/chatgui/build/test_audio_recovery --latency 480 0 60 > /tmp/audio-capture10.log 2>&1
tools/chatgui/build/test_audio_recovery --latency 960 480 60 > /tmp/audio-playback10.log 2>&1
tools/chatgui/build/test_audio_recovery --latency 480 480 60 > /tmp/audio-both10.log 2>&1
```
Запуски последовательные: несколько потоков могут менять общий quantum сервера.
Сравнивать одинаковые маршруты и нагрузку, отдельно от старта/остановки и после
стабилизации адаптивного PCM-резерва. Проверять backend latency, возраст первого
сэмпла перед готовым TX-кадром, очередь после mix, starvation, holes, потери PCM,
AEC reference и событий диагностики. Затем повторить baseline и проверить
выбранный вариант под нагрузкой. Меньший возраст PCM в capture callback сам по
себе не доказывает такой же выигрыш до кодирования: worker собирает кадры 20 мс.
`test_voice_audio_io` дополнительно проверяет порции по 256 samples: порядок PCM,
временные метки, границы mute/PTT и позицию частичного playback. AEC проверяется
при разных порциях capture/reference (256/480 и 480/256) с противофазным stereo
reference. Очереди по-прежнему ограничены числом событий; уменьшение периода
не увеличивает их ёмкость в миллисекундах.
Сравнение 2026-10-15 на встроенном HDA, PipeWire 1.6.2, 48 кГц. Каждый вариант
работал 60 секунд; таблица содержит средние по callback/кадрам после 45 секунд
прогрева (PCM-резерв к этому моменту уменьшился до одного блока).
| Запрос capture / playback | Capture backend, мс | Первый сэмпл готового TX-кадра, мс | Playback backend, мс | PCM после mix, мс |
| --- | ---: | ---: | ---: | ---: |
| 20 мс / default 25 мс | 30,69 | 34,53 | 16,32 | 18,67 |
| 10 мс / default 25 мс | 16,02 | 29,79 | 16,15 | 18,67 |
| 20 мс / 10 мс | 31,51 | 34,97 | 18,09 | 24,00 |
| 10 мс / 10 мс | 16,13 | 29,66 | 18,46 | 24,00 |
Изменение capture даёт около 14,7 мс на границе backend и около 4,7 мс перед
передачей полного кадра в голосовой стек. Для playback сервер согласовал
`tlength=16ms`, `minreq=5.333ms` при запросе 10 мс; callback уменьшился с 512
до 256 samples, но выигрыша по задержке не получилось. Поэтому в рабочем коде
изменён только capture. Значения в столбцах не следует складывать как независимые
участки общей задержки: capture backend входит в возраст TX-кадра, а очередь после
mix снята уже после извлечения текущего выходного блока из очереди.
В этих четырёх прогретых окнах не было потерь capture/reference PCM, выходного
starvation, backend underrun/overflow или потери событий диагностики.
Отдельные отсутствующие AEC references были и в baseline (4 кадра за окно),
и в остальных вариантах (4–5). Это существующее ограничение привязки reference,
а не проверка акустического качества. Первоначальный output starvation и
временное увеличение PCM-резерва учитываются отдельно от стабильного окна.
Повтор baseline дал 30,76 мс capture backend и 34,99 мс возраста TX-кадра.
Двухминутный прогон выбранного capture 10 мс под нагрузкой четырёх CPU-процессов
дал после прогрева 15,81 и 28,55 мс соответственно; PCM после mix — 18,67 мс.
Потерь PCM, backend underrun/overflow и потери событий диагностики в этом окне
не было. За 75 секунд окна зарегистрировано 25 отсутствующих AEC references.
Отдельный минутный прогон рации: capture backend в среднем 15,48 мс, без потерь PCM.
Сборка `vibechat` и 6 аудиотестов прошли. Изолированный интеграционный тест
подтвердил объединение выходных маршрутов и восстановление capture/playback после
перезапуска тестового PulseAudio. Запускать весь стенд через `dbus-run-session`,
чтобы имя его PulseAudio не конфликтовало с сервером пользовательской сессии.
В fixture перед `radio_audio_init` создаётся `radio_instance`, освобождаемый после
`radio_audio_destroy`; это исключает ложную ошибку регистрации mute callback.
Полный `check.sh` вне sandbox: 156/156, 9/9 proxy-интеграций, burst и load — PASS.
В sandbox сетевые тесты не проходят из-за запрета сокетов; этот запуск не является
проверкой сетевых регрессий.
## Референсы
Первичные исходники и документация просмотрены 2026-10-01; ссылки на master могут
изменяться. Используем принципы, а не копируем целиком архитектуру библиотеки.
- [PipeWire audio-src-ring.c](https://docs.pipewire.org/audio-src-ring_8c-example.html):
callback читает доступные кадры, заполняет остаток тишиной и возвращает полный
буфер; пополнение инициирует через событие управляющего потока. Хороший минимальный
пример поведения при пустой PCM-очереди.
- [PulseAudio stream API](https://github.com/pulseaudio/pulseaudio/blob/master/src/pulse/stream.h):
отдельные underflow/overflow/started/suspended/moved callbacks; peek различает
пустую очередь и hole с ненулевой длиной. Нужно разделять эти состояния в логах.
- [PulseAudio pacat.c](https://github.com/pulseaudio/pulseaudio/blob/master/src/utils/pacat.c):
underflow callback сообщает событие без пересоздания stream. При failed context
утилита завершает работу — это пример диагностики, не готовая self-healing система.
- [Mozilla cubeb PulseAudio backend](https://github.com/mozilla/cubeb/blob/master/src/cubeb_pulse.c):
явные context/stream states, пересоздание ошибочного context при инициализации,
согласованное отключение callbacks и доступ под mainloop lock; minimum latency
25 мс. Это полезный референс владения и границ управления, а не основание
автоматически заимствовать всю библиотеку.
- [miniaudio](https://github.com/mackron/miniaudio/blob/master/miniaudio.h):
init/start/stop/uninit запрещены внутри data callback из-за deadlock. Просмотренный
upstream тоже содержит break при пустом duplex-ring: обновление библиотеки само
по себе не решает этот найденный дефект.
+66
View File
@@ -0,0 +1,66 @@
# Важные нюансы работы встроенного lwIP TCP-стека
Краткая памятка по тонким местам `src/lwip_tcp/`, критичным для прокси.
Полное описание модуля — в `src/lwip_tcp/lwip_tcp_doc.md`.
## Ключевые константы (`lwip_tcp_opts.h`)
| Константа | Значение | Смысл |
|-----------|----------|-------|
| `TCP_MSS` | 1460 | максимальный размер сегмента |
| `TCP_WND` | 8×MSS = 11680 | окно приёма lwIP (объявляется пиру) |
| `TCP_SND_BUF` | 16×MSS = 23360 | буфер отправки |
| `TCP_TMR_INTERVAL` | 250 мс | базовый интервал таймера |
| `tmr_interval_ms` | `TCP_TMR_INTERVAL/4` = 62.5 мс | фактический шаг `tcp_fasttmr` |
| `TCP_WND_UPDATE_THRESHOLD` | `TCP_WND/4` = 2920 | порог немедленного ACK окна |
**Важно:** таймер uasync использует timebase 0.1 мс — `uasync_set_timeout(ua, N, …)`
задаёт N×0.1 мс. Например, 5000 = 500 мс.
## Контракт recv_cb — главный источник багов
lwIP вызывает `recv_cb(arg, pcb, pbuf, err)` для данных, **уже извлечённых из pcb**.
Дальше приложение само решает судьбу данных и окна:
| Действие | Результат |
|----------|-----------|
| скопировать + `tcp_recved(pcb, len)` | данные приняты, окно восстановлено (норма) |
| `return != LERR_OK` | lwIP кладёт pbuf в `refused_data` и отдаст позже — **без потери**, окно закрывается |
| `pbuf_free(p)` без `tcp_recved` | данные потеряны, окно не восстановлено → схлопывание окна |
`refused_data` — штатный backpressure lwIP: одноканальный (один pbuf), ре-доставка
через `tcp_fasttmr` (62.5 мс) или при следующем входящем сегменте.
**Ошибка, из-за которой падал `tcp_proxy_full`:** при backpressure прокси делал
`pbuf_free(p)` без `tcp_recved`. Каждый такой сброс уменьшал `rcv_wnd` на MSS; после
~8 пакетов окно падало в 0, OS-TCP переставал слать, и передача умирала.
## Управление окном и «деградация 62.5 мс»
`tcp_recved(pcb, len)` увеличивает `rcv_wnd` и через `tcp_update_rcv_ann_wnd` решает,
слать ли ACK окна немедленно. Немедленный ACK идёт только если прирост окна
`wnd_inflation ≥ TCP_WND_UPDATE_THRESHOLD` (=2920 = 2×MSS); иначе ACK откладывается
до `tcp_fasttmr` (62.5 мс).
Отсюда классическая ловушка: если после backpressure окно восстанавливать **по одному
пакету** (`tcp_recved(1460)` за раз), прирост 1460 < 2920 → ACK уходит раз в 62.5 мс →
пир шлёт 1 пакет за 62.5 мс ≈ 23 КБ/с. Это выглядит как «пропускная способность
застряла», хотя сеть свободна.
**Правильно:** восстанавливать окно **пачкой** (несколько `tcp_recved` подряд либо один
`tcp_recved(TCP_WND_MAX - rcv_wnd)`), тогда прирост ≥ 2920 → ACK немедленный → пир
сразу возобновляет бурст.
## Рекомендуемый паттерн для relay-потребителя (как в `tcp_proxy_client.c`)
1. При получении данных — копировать в ограниченную очередь, **не вызывать**
`tcp_recved` (окно само плавно закрывается; очередь ограничена `TCP_WND`).
2. Дрейн очереди по сигналу освобождения нижележащего канала
(`etcp_router_on_send_ready`) + retry-таймер (force=1) как safety-net.
3. `tcp_recved(len)` вызывать **только после успешной отправки** — окно
восстанавливается пачкой по мере дрена, ACK уходит немедленно.
4. Никогда не сбрасывать pbuf без `tcp_recved`; не держать данные в `refused_data`
дольше одного пакета (single-slot).
См. реализацию: `tcp_proxy_client_tx_queue_drain_cb` / `tcp_proxy_client_fin_flush`
в `src/proxy/tcp_proxy_client.c`.
+530
View File
@@ -0,0 +1,530 @@
Аудит media_async / media_delivery — 2026-10-24
Статус: alias encrypted_file, граница voice_file, переанонс SQLite и расхождение MIME write/read исправлены.
Сверка двух каталогов завершена: все11 заголовков, sync.md, _desc.txt и encrypted_attachments.md.
Общий service_lifecycle.md проверен в media-части; разделы других сервисов не считаются закрытыми.
Ниже сохранена хронология воспроизведений и исправлений; прежние отметки незавершённости относятся к этапам.
Последние проверки MIME и итоговая архитектурная сверка — в конце.
Подтверждённый дефект: исходник уничтожается через другое имя того же файла
encrypted_file_write, src/media_async/encrypted_file.c:79-87, сравнивает только строки
source/destination. Разные строки могут обозначать один inode. fopen(destination,"wb")
усекает уже открытый source. Криптография затем успешно обрабатывает пустой файл.
Контейнер корректен для нулевого содержимого, но исходные данные потеряны; возврат равен 0.
Воспроизведено на настоящих функциях из src/libutun.a:
- source и source с компонентом /./: исходные30 байт заменены контейнером152 байта,
plaintext_size=0, rc=0, SHA256 исходника изменился;
- source и hardlink: тот же результат;
- encrypted_file_read тоже пропускает оба вида alias: контейнер182 байта заменяется
plaintext30 байт, rc=0, исходный ciphertext больше не существует в прежнем виде.
У read такая же проверка strcmp в строке118 и разрушающий fopen в строке142.
В текущем групповом pipeline вызывающий выбирает отдельный .part; воспроизведение
доказывает дефект файлового API, а не наблюдавшуюся потерю реального пользовательского вложения.
Ошибку нельзя замаскировать описанием одинаковых файлов как успешного no-op:
шифрование и расшифровка должны создавать другое содержимое.
Диагностика:
- /tmp/media-encrypted-alias-repro.c — четыре проверки, сверяются inode, размеры и SHA256;
- /tmp/media-encrypted-alias-repro.mk — использует цели/библиотеки/флаги tests/Makefile;
- /tmp/media-encrypted-alias-repro.log — полный журнал с media=debug;
- /tmp/media-encrypted-alias-lvVS77/ — сохранённые входы и результаты опыта.
Проектные .c/.h для воспроизведения не менялись.
Согласованная архитектура исправления
Одной проверки file_io_paths перед fopen(wb) недостаточно для полного исправления
контракта владения. Сейчас encrypted_file удаляет destination при ошибке, а
ak_file_done повторно удаляет заранее вычисленный .part даже при раннем отказе.
Нужно убрать открытие произвольного назначения с усечением и очистку по одному имени.
Граница модулей
- lib/file_io: UTF-8/regular-file проверки, идентичность, эксклюзивный temp,
flush/sync/close, atomic rename, владение temp и защита от orphan cleanup.
- encrypted_file: создание/проверка контейнера в собственном temp; без публикации
конечного имени, SQLite и состояния async-задачи.
- chat_attachment: задача владеет temp, выбирает конечное имя и выполняет одну
публикацию; completion меняет состояние БД/UI и освобождает только свои ресурсы.
Подготовка, sync и rename остаются в worker, как сейчас; новые файловые I/O в uasync не добавляются.
Предлагаемый контракт write/read
1. Функция получает source, конечный target_path и указатель на обнулённый file_io_temp.
target_path задаёт место будущего результата; он не открывается с усечением.
До начала работы file_io_paths(source,target_path) обязан вернуть 0.
Совпадение строки, /./, hardlink и конечная ссылка назначения отклоняются без изменений.
2. Источник открывается общим file_io_open_read. Только после успешного открытия
создаётся уникальный temp рядом с target_path через file_io_temp_open.
На POSIX он имеет права 0600; создание эксклюзивное, владелец сразу учтён в общем реестре.
3. Контейнер пишется/проверяется через уже открытый FILE этого temp. Нет второго .part,
второго открытия выхода по пути или дополнительного слоя публикации внутри codec.
4. Полностью проверяются данные, закрытие входа и durable завершение выхода.
При ошибке удаляется только созданный этой операцией temp; существующие source/target сохраняются.
При успехе возвращается закрытый, синхронизированный temp, который ещё не опубликован.
Результат info/type у read присваивается только после полного успешного завершения подготовки.
5. Владелец file_io_temp размещается прямо в ak_file и имеет стабильный адрес до done;
копировать его структуру нельзя: реестр хранит адрес владельца.
Функции не принимают уже активный output и не уничтожают его при отказе аргументов.
Единая политика durability
В file_io_temp_open добавить flags, в file_io_temp хранить выбранный режим на весь
срок операции. FILE_IO_DURABLE включает обе части: finish и publish.
Обычные пользователи передают 0 и сохраняют свою семантику; отдельные флаги в каждом
finish/publish не нужны. Это предотвращает случайное сохранение файла без сохранения имени.
Durable finish: fflush -> fsync/_commit того же открытого fd -> fclose. Ошибка любой
фазы сохраняет первый код ошибки; close выполняется и при отказе sync. Открывать путь
заново ради fsync не нужно. После отказа temp остаётся у владельца до discard.
Publish повторно проверяет идентичность source/target и принимает только закрытый temp.
На POSIX: rename и fsync каталога назначения; соседний temp находится в том же каталоге.
На Windows: общий wide API и MOVEFILE_WRITE_THROUGH. Родительский каталог вычисляется
без обрезания: имя без '/' означает '.', '/file' означает '/'.
Политика sync принадлежит file_io, а не формату зашифрованного контейнера.
Точка фиксации — успешный rename. До неё ошибка сохраняет старый target.
После rename ошибка fsync каталога означает: результат уже установлен, долговечность
имени не подтверждена. Нельзя обещать откат, удалять новый файл вслепую или восстанавливать
старое содержимое. В этот момент temp уже отсоединён от реестра и его имя в owner пусто.
Этот переход должен быть явно описан в file_io.h и проверен тестом.
Длительный fsync выполняется после снятия блокировки реестра временных файлов.
Владение задачи и отмена
ak_file хранит file_io_temp вместо temporary[...]. Worker после успешной подготовки
один раз вызывает file_io_temp_publish с тем же target и источником.
До publish поле temporary непустое; после успешного rename оно пустое даже при
ошибке directory sync. По этому переходу задача фиксирует факт своей публикации.
Completion всегда может вызвать discard: после rename он ничего не удаляет.
Cleanup при отмене/удалении канала учитывает отдельно действительно созданные/
заменённые артефакты. Вычисленный путь не является доказательством владения файлом.
При отказе до создания выхода нельзя удалять существующий target или его alias.
Отдельных async owners, refcount и фоновых потоков не требуется. Ключ по-прежнему сохраняется
до шифрования; ready получателя/публикация сообщения подтверждаются только после
успешного worker и проверки актуальности канала в completion. Авторская строка ключа
сохраняется до worker по прежним правилам. Общей транзакции ФС/SQLite нет: после rename
путь остаётся известен attachment_keys для последующей очистки/повторной операции.
Кеш и восстановление после crash
Активный temp защищён существующим file_io-реестром, в том числе после finish.
В chat_msg убрать специальное сохранение вычисленного cipher_path.part; конечный
cipher_path по-прежнему защищается attachment_keys, временный файл — своим owner.
После crash реестра нет: такие temp допустимо удалить.
Нельзя ограничиться нынешним orphan walk дерева media: plaintext-temp находится
в decrypted, а ak_recover_preparations знает только фиксированный .part.
В init/очистке после завершения workers нужно удалять оставшиеся временные файлы
строгого собственного формата <UUID>.uenc.tmp.<32hex> / <UUID>.file.tmp.<32hex>
в управляемых каталогах media/decrypted. Использовать file_io_remove_orphan,
чтобы сохранить активные temp другого instance того же процесса. Конечные файлы,
ключи и данные иных форматов такой проход не удаляет; ошибка обхода логируется.
Область изменений
Основные файлы: lib/file_io.c/h, media_async/encrypted_file.c/h,
chat/chat_attachment.c, chat/chat_msg.c, test_encrypted_file/test_media_primitives
и сетевые проверки зашифрованных вложений. Остальные вызовы file_io_temp_open
в media_async/media_delivery/video переводятся на явные flags=0.
DM не вызывает encrypted_file_write/read: он использует media_crypto streams
и общие sync/publish helper-функции из encrypted_file.h. При правке общего слоя
необходимо сохранить их durable контракт; перенос общих операций в file_io должен
быть прямым, без совместимых обёрток и без изменения DM state machine.
Формат контейнера, nonce, протокол ключей и схема БД для этого фикса не меняются.
Регрессии и критерии готовности
- Обе операции: одинаковая строка, /./, hardlink, symlink, отсутствующий/необычный source;
source и прежний target не меняются, alias не удаляется, чужой temp не освобождается.
- Обычные файлы размером 0/1/32767/32768/32769 байт, большой файл, UTF-8 пути;
успешная подготовка не меняет target до publish. Read не публикует info/type при ошибке.
- Внедрение ошибок open/fdopen/read/write/seek/flush/fsync/input-close/output-close/
rename/directory-sync/RNG; отсутствие утечек fd/памяти/регистраций temp.
- Подмена target на hardlink между подготовкой и publish: повторная проверка
отклоняет публикацию, исходник сохраняется, удаляется только собственный temp.
- Отказ directory sync после rename: новый файл цел, temp-owner отсоединён,
ошибка не представляется как откат; повторная операция корректна.
- Orphan cleanup во время work, после finish, после publish; crash/restart
в обоих деревьях, чужие имена сохраняются. Отмена, stop и удаление канала
до work, после подготовки и после rename: один done, нет публикации сообщения/ready.
- Профильные 20 тестов, ASAN/UBSan/LSan и полный набор. Изменение file_io требует
также проверки его прежних пользователей: copy, video и media blocks/download.
Этот проект реализован. Успешные 20 тестов ниже относятся к исходной реализации;
проверки исправленного кода приведены в конце.
Что проверено и что осталось
Прочитаны все шесть пар .c/.h media_async, целиком file_transfer.c/h, заголовки
media_delivery/download/index/proto, sync.md и doc/encrypted_attachments.md.
Полная повторная сверка остальных реализаций media_delivery и исправление контрактов
ещё не выполнены. Прежние [V] в общем checklist относятся к предыдущим аудитам;
новые незавершённые пункты отмечены [~].
Linux: ./build.sh --no-gui -j4 — PASS;20/20 профильных тестов — PASS:
media_primitives, attachment, attachment_send, encrypted_file, encrypted_attachment,
media_async, media_index, media_index_regressions, media_delivery_sql,
media_delivery_download, media_download_timeout, media_query, media_blocks,
media_replication, media_delivery_integration, media_delivery_full,
media_delivery_chat, dm_media, dm_worker, services.
Логи: /tmp/media-audit-build.log, /tmp/media-doc-audit-results.log,
/tmp/media-doc-audit-logs/. Штатный test_encrypted_file не покрывает alias.
Старые сбои encrypted_attachment/media_delivery_chat в этом прогоне не воспроизвелись;
один PASS не закрывает задачи о нестабильности.
На исходном этапе полный check.sh и новые sanitizer-прогоны не запускались.
Результаты исправления — 2026-10-24
- file_io_temp получил единый FILE_IO_DURABLE: sync открытого fd при finish,
write-through rename на Windows и directory sync после rename на POSIX.
После rename temporary пуст, включая отказ directory sync; discard не удаляет результат.
Общие sync/publish функции перенесены в file_io; DM вызывает их напрямую.
- Write/read готовят отдельный закрытый temp. Source/target не открываются с усечением;
совпадающая строка, /./, hardlink и конечная ссылка отклоняются до создания выхода.
Read выдаёт info/type только после успешного закрытия/сохранения проверенного результата.
- ak_file хранит один output, учитывает подготовленный plaintext snapshot и выполненный rename.
Worker публикует один раз; completion очищает только собственные артефакты.
Кеш использует реестр владельцев; recovery очищает строгие temp в media и decrypted.
Построение путей и SQL recovery используют одну каноническую media_base.
- test_encrypted_file: прежние шесть размеров, восемь alias-сценариев обоих API,
активный output, alias перед publish и 23 отказа I/O с контролем fd/temp/метаданных.
До rename прежний target сохраняется; после отказа directory sync новый файл установлен.
- test_media_primitives: durable и обычный режим, sync failure до rename, failure после
rename, относительный/корневой каталог, защита активного temp в worker и после close.
- test_attachment_send детерминированно ждёт настоящего crypto rename без обработки
completion, затем останавливает сервис: нет сообщения/ключа/оставшихся файлов/temp.
- test_encrypted_attachment при каждом startup/restart проверяет оба дерева:
stale temp удалены; открытый/закрытый активные owners, конечные файлы и чужие имена сохранены.
- Профильный Linux-набор: 21/21 PASS, включая video/no_ffmpeg, DM, services и media delivery.
Основной make check: 185/185 PASS. TUN в отдельном network namespace с тем же бинарником:
proxy9/9, burst1/1, load8/8 PASS; 134217728 проверенных байт.
- ASAN/UBSan/LSan: 21/21 PASS с detect_leaks=1. Все встроенные зависимости собраны
с sanitizer-флагами в /tmp/utun-media-fix-sanitizers; обычные объекты проекта не смешивались.
Windows/FreeBSD build/runtime не проверялись.
Логи: /tmp/media-fix-profile-results.log, /tmp/media-fix-full-check.log,
/tmp/media-fix-network-check.log, /tmp/media-fix-sanitizer-results.log,
/tmp/media-fix-cancel-after-rename.log, /tmp/media-fix-encrypted-file.log.
Проверка реализации не выявила противоречий владения в перечисленных сценариях.
Частичные отметки остальных файлов аудита сохраняются; этот фикс не означает полного
завершения проверки media_async/media_delivery или закрытия старых нестабильных тестов.
Продолжение аудита — 2026-10-24: граница длительности voice_file
voice_file.h обещает максимум24ч; attachment_validate (attachment.c:35) отвергает
duration_ms >86400000. Однако voice_file.c:16 проверяет samples/channels/rate >86400:
целочисленное деление отбрасывает долю секунды. Записи с полными кадрами на24ч+20..980мс
проходят проверку аргументов. Затем voice_file.c:37 вычисляет точную длительность
в миллисекундах по числу20мс кадров, уже выходящую за предел метаданных.
attachment_send.c вызывает voice_file_encode в worker, а attachment_validate —
при публикации результата. При успешном полном кодировании такой результат будет
отклонён после подготовки файла. Это дефект границы записей длиннее суток;
сбой обычных коротких записей или потеря данных этим опытом не установлены.
Диагностическое воспроизведение на настоящем voice_file_encode из src/libutun.a:
- /tmp/media-voice-duration-repro.c и .mk; сборка через tests/Makefile и его библиотеки/флаги.
- Linux64: виртуальный read-only PCM достаточного размера, без чтения или заполнения.
Linker wrapper opus_codec_encoder_create фиксирует достижение кодировщика
и возвращает NULL до чтения PCM/открытия output. Реальная проверка аргументов не подменена.
- Частоты8000/12000/16000/24000/48000, mono/stereo; для каждой пары проверены
24ч-20мс, ровно24ч, +19мс хвоста, +20мс, +980мс и +1000мс: всего60 случаев.
- Ровно24ч и +19мс хвоста проходят, так как хвост короче полного кадра отбрасывается;
+1000мс отвергается до encoder. На +20/+980мс во всех10 сочетаниях codec достигнут,
тогда как attachment_validate соответствующей длительности возвращает-1:
duration_guard_mismatches=20, confirmed=1.
- Все60 вызовов возвращают-1: для допустивших encoder это намеренная инъекция отказа,
для +1000мс — штатная валидация. Опыт доказывает ошибку допуска к encoder,
но не является24-часовым кодированием или сквозной проверкой отправки такого файла.
- Полный журнал с media=info/debug=info: /tmp/media-voice-duration-repro.log;
сборка: /tmp/media-voice-duration-build.log. Проектные .c/.h не менялись.
Проект простого исправления:
1. Задать один ATTACHMENT_DURATION_MAX_MS=86400000 в attachment.h; использовать
в валидаторе voice/video и проверке video completion вместо отдельных литералов.
2. После проверки rate/channels считать frame_samples=(rate/50)*channels,
frames=count/frame_samples. До создания encoder/открытия файла отклонять
frames<25 либо frames>ATTACHMENT_DURATION_MAX_MS/20 (4320000).
Проверяется длительность записываемых полных кадров, хвост <20мс по-прежнему отбрасывается.
3. Только после ограничения frames считать samples=frames*frame_samples
и duration_ms=frames*20. Размер samples не превосходит count, длительность
помещается в uint32_t; большие count не требуют потенциально переполняющего умножения на20.
4. Сообщение раннего отказа различает неверные параметры и недопустимую длительность;
для длительности выводит count/rate/channels/frames и разрешённые границы.
Дополнительный helper/API или изменение pipeline/формата не нужны.
5. В test_attachment добавить проверку границ500мс/24ч, хвоста <20мс,
превышения на полный кадр, разных rate/channels и раннего отказа без изменения файла/метаданных.
Верхние границы проверять диагностической точкой до encoder без реального кодирования суток;
сохранить существующий настоящий Opus roundtrip короткой записи.
На этапе подтверждения аудит был остановлен по AGENTS.md:10, без изменения рабочего кода.
Пользователь согласовал исправление; результаты приведены ниже.
Результаты исправления voice_file — 2026-10-24
- ATTACHMENT_DURATION_MAX_MS в attachment.h задаёт один предел86400000мс:
его используют валидатор voice/video, video completion и кодировщик голоса.
- voice_file считает frames=count/frame и проверяет25..4320000 до создания Opus
и открытия выходного файла. Метаданные при раннем отказе не изменяются.
Хвост <20мс сохраняет прежнее поведение: отбрасывается, включая хвост после ровно24ч.
- Проверка архитектуры: frame всегда положителен и не больше1920 для разрешённых rate/channels;
деление count/frame не переполняется. После ограничения frames произведение frames*frame
не превосходит исходного count; frames*20 не превосходит86400000 и помещается в uint32_t.
Подготовка, публикация, владение PCM/файлом и формат контейнера не меняются.
- Лог раннего отказа содержит count/rate/channels/frames и разрешённые границы;
debug принятой записи показывает число кадров, длительность и отброшенный хвост.
- test_attachment: 120 граничных вызовов для пяти частот и mono/stereo.
Все70 недопустимых длительностей отвергнуты до encoder; все50 допустимых
дошли до диагностического encoder с инъекцией отказа. Во всех120 случаях
побайтно сохранены исходные metadata и содержимое существующего output.
Linux64 использует виртуальный read-only PCM; суточное кодирование не выполняется.
- Настоящий Opus encode/decode: прежняя1с запись и десять записей на500мс с хвостом
почти20мс, все rate/channels, все три preset. Проверены header, декодирование,
число20мс кадров, EOF и валидность метаданных результата.
- validate/encode/decode voice/video принимают86400000мс и отвергают86400001мс.
- ./build.sh --no-gui -j4 — PASS. В изменённых C-файлах/тесте предупреждений сборки нет;
общие прежние предупреждения остальных модулей остаются в полном логе.
- Профильные тесты10/10 PASS: attachment, attachment_send, encrypted_file,
encrypted_attachment, media_async, dm_media, dm_worker, services, video, video_no_ffmpeg.
Первый запуск attachment внутри sandbox выдал запреты создания stream fd;
весь профильный набор, включая attachment, успешно повторён вне sandbox.
Логи: /tmp/media-voice-fix-build.log, /tmp/media-voice-fix-profile-results.log,
/tmp/media-voice-fix-profile-logs/test_attachment.log и остальные логи этого каталога.
Новый полный make check и ASAN/UBSan/LSan не запускались; прежние результаты encrypted_file
выше не относятся к этому изменению. Windows/FreeBSD не проверялись.
voice_file.h и attachment.h сохраняют [~]: завершение этого фикса не закрывает
оставшуюся сверку контрактов всего аудита. Повторная проверка media_delivery ещё не завершена.
Продолжение аудита — 2026-10-24: переанонс записей SQLite
Полностью прочитаны media_index.c/h, media_download.c/h, media_delivery.c/h,
file_transfer.c/h, media_delivery_proto.h и sync.md. Повторно сверены media_crypto.c/h.
В media_crypto.h уточнены native байты nonce, границы CCM/буферов, пустой поток,
момент выдачи размеров/хеша и отсутствие close/fsync/rename; неверный аргумент не обнуляет выходы.
В file_transfer.h уточнены lookup/авторизация, 16 операций, размер1..INT64_MAX,
усечение своего временного назначения даже при отказе запуска, таймаут и отмена/владение callback.
Изменения только описаний сохранены коммитом5dbca4a2.
Подтверждённый дефект
media_delivery_announce_local_blocks (media_delivery.c:1610) читает media_id/block_id
через sqlite3_column_blob, но проверяет только ненулевой указатель. Затем
media_delivery_send_have_block копирует по16 байт в пакет и подписывает объявление.
media_files имеет BLOB NOT NULL без CHECK длины: такие строки допустимы текущей схемой.
Обычные media_index writers пишут правильные длины; опыт моделирует повреждённые
локальные данные, а не доказанный путь внесения повреждения удалённым участником.
Настоящие SQLite, схема, обработчик, выбор группы/суперузла и Ed25519; перехвачена
только транспортная отправка в существующем tests/media_delivery_fixture.h:
- Контрольные UUID16/16, chunk0, chat_id111: один корректный HAVE_BLOCK.
- media_id длиной1 байт: один HAVE_BLOCK с16-байтным media_id, из них15 байт вне BLOB.
В журнале вместо ожидаемого единственного7a видны посторонние ненулевые байты памяти.
- block_id длиной1 байт: аналогичное чтение15 лишних байт в сетевой block_id.
- media_id или block_id длиной17 байт: отправка с молчаливым усечением до16.
- chunk=-1: wire_chunk=4294967295; chunk=4294967296: wire_chunk=0.
Причина — sqlite3_column_int с последующим приведением к uint32_t без диапазона.
- chat_id111junk: strtoull без endptr принимает префикс111 и направляет объявление в группу111.
- Итого8 случаев,7 повреждённых строк из7 отправлены; invalid_records_sent=7, confirmed=1.
Ошибки соответствующих полей в модуле не логируются. Пакеты сохранены только стендом,
реальные удалённые узлы в опыте не участвовали. Ошибка ASAN или падение не воспроизводились.
Артефакты: /tmp/media-announcement-audit.c, /tmp/media-announcement-audit.mk,
/tmp/media-announcement-audit-build.log, /tmp/media-announcement-audit.log.
Сборка через tests/Makefile с настоящими src/libutun.a/libuasync.a и штатными флагами;
проектные .c для диагностики не менялись. Очистка собственного стенда исправлена и повторно проверена.
Отдельно по коду видно: результаты bind/завершающего step/finalize игнорируются;
SQL-ошибку окончания обхода нельзя выдавать как обычное «done». Инъекция SQL-ошибок здесь не выполнялась.
Проект простого исправления
1. Проверять строку на границе чтения SQLite до доступа как к фиксированным структурам:
media_id/block_id имеют тип BLOB и ровно16 байт; проверить полученные указатели.
Скопировать прошедшие проверку UUID в локальные массивы до вложенных SQL/отправок.
2. chunk читать как int64: тип INTEGER, 0..UINT32_MAX, только затем cast.
chat_id — TEXT, длина1..20 без embedded NUL, каноническое ненулевое uint64 decimal.
Полный разбор должен отвергать суффиксы, знаки, пробелы, leading zero и переполнение;
строка канала должна сохранять свой смысл при переводе в router group_id.
3. Плохую строку пропускать с WARN, указав rowid/типы/длины/некорректное поле,
без чтения за BLOB и без анонса. Корректные соседние строки продолжать обрабатывать.
Никакого общего выделяемого снимка или отката уже отправленных корректных анонсов не требуется.
4. Проверять bind, terminal step и finalize; SQL-ошибки логировать с фазой/кодами
и количеством обработанных строк. Не превращать их в обычный успешный финальный лог.
Сохранить существующий void API, поштучные анонсы и wire-формат.
5. Регрессии в штатной media-цели: valid row, длины UUID0/1/15/16/17 и неверные SQL-типы,
chunk=-1/0/UINT32_MAX/UINT32_MAX+1, неверные/граничные chat_id, embedded NUL;
смесь корректных/плохих записей и ошибки SQL. Перехват HAVE_BLOCK обязан доказать,
что наружу ушли только исходные16 байт проверенных UUID и правильный номер группы/блока.
Аудит остановлен по AGENTS.md:10 после подтверждения дефекта и подготовки проекта исправления.
Новые полные build/check/sanitizer-прогоны не запускались: изменения в репозитории только документальные.
media_crypto.h и file_transfer.h можно отметить [V]; media_delivery.h/proto.h остаются [~]:
нужно исправление этого пути и завершение сверки описаний. Остальные прежние отметки не закрывают
весь аудит. Остались контракты attachment/attachment_send/voice, связанные encrypted/lifecycle документы
и устаревший _desc.txt (RTT-подбор, direct-only репликация и её интервалы расходятся с текущим кодом).
Результаты исправления SQLite-переанонса — 2026-10-24
- Проверка остаётся внутри существующего обхода media_files. UUID допускаются только
как BLOB ровно16 байт; chunk — INTEGER в0..UINT32_MAX, читается через int64.
Исходные SQL-типы получены до преобразований. Проверенные UUID копируются
в локальные16-байтные массивы до вложенных запросов/отправок.
- chat_id имеет тип TEXT, длину1..20; разбор по объявленной длине отвергает embedded NUL,
знаки/пробелы/суффиксы, ноль/leading zero и переполнение. Допускается весь ненулевой uint64,
включая UINT64_MAX. Условие gid<=(UINT64_MAX-digit)/10 до операции исключает переполнение.
- Непроверенные фиксированные memcpy и сужающие cast устранены. Некорректная строка
пропускается с WARN: id/поле, типы/длины, исходный chunk. Хорошие соседние строки продолжают обрабатываться.
- prepare/bind/завершающий step/finalize проверяются. Отказ SQL логирует фазу/коды
и неполный обход; обычный complete-лог не выдаётся. Счётчики rows/eligible/skipped
описывают обработку, а не гарантированную доставку. Уже корректно отправленные пакеты не откатываются.
- Архитектура сохраняет существующие void API, wire-формат, источник ролей и единичные анонсы;
нет общего снимка/новых ресурсов, транзакций или состояния. Statement закрывается и при ошибке bind/step.
- test_media_delivery_sql: 36 случаев полей,32 недопустимых строки не отправляются,
четыре допустимые сохраняют точные UUID/group/chunk/timestamp. Проверены границы
UINT32_MAX/UINT64_MAX, все заявленные длины UUID и неверные SQL-типы, embedded NUL.
В смеси valid/invalid/valid отправлены только два корректных анонса.
- Linux: пять внедрений prepare/bind/first-step/step после первой строки/finalize.
До отказа сохранены0/0/0/1/2 корректных отправки, statement не остаётся активным;
проверяется ERROR с фазой и отсутствие ложного complete. После каждого отказа
обычный повтор успешно отправляет обе строки с исходными UUID и номерами блоков.
После захвата файлового лога тест явно восстанавливает console_enabled.
- ./build.sh --no-gui -j4 PASS; в изменённых C-файлах предупреждений нет.
Профиль12/12 PASS: delivery_sql, query, blocks, replication, index/index_regressions,
delivery_download, download_timeout, delivery_integration/full/chat, services.
После изменения только вывода диагностики SQL-тест повторно прошёл отдельно.
- ASAN/UBSan/LSan4/4 PASS: delivery_sql/query/blocks/replication, detect_leaks=1.
Свежая копия /tmp/utun-media-announcement-sanitizers содержит текущий код;
CPPFLAGS=-fsanitize=address,undefined применён и к встроенным зависимостям.
Обычные объекты проекта не смешивались с sanitizer-объектами.
Первый configure внутри sandbox отклонён LeakSanitizer из-за ptrace;
сборка и тесты успешно выполнены вне sandbox.
Логи: /tmp/media-announcement-fix-build.log, /tmp/media-announcement-fix-profile-results.log,
/tmp/media-announcement-fix-sql-final.log, /tmp/media-announcement-sanitizer-build.log,
/tmp/media-announcement-sanitizer-tests-build.log, /tmp/media-announcement-sanitizer-results.log.
Полный make check и Windows/FreeBSD не запускались. Завершение фикса не означает завершения
сверки оставшихся заголовков и документов двух каталогов.
Продолжение сверки описаний — 2026-10-24: MIME контейнера
Сверены и уточнены attachment.h, attachment_send.h, voice_file.h, media_delivery_proto.h
и комментарий неиспользуемого HAVE_BLOCK timeout в media_delivery.h:
- attachment: UTF-8 и только ASCII C0/DEL, границы полей по kind, состояние выходов
при ошибке encode/decode, content_type не является валидатором kind.
- voice_file: собственное перезаписываемое назначение, поздняя ошибка close может
изменить info; native контейнер и отсутствие fsync/атомарной публикации.
- attachment_send: владение начинается при вызове trampoline, при отказе самого post
остаётся у caller; PCM/compressor, контекст освобождения, DM generation, канальные
request_id/reply, внутренний UUID подготовки и события подготовки вместо обещания доставки.
- proto: вне SYNC числа native-endian; SERVE_REG/LEAVE только вспомогательный учёт,
загрузчик их не посылает. SERVE_ACK не отправляется; приём SERVE_ACK/HAVE_BLOCK_ACK игнорируется,
ожидания ACK/его таймера нет. Не менялись команды, wire или runtime-поведение.
Коммиты описаний:08692fa4 и4e4c60ee. Из общего service_lifecycle повторно сверена
только media-секция111..196 по текущему порядку start/quiesce/destroy и прочитанным модулям;
за пределами media документ не считается полностью проверенным.
Подтверждённый дефект MIME
encrypted_file_write проверяет только ненулевой type и strlen(type)<32, затем шифрует строку.
encrypted_file_read требует непустую NUL-терминированную строку в32-байтном поле,
ASCII33..126 без '"' и '\\'. Поэтому write может успешно подготовить непригодный для read контейнер.
chat_attachment_send (chat_attachment.c:198..225) имеет такую же слабую входную проверку:
до сохранения ключа/старта worker проверяется лишь наличие указателя и длина.
Реальный roundtrip, без подмены криптографии или файловых функций:
- Контроль application/octet-stream: write=0/read=0, hash расшифрованного temp равен исходному.
- Пустой MIME, перевод строки, двойная кавычка, обратная косая черта и не-ASCII UTF-8:
все пять write=0, после publish и вычисления точного внешнего hash соответствующий read=-1.
Ключ, author, UUID и контейнер при чтении те же. Ошибка — invalid metadata/type,
а не аутентификация или порча контейнера в опыте.
- При ошибке read прежние info/type сохранены, temp очищен; SHA256 исходника неизменен во всех6 случаях.
Это подтверждённый дефект принятия аргумента в API. Сквозная отправка/открытие карточки GUI не моделировалась.
- /tmp/media-content-type-audit.c и .mk, сборка через tests/Makefile и настоящие библиотеки;
/tmp/media-content-type-audit-build.log, /tmp/media-content-type-audit.log.
Артефакты: /tmp/media-content-type-audit-FdtMC3/. Лог содержит MIME как hex, без управляющих символов в тексте.
Проект простого исправления
1. Экспортировать encrypted_file_validate_type в encrypted_file.h: 0/-1, bounded strnlen(type,32),
непустая строка длиной1..31, ASCII33..126 кроме двойной кавычки и обратной косой черты.
Это ограничение существующего контейнера, не новый полноценный MIME parser и не правило для всех сообщений чата.
Диагностика показывает длину/позицию/hex ошибочного байта, не выводит сырую непроверенную строку.
2. Вызывать один валидатор в chat_attachment_send до выделения задачи/генерации ключа/записи в БД,
в encrypted_file_write до открытия source/temp и в read для расшифрованного32-байтного поля.
Существующие проверки длины расшифрованного блока сохраняются; правило MIME не дублируется в трёх местах.
3. Не менять формат, nonce, crypto, DB state machine или файловое владение. Reader остаётся строгим;
writer не должен выдавать успех для метаданных, которые этот же reader не принимает.
4. Регрессии: NULL/пустая строка/31/32 байта, каждый запрещённый символ, ASCII-границы
и валидные значения; writer на раннем отказе сохраняет source/target/output.
Для reader плохие metadata шифруются тестом с известным ключом и пересчитанным внешним hash:
отказ не теряет прежние info/type и не оставляет temp. Проверить send-отказ без строки ключа/worker/файлов.
На этапе воспроизведения аудит был остановлен по AGENTS.md:10. Затем пользователь разрешил продолжать исправления.
Реализация и проверка MIME-фикса — 2026-10-24
- encrypted_file_validate_type использует bounded strnlen(type,32) и одно правило для send/write/read.
NULL, пустая/нетерминированная строка и недопустимый байт отклоняются с WARN: длина/позиция/hex.
Успешная проверка имеет DEBUG-лог; сырые непроверенные строки в лог не попадают.
- send проверяет type до выделения задачи, генерации/сохранения ключа и старта worker.
write отказывает до файловых операций и изменения output. read проверяет уже аутентифицированное
поле после проверки длины metadata и сохраняет атомарное присваивание info/type только при успехе.
Валидатор не выполняет I/O и разрешён в uasync; файловые write/read остаются worker-only.
- Правило сохраняет действующий набор reader, включая короткое значение "a"; это не полный MIME parser.
Не добавлены новый wire, изменение nonce/crypto, состояние БД или второй владелец файлов.
Код:90964e7a/f4cc6920, регрессии:aace975a.
- test_encrypted_file: все256 значений первого байта,92 допустимых; NULL отдельно.
Восемь ранних отказов сохраняют output побайтно, source/cipher/target по SHA256.
Три настоящих roundtrip проверяют длины1/24/31, metadata, SHA256 plaintext и сохранность target до publish.
Семь плохих wire-полей зашифрованы настоящим CCM с пересчитанным внешним SHA256:
read отказывает, сохраняя info/type/старый target/source; temp и fd не остаются.
- test_attachment_send: восемь отказов до ключей/worker, число файлов и исходный байт сохранены.
Сохранён прежний сценарий реального encrypted worker, отмены после публикации и recovery.
- ./build.sh --no-gui -j4 PASS; в изменённых C-файлах предупреждений нет.
Профиль10/10 PASS: encrypted_file, attachment_send, encrypted_attachment, attachment, media_async,
services, media_delivery_chat/full, dm_media, dm_worker.
- ASAN/UBSan/LSan4/4 PASS: encrypted_file, attachment_send, encrypted_attachment, services.
detect_leaks=1, halt_on_error=1; встроенные зависимости также инструментированы через CPPFLAGS.
Отдельная копия /tmp/utun-media-mime-sanitizers исключает смешение обычных/sanitizer-объектов.
Первый build остановился из-за нехватки /tmp; очищены воспроизводимые объекты прежних копий,
затем повторная сборка и весь прогон завершились успешно. Исходники и журналы сохранены.
Логи: /tmp/media-mime-fix-build.log, /tmp/media-mime-fix-profile-results.log,
/tmp/media-mime-fix-profile-logs/, /tmp/media-mime-sanitizer-build-retry.log,
/tmp/media-mime-sanitizer-tests-build.log, /tmp/media-mime-sanitizer-results.log,
/tmp/media-mime-sanitizer-test_*.log. Полный check и Windows/FreeBSD для MIME-фикса не запускались.
encrypted_file.h теперь согласован с реализацией. Последующая сверка общих описаний завершена ниже.
Завершение аудита media_async/media_delivery — 2026-10-24
- Все11 публичных заголовков двух каталогов сверены по полным функциям и структурам реализаций:
media_async/crypto, attachment/send, encrypted_file, voice_file, media_index/download/delivery/proto,
file_transfer. Уточнены назначения, входы/выходы, владение и потоки, ошибки и wire-ограничения.
- _desc.txt заменён актуальным описанием подготовки, поиска/загрузки, relay, каталога и репликации.
Убраны прежние предположения о RTT-выборе, только прямых соединениях, часовых повторах и ACK-окне.
Отражены фактические QUERY/CM/transfer UUID, пределы выдач, per-group RX-курсор и атомарный приём страницы.
sync.md уточняет отсутствие ожидания HAVE ACK и проверки SQLite при локальном переанонсе.
- encrypted_attachments.md целиком сопоставлен с chat_attachment.c и источниками desktop/Android UI:
inputbar, MessageList/attachment_key_ui, JNI snapshot, ChatRepository, MessageBubble/Reply/ChannelList.
Уточнены MIME, видимость desktop-кнопки при наличии pending requests и preview цитаты после расшифровки.
Контейнер не выдаётся UI как готовый файл; путь/метаданные заменяются после готовности plaintext.
Это сверка исходников, без нового интерактивного GUI/Android runtime-прогона.
- media-раздел service_lifecycle.md сверён по текущему start/quiesce/join/done/destroy и владельцам
worker/temp/CM/relay. Дописано, что ранний MIME-валидатор допустим в uasync и не выполняет I/O.
Общий документ сохраняет частичную отметку: голос, звонки и другие немедийные владельцы вне этой проверки.
- Архитектура исправлений остаётся простой: формат/валидация контейнера принадлежат encrypted_file,
temp/durability — file_io, публикация и БД/UI — задаче chat_attachment; один владелец на ресурс.
Media_index регистрирует подготовленное, delivery проверяет передачу и ведёт каталог своей группы.
Синхронные приём/проверка/сборка delivery в uasync и отсутствие общей транзакции ФС/SQLite
явно описаны как действующие границы. File_transfer оставляет ключи/авторизацию/проверку caller.
- Все четыре воспроизведённых дефекта исправлены и имеют регрессии. Неразобранных подтверждённых
дефектов в рамках этого аудита не осталось. Последние изменения после MIME-прогона — только описания;
git diff --check проходит. Для MIME действуют профиль10/10 и ASAN/UBSan/LSan4/4 выше;
прежние полные185/186 и sanitizer21/21 не приписываются последующим исправлениям.
Коммиты общей сверки:91fed902 (delivery),8d175049 (encrypted/UI/lifecycle).
Чек-лист: все media-заголовки и encrypted_attachments.md — [V], общий service_lifecycle.md — [~].
Windows/FreeBSD, новый полный check и интерактивные GUI/Android проверки не выполнялись.
+380
View File
@@ -0,0 +1,380 @@
Файлы участников CHAT-группы
===========================
Модуль: src/member_files/. Desktop-браузер: tools/chatgui/src/member_files/.
Отдельный сервис router ETCP_RT_ID_MEMBER_FILES (0x39), отдельно от вложений
сообщений media_delivery и E2E-файлов DM. Серверы выбираются явно; между
серверными деревьями автоматической репликации нет.
Хранение и конфигурация
----------------------
Корень по умолчанию: <db_path>/member_files/ (без db_path — ./member_files/).
Публичное дерево: <корень>/<group_id>/<node_id>/<относительный путь>.
Временные файлы: <корень>/.uploads/<upload UUID в hex>.
При старте сервис создаёт/открывает корень в дисковом worker, удаляет старые
незавершённые temp и сканирует опубликованные файлы. До готовности отвечает BUSY.
Ошибка сканирования оставляет локальное хранилище недоступным до restart;
частичные счётчики клиенту не выдаются. Клиентские передачи к другим серверам продолжают работать.
Очистка кэша вложений media/ это дерево не обходит.
Настройки [chatserver] / [chat]:
member_files_enabled=1
member_files_user_quota_mb=1024
member_files_upload_ttl_sec=3600
member_files_path=/путь/к/хранилищу (опционально, до 255 байт UTF-8)
Путь, квота и TTL читаются при старте чат-сервиса; изменение требует его restart.
enabled проверяется на каждом запросе. Лимит одинаков для каждого UID отдельно,
размер UID суммируется по всем группам на этом сервере. Значение в МиБ (1024*1024 байт).
Счётчики used/reserved/files/directories хранятся в памяти дискового worker и восстанавливаются с диска только при старте.
Каталоги не занимают байтовую квоту. files/directories считают опубликованные файлы и каталоги внутри деревьев UID
по всем группам; корни групп, корни UID и .uploads исключены. Пустой опубликованный файл считается файлом.
Сохранённые файлы удалённых участников также занимают квоту.
При начале загрузки резервируется размер нового файла или положительный прирост при замене.
WRITE не изменяет used; COMMIT обновляет used на разницу размеров и освобождает резерв.
ABORT/Cancel/TTL освобождают резерв после удаления temp; REMOVE вычитает размер файла.
Переименование внутри UID не меняет размер и числа объектов. Новый COMMIT увеличивает files на 1;
замена существующего файла, включая нулевой размер, сохраняет files. MKDIR увеличивает directories на 1,
REMOVE уменьшает соответствующий счётчик. Для mkdir учитывается факт создания даже при последующей ошибке sync.
BEGIN/WRITE/ABORT/Cancel/TTL не увеличивают files: temp не опубликован.
Все изменения корректируют счётчики инкрементально, без обхода каталога пользователя;
QUOTA только читает их из памяти. LIST выполняет свой обычный обход для отображения записей, не пересчитывая статистику.
Повторы BEGIN/COMMIT/MKDIR/REMOVE не меняют учёт повторно.
Уже превышенная квота не удаляет данные: чтение, удаление и уменьшение файла доступны,
новый положительный прирост запрещён. Физический temp может занимать больше места,
чем резерв прироста; нехватка места файловой системы остаётся отдельной I/O-ошибкой.
Нехватка квоты/места возвращает NO_SPACE; опубликованные файлы не вытесняются.
Общая member_files_quota_mb удалена. Корень обслуживается только файловым сервисом;
ручные изменения требуют повторного сканирования через restart.
Сервер — действующий полный мембер группы с подписанным adm_tags.storage="yes".
Колонка peers.storage — производный сетевой приоритет, для полномочий не используется.
Каждый запрос и ответ подписаны Ed25519 и зашифрованы end-to-end средствами
route_crypto. group/src берутся только из проверенного заголовка router;
ETCP peer промежуточного хопа не считается автором запроса.
Читать могут действующие участники той же группы. Изменять дерево может только
src == owner. Удалённые записи и source=1 (плейсхолдеры) доступа не дают.
Открытый fd/UUID загрузки не отменяют повторную проверку членства.
Файлы удалённого участника сохраняются на диске, но через сервис не доступны.
Wire, версия 4
--------------
Все числа LE, без сырых структур и padding. Фиксированный заголовок 125 байт:
0 protocol_version:u8 = 4
1 kind:u8 (1=request, 2=response)
2 command:u8
3 status:u16 (request=0)
5 request_id:u64, ненулевой случайный ID
13 owner:u64 (UID каталога; 0 только для LIST виртуального корня группы)
21 offset:u64
29 size:u64
37 cursor:u64
45 version:16
61 upload:16
77 hash:32
109 mtime:u64, Unix seconds
117 type:u8 (1=file, 2=directory)
118 more:u8 (0/1)
119 path_len:u16
121 target_len:u16
123 data_len:u16
После заголовка: path[path_len], target[target_len], data[data_len], без NUL.
Длина пакета обязана точно совпадать с указанными размерами.
Путь максимум 1024 байта, компонент максимум 255 байт, максимум 60 компонентов.
Данные максимум 8192 байт; вместе с полным router-путём и crypto укладываются
в лимит normalizer 16384 байта. Размер файла/смещение ограничены INT64_MAX.
Пути — строгий UTF-8, относительные, разделитель '/'. Запрещены пустые компоненты,
компоненты с ведущей '.', завершающей точкой/пробелом, управляющие символы,
Windows device names и символы \ : * ? " < > |. Корень внутри UID — пустой путь.
POSIX разрешает путь через *at/O_NOFOLLOW; regular-файлы с hardlink отвергаются.
Windows удерживает родительские каталоги без SHARE_DELETE и не принимает reparse points.
Команды
---------------
1 LIST: owner/path/cursor/version. Первая страница cursor=0, version=0.
Ответ data — записи: name_len:u16,type:u8,size:u64,mtime:u64,version:16,name.
cursor — сколько записей уже выдано. more=1 требует следующую страницу с
возвращёнными cursor/version. При изменении снимка — STALE, начать сначала.
Размер каталога не ограничивает память страницы. Виртуальный корень группы
выдаёт UID действующих полных участников, даже если каталог ещё не создан.
Изменение namespace сервера консервативно инвалидирует дисковые LIST-курсоры.
2 STAT: owner/path. Ответ type/size/mtime/version. Не хеширует весь файл.
version — непрозрачный снимок identity/size/mtime/ctime и эпохи сервера.
Закрытие fd по таймауту токен не меняет; restart меняет дисковые токены.
3 READ: owner/path/version/offset/size (1..8192).
Ответ size — полный размер файла, data_len — фактически прочитано,
more — есть ли байты после ответа. offset==EOF возвращает 0 байт/more=0;
offset>EOF — INVALID_RANGE. Изменение файла между блоками — STALE.
Клиент не смешивает версии и публикует локальный файл только по завершении.
4 BEGIN_WRITE: owner/path/upload/size/hash/version.
upload — 16 случайных байт, size — конечный размер, hash — SHA256 содержимого.
version=0 означает создать новый файл, существующий возвращает EXISTS.
Для замены требуется версия из STAT/LIST. В один путь одна активная загрузка.
UUID привязан к group/src/owner/path; повтор BEGIN с теми же параметрами безопасен.
5 WRITE: owner/path/upload/offset/size/data. size==data_len, диапазон внутри
конечного размера. Можно писать в произвольном порядке и повторять блок.
ACK подтверждает запись в temp, а не durable публикацию всего файла.
6 COMMIT: owner/path/upload. Проверяет SHA256, fsync/_commit и ожидаемую
версию назначения, затем атомарно заменяет файл и синхронизирует namespace.
Прежний файл виден до публикации. Ответ содержит размер/версию/SHA256.
Незаписанные sparse-области читаются как нули; неправильное содержимое не
проходит SHA256. Пустые файлы поддерживаются. При I/O-ошибке во время
публикации результат может быть неопределённым; нужно повторно проверить STAT.
7 ABORT: owner/path/upload. Удаляет temp, освобождает резервацию.
Неизвестный UUID — OK; опубликованные файлы никогда не удаляет.
8 MKDIR: owner/path. Создаёт один каталог; родители должны существовать.
9 RENAME: owner/path/target/version. Только внутри UID/группы, без замены
существующего назначения. Активная загрузка в затрагиваемом пути — BUSY.
10 REMOVE: owner/path/version. Файл или пустой каталог, без рекурсивного удаления.
Непустой каталог возвращает DIRECTORY_NOT_EMPTY.
11 STREAM_WRITE: открытие потоковой загрузки; поля как BEGIN_WRITE,
cursor — ненулевой ID потока. Ответ подтверждает готовность temp и конечный размер.
12 STREAM_READ: открытие потокового скачивания; owner/path/version/size/cursor,
upload — случайный токен клиента. Сервер сверяет снимок и размер перед началом.
13 STREAM_DATA: request_id — ID потока, owner/path/upload, offset/size/data.
kind=request для upload, response для download. Ответа на блок нет.
offset обязан равняться следующему принятому смещению; size==data_len, не более 8192.
14 STREAM_END: ID потока, owner/path/upload, offset==size==размер файла, SHA256.
Upload: клиент отправляет END после всех DATA, сервер отвечает END после COMMIT.
Download: сервер отправляет END после DATA и повторной проверки версии; клиент
проверяет SHA256 записанных данных, синхронизирует и публикует локальный файл.
Ошибка сервера приходит END.status!=OK; ошибка открытия — ответом на STREAM_READ/WRITE.
15 STREAM_CANCEL: клиент отменяет конкретный поток. Поздние DATA не публикуются.
16 QUOTA: owner=UID, path/target пустые, cursor=0, data_len=0.
Права чтения те же, что у STAT/LIST выбранного участника. Ответ data_len=40:
used:u64, limit:u64, reserved:u64, files:u64, directories:u64, LE.
Первые три значения в байтах, последние два — числа объектов. Снимок общий для UID во всех группах.
Не резервирует место и не занимает квитанцию мутации. used может превышать limit
после уменьшения лимита; сервер не выдаёт фиктивный нулевой размер при ошибке учёта.
Открытие потока — обычный повторяемый запрос с отдельным request_id. cursor связывает
его с DATA/END/CANCEL. Поток определяется group/peer/kind/ID; один ID не может
одновременно обозначать разные открытия. Каждый пакет по-прежнему подписан и зашифрован.
При закрытии/перезапуске router-канала его handles завершаются с IO_ERROR;
байты разных эпох не смешиваются. Возобновление через новую эпоху пока не реализовано.
Статусы (0..14): OK, ACCESS_DENIED, NOT_FOUND, INVALID_PATH, INVALID_RANGE,
EXISTS, BUSY, STALE, NO_SPACE, IO_ERROR, BAD_PACKET, HASH_MISMATCH, CANCELLED, TIMEOUT,
DIRECTORY_NOT_EMPTY.
Мутации COMMIT/MKDIR/RENAME/REMOVE сохраняют успешный ответ на 120 секунд
(64 квитанции); тот же request_id с другим содержимым — BAD_PACKET.
COMMIT дополнительно узнаётся по upload UUID. При заполнении квитанций новая
мутация возвращает BUSY до истечения старых. Клиент повторяет запрос с прежним
request_id раз в 5 секунд, общий срок 120 секунд. Повторы после этого срока
не дают гарантии того же результата; перед новой мутацией требуется STAT.
Потоки, таймауты и остановка
---------------------------
На экземпляр один дисковый pthread с очередью до 64 обычных задач. Сеть,
ACL, таймеры и state machines выполняются в uasync; open/stat/readdir/read/write,
resize/hash/sync/rename/remove/close — в дисковом потоке. GUI только передаёт
команды через bridge и получает события. Нет вызова дискового I/O из callback
таймера. Completion резервируется до постановки задачи; без polling workers.
Локальные файлы transfer и серверное дерево используют тот же дисковый поток.
Для обязательной очистки резервируется отдельная задача до открытия файла.
Обращение к своему серверу доставляется через отдельную ограниченную очередь
call_soon в тот же обработчик, без сетевого канала. ACL и дисковый поток общие;
сетевой recv не принимает unsigned даже при src == self.
Сервер держит до 64 открытых файлов. Каждый READ/WRITE/BEGIN продлевает idle.
После 10 секунд простоя fd закрывается на очередном дисковом sweep (таймер 1 с);
занятый дисковый поток откладывает sweep до освобождения. Закрытие не публикует
и не удаляет файл. Temp можно открыть заново до истечения upload TTL.
После restart незавершённые temp удаляются; опубликованные файлы остаются.
Лимиты: 16 клиентских запросов, 32 серверных задания/ответа, 16 временных
загрузок, 16 клиентских и 16 серверных потоков на экземпляр. Каждый поток имеет
кольцо 32 x 8192 байт (256 КиБ); worker обрабатывает до 16 блоков за задачу.
Чтение следующей порции перекрывается с отправкой уже подготовленной.
Все серверные и локальные I/O используют один FIFO worker, без блокировки uasync.
Отправитель добавляет пакеты, пока send_q router находится под нижним порогом
(сейчас count<=0), затем ждёт etcp_router_on_send_ready. Файловый слой не устанавливает
своё окно inflight и не повторяет DATA; транспортные ACK, retransmit и порядок — router.
Callback отправляет не более 8 блоков за запуск, уступая очередь другим потокам.
Параллельные файлы одного пира разделяют канал и FIFO/round-robin waiters.
Готовность приёмника проверяется router до доставки следующего seq. Заполненное
кольцо или очередь заданий оставляют пакет в recv_q: rx_seq/ACK не продвигаются.
Освобождение места вызывает отложенный resume; pump отменяется при reset/close.
ACK RECEIVE_BUSY (flags bit0) раз в 100 мс отличает ожидание диска от потери транспорта.
Отправитель приостанавливает новые DATA, а живые busy ACK продлевают таймаут router.
Буфер router ограничен его receive window. Локальная доставка использует такую же
проверку готовности и ограниченный FIFO, а не сеть или отдельные подтверждения.
При остановке сначала отменяются producers/requests и дожидаются все stream cleanup
continuations; только затем worker освобождает storage. Отмена после уже выполненной
публикации не удаляет опубликованный файл.
Transfer владеет CM handle до окончания/отмены; DOWN/TIMEOUT handle не освобождает.
quiesce запрещает новые команды, отменяет таймеры, waiter и запросы; destroy
вне callbacks дожидается только своих работ/completions и закрывает дисковый
поток до уничтожения CHAT-групп. Контексты не освобождаются под работающим I/O.
Отмена не прерывает текущий системный I/O или хеширование, а ждёт его completion.
Desktop
-------
Контекстное меню канала: Group files. Меню участника: Member files.
Окно: выбор сервера/UID, путь, Up/Refresh, таблица каталогов/файлов,
Upload/Download/New folder/Rename/Delete, прогресс и Cancel transfer.
Чужой UID доступен только для чтения. Автор может быть offline.
Open скачивает в выбранный пользователем локальный файл и открывает его
приложением ОС после завершения. Смена контекста отбрасывает поздние ответы.
Для выбранного UID строка хранилища показывает занято/всего, число файлов/каталогов и резерв текущих загрузок.
Точные байты доступны в tooltip. QUOTA запрашивается отдельно от LIST при выборе
сервера/UID, обновлении каталога, завершении передачи и каждые две секунды видимого окна.
Поздние ответы старого UID/сервера игнорируются. BUSY при стартовом сканировании
сохраняет Loading directory и повторяет LIST через секунду; новый переход отменяет повтор.
API параллельных передач
-----------------------
member_files_transfer_start(request, callback, arg) копирует request и возвращает
отдельный mf_transfer* на каждый файл. upload задаёт направление, group/server/owner
выбираются для каждой операции. id=0 выбирает случайный ID; id в callback — фактический.
NULL означает отказ запуска без callback; причина пишется в лог.
max_bytes=0 оставляет прежний лимит; ненулевое значение ограничивает размер файла.
Upload проверяет размер источника до хеширования, download — ответ STAT до открытия
локального temp. STREAM привязан к проверенным размеру/version; превышение — INVALID_RANGE.
callback(arg, mf_transfer_progress*) получает id/done/total/status/finished.
Прогресс заимствован до возврата callback. done для отправителя означает байты,
принятые router в очередь; успешный finished подтверждает завершение операции,
для upload — публикацию на сервере, для download — публикацию локального файла.
finished приходит ровно один раз, handle к этому моменту уже освобождён;
вызывающий обнуляет свой указатель в callback. member_files_transfer_cancel(handle)
отменяет только этот файл, ждёт его I/O и ставит CANCEL в очередь с backpressure.
Stop сервиса/группы завершает все принадлежащие ему handles.
Все API/callbacks выполняются в uasync-потоке экземпляра. GUI trampolines используют
тот же API и превращают callback в существующие события прогресса; Qt не делает I/O.
Один локальный файл назначения не может одновременно принадлежать двум download.
Аватарки пользователей
---------------------
Относительный путь avatars/<sha256>.<расширение> находится внутри обычного дерева UID:
<корень>/<group>/<UID>/avatars/<sha256>.<расширение>. Дополнительного UID в API/пути нет.
Аватарка по умолчанию копируется в каждую группу, где выбранный сервер имеет
подписанную storage-роль. Ссылка каждого канала публикуется только после COMMIT
соответствующей копии; отсутствующий сервер в другом канале не создаёт битую ссылку.
Новое членство обнаруживается через снимки участников и повторяет публикацию.
Между серверами автоматического копирования нет.
userinfo содержит строковые avatar_server (16 hex цифр) и avatar_file (64 hex SHA256 + .json).
Оба поля отсутствуют при удалённой аватарке. Профиль подписан ключом участника;
смена имени/адресов сохраняет остальные плоские JSON-атрибуты. userinfo ограничен
1024 байтами UTF-8. Merkle, JOIN_INFO_RESP, CHANNEL_INVITE и JOIN_REQUEST используют
длину userinfo:u16 BE вместо прежнего u8. Bootstrap передаёт существующий подписанный
профиль инвайтера, не создавая более свежую запись только с именем. Обе стороны
нужно обновить: обратной совместимости нет.
Снимок участника CHAT_MEMBER_DISPLAY_SIZE теперь 158 байт: прежние 79 байт,
avatar_server:8LE, avatar_file:70 (с NUL), storage_role:1. storage_role обозначает
подписанную роль сервера, независимо от существующей колонки storage — сетевого
приоритета, который может быть 0 у сервера за NAT. Потребители используют общий
chat_member.h; локальное изменение профиля также вызывает MEMBER_UPDATED.
В Settings → Profile доступны Upload avatar, выбор storage-сервера, предпросмотр
и Remove avatar. Изменения применяются сразу, независимо от Save других настроек.
Перед загрузкой desktop/Android показывают редактор: неподвижная квадратная рамка,
перемещение фото и масштабирование (мышь/колесо или жесты). Отмена не публикует новую ссылку.
Worker принимает JPEG/PNG/WebP до 32 МиБ и 16 мегапикселей. Исходные байты, включая EXIF,
сохраняются без изменений. Выбранная область после EXIF-ориентации становится WebP lossy
256×256, quality=85, максимум 1 МиБ. Описание JSON (максимум 4096 байт): version=1,
original/preview — имена объектов; original_bytes/preview_bytes, width/height — размеры
ориентированного оригинала; x/y/w/h — нормализованный квадрат в его координатах.
Все объекты лежат в avatars/<SHA256 собственных байтов>.<jpg|png|webp|json>.
Порядок COMMIT: оригинал, превью, описание; затем подписанная ссылка на описание.
Повтор использует уже сохранённые неизменяемые объекты. Сервер проверяет имя/SHA256,
лимит объекта и запрещает замену под прежним именем и rename в/из avatars.
Файлы и каталог входят в обычные квоты. Прежние версии сохраняются на сервере;
владелец может удалить их через файловый браузер. Старые PNG-ссылки не поддерживаются.
Клик по аватарке сообщения/участника открывает галерею пользователя: LIST каталога
выбирает только описания, проходит все страницы cursor/version, сортирует mtime DESC,
при равенстве — по имени. Изначально выбрана текущая подписанная ссылка. Большое фото —
полный оригинал, до его загрузки показывается превью. Внизу пять квадратных позиций:
две предыдущие, выбранная по центру, две следующие; за границами списка позиции пустые.
Desktop: кнопки/клавиши ←/→; Android: кнопки/свайпы при исходном масштабе. Превью кликабельны.
Загружаются выбранный оригинал и видимые превью; закрытие отменяет запросы без других владельцев.
Для DM источник — общий канал, из которого берётся отображаемая аватарка.
AvatarManager общий для списка участников и сообщений, максимум две операции
одновременно, одинаковые хеши не скачиваются параллельно. Изменение своего профиля
имеет приоритет перед фоновыми загрузками. Для DM используются ссылки из общих
каналов; при отсутствии доступной ссылки остаются инициалы. Онлайн-статус списка
отображается отдельной точкой поверх фотографии. Поздний результат старой ссылки
пополняет кеш, но не заменяет картинку новой ссылки.
Кеш аккаунта: <db_path>/avatar_cache/<local_UID_hex>/<sha256>.<расширение>; без db_path
используется Qt AppLocalDataLocation. Проверяются SHA256 объектов, описание и WebP 256×256;
превышение/битая картинка не публикуются. Публикация кеша атомарна через QSaveFile,
незавершённые download/part очищаются при старте. Готовые файлы не требуют TTL/STAT:
смена содержимого меняет имя. Дисковый LRU — 100 МиБ, кеш QImage — 32 МиБ;
собственная аватарка и источники незавершённых upload сохраняются при вытеснении.
Дисковое I/O и декодирование выполняются вне GUI/uasync. Повторы неудачных загрузок:
5/10/20/40/80/160 секунд; новая ссылка сбрасывает задержку. RTT/online не меняют ссылку
и не вызывают повторной загрузки. Желаемая собственная ссылка/удаление сохраняются
атомарно одной строкой ui_state.avatar_default, незавершённая публикация повторяется
после запуска при наличии локального источника.
test_avatars проверяет нормализацию, хеш/размеры, очистку temp, настоящее storage
в двух каналах, публикацию, сохранение при смене имени, DM-кеш, restart без сети,
автоматический download после потери кеша и удаление ссылок. Также проверяются
max_bytes до открытия local temp и смена аватарки при задержанном download старой
версии: поздний результат не заменяет новый рисунок. test_member_adapter проверяет
подписанные профили 191/255/256/512/1023/1024 байт вместе с adm_tags до 8192.
Категория media=debug показывает discovery, cache hit/eviction, направление,
group/owner/server/hash, ошибки проверки и retry; media=info — передачи/публикации.
Проверки и диагностика
---------------------
tests/test_member_files: wire/UTF-8, произвольные блоки, повторы, EOF, версии,
короткая замена, SHA256, пустой файл, независимые квоты UID, общий учёт по группам,
резервирование/повторы/TTL, инкрементальные files/directories, замены пустых файлов,
создание/удаление/rename каталогов, отсутствие runtime-rescan, стартовый scan и его ошибки, ссылки, expiry/restart, 10-секундный
fd idle и принадлежность work/done потокам.
tests/test_member_files_network: настоящие A/B/M, подписанная storage-роль,
параллельные upload/download одного пира, local loopback, пустые файлы, побайтная
проверка, отмена одного handle, restart router и destroy с активным server I/O.
Шестисекундная задержка worker проверяет receive backpressure и busy ACK.
Также проверяются чужая запись, виртуальный корень и подписанное удаление читателя.
QUOTA проверяется на настоящем подписанном/зашифрованном канале; удалённый читатель не получает счётчики.
test_services — stop/start общего чат-сервиса.
Диагностика файлового сервиса — категория media, маршрутизатора — etcp_route.
Настройка для полного трасса этих модулей:
[debug]
media=trace
etcp_route=trace
media=debug показывает запрос/ответ, ACL, очереди файлового сервиса, I/O и fd idle.
etcp_route=debug добавляет выбор маршрута, состояния канала и ретрансмиты.
Уровень trace дополнительно показывает каждый
read/write и передачу задач/завершений между дисковым потоком и uasync.
stream progress раз в секунду показывает ID/направление, done/total, число блоков
в кольце, активность I/O, причину ожидания и скорость B/s. Router логирует переходы
receive paused/resumed и peer receive busy без повторного сообщения на каждый блок.
quota scan started/ready показывают путь, число пользователей и время обхода;
quota updated показывает UID/операцию/used/reserved/files/directories; quota exceeded — used/reserved/growth/limit.
По request_id видны GUI post → core request → router seq → server rx → ACL →
disk queued/start/done → response tx → GUI result/bridge → directory ready.
PID создания worker, PID отправки задачи и startup PID позволяют однозначно
обнаружить fork после создания потока; несовпадение PID логируется как ошибка.
Ключи шифрования, plaintext файлов и сырые медиа в debug не выводятся.
Старые VAD/рация/GUI/вложения сообщения перенесены в vad/radio/chat/media/config;
ошибки и пользовательские info остаются в тематических категориях.
+36
View File
@@ -0,0 +1,36 @@
# Ссылки в сообщениях
Android и Qt распознают `http://`, `https://` и `utun://` в обычном тексте
личных и групповых сообщений. HTTP/HTTPS нечувствительны к регистру схемы.
Ссылки выделяются цветом и подчёркиванием; длинные веб-адреса переносятся по ширине.
Сокращённая строка структурированного PM-приглашения сохраняет полный `utun://` адрес.
Пробелы, управляющие символы, кавычки и угловые скобки отделяют ссылку от текста.
Конечные `. , ; : ! ?` и лишние закрывающие скобки удаляются из распознанного адреса.
Сбалансированные скобки в адресе, параметры, fragment и Unicode сохраняются.
Адреса без схемы не распознаются. Некорректные веб-адреса остаются обычным текстом.
Исходное сообщение в БД и протоколе не меняется.
Разборщики возвращают диапазоны UTF-16 и полные адреса:
`tools/chatgui/src/message_links.cpp`, Android `data/MessageLinks.kt`.
Подсветка и нажатие используют эти диапазоны. Android подставляет emoji placeholders
только в обычный текст между ссылками: emoji внутри адреса остаётся частью полного URL.
Qt использует один QTextLayout для размеров, отрисовки, выделения и координат нажатия.
Перетаскивание выделяет текст без открытия ссылки.
HTTP/HTTPS открываются из GUI системным обработчиком: Android `ACTION_VIEW`,
Qt `QDesktopServices::openUrl`. `utun://` запускает существующий JOIN.
Ошибки открытия показываются через перевод интерфейса и логируются.
Диагностика открытия содержит схему и host, без полного пути/query.
Общие случаи: `tools/chatgui/tests/message_links_cases.tsv`.
Qt `test_invite_links` проверяет разбор, перенесённые веб-ссылки, смесь схем,
нажатие, drag/copy и сокращённые приглашения; `test_emoji_playback` — жесты приглашений.
Android `tests/test_message_links.kt` проверяет тот же TSV на классах из APK-сборки:
```bash
cd tools/chatgui-android
./build.sh noinstall
kotlinc tests/test_message_links.kt -classpath app/build/tmp/kotlin-classes/debug -d /tmp/message-links-tests.jar
kotlin -classpath /tmp/message-links-tests.jar:app/build/tmp/kotlin-classes/debug Test_message_linksKt ../chatgui/tests/message_links_cases.tsv
```
+146
View File
@@ -0,0 +1,146 @@
Как работает детектор NAT
========================
Детектор выясняет две вещи: какой внешний IP:порт получает рабочий UDP-сокет
при обращении к разным IP и проходят ли к нему пакеты от незнакомого IP.
Проверяется именно сокет, через который работает приложение.
Есть быстрый путь без помощников: если рабочий сокет принял подключение
и локальный IP и фактический IP подключившегося узла публичные,
ему сразу ставится direct/open (UNCHANGED + OPEN). Это правило действует
для UDP и TCP, даже если с IP пира раньше уже был исходящий обмен.
Локальные подключения и SOCKS интернет-вход не подтверждают. Для TCP
фактический локальный IP должен совпадать с IP интерфейса сокета.
Нужны три узла: A проверяет себя, B наблюдает его внешний адрес,
C помогает проверить входящие пакеты. Команды и результаты передаются
по уже работающей сети uTun, в том числе через TCP или промежуточные узлы.
Сами тестовые пакеты идут напрямую по UDP.
Порядок проверки
---------------
1. A отправляет пакет узлу B. Тот сообщает, с какого IP:порта его получил.
Ответ идёт по управляющему соединению, поэтому блокировка прямого
UDP-ответа не мешает узнать внешний адрес A.
2. Через B узел A просит C отправить несколько пакетов на этот внешний адрес.
До этого A не должен отправлять ничего на IP узла C с проверяемого сокета.
Приложение хранит историю таких отправок и учитывает новые во время проверки.
На входящие тестовые пакеты A автоматически не отвечает.
3. Получатель проверяет настоящий адрес отправителя каждого тестового пакета.
Локальный или другой непубличный IP не подходит для интернет-проверки.
Для проверки незнакомого входа также не подходят IP узла B, собственный
внешний IP узла A и IP, на который A уже отправлял пакеты.
Если помощник не подходит, A выбирает другого. Разные узлы с одним IP
не считаются разными источниками. Проверяются также подлинность пакета
и принадлежность текущей попытке, чтобы чужой или старый пакет не влиял на результат.
4. Затем A сам отправляет пакет C и узнаёт, какой внешний IP:порт видит C.
Если входящие до этого не проходили и внешний адрес совпал с увиденным B,
C повторяет отправку с того же IP:порта на исходный внешний адрес A.
Так проверяется, открыл ли исходящий контакт путь для входящих.
Параллельный обмен с B подтверждает, что UDP-связь в целом работает.
5. A повторяет измерение внешнего адреса через B и C. Результат принимается,
только если адрес для каждого направления остаётся стабильным.
Сравниваются разные IP назначения; другие порты того же IP не проверяются.
Что означает результат
----------------------
strict — B и C стабильно видят разные внешние IP:порты узла A.
Единого внешнего адреса для подключения с любого IP нет.
eim open — B и C видят одинаковый внешний IP:порт, и пакет от C пришёл
ещё до исходящего контакта A с его IP.
eim filtered — внешний IP:порт одинаковый, серия пакетов до контакта не прошла,
а после исходящего контакта пакеты с того же адреса C начали приходить.
Это оценка: потерю UDP-пакетов нельзя абсолютно отличить от блокировки.
EIM? — два направления видят одинаковый внешний IP:порт, но незнакомый вход
проверить не удалось, например все доступные помощники уже были знакомы.
Это EIM с неизвестной доступностью входящих, без обещания открытого входа.
Такой же предварительный EIM? дают два STUN-сервера, видящие одинаковый внешний
IP:порт тестового сокета на том же локальном IP и интерфейсе. Поддержка запроса
с незнакомого IP для этого не нужна. Источник оценки — STUN, вход остаётся UNKNOWN.
Измерение рабочего сокета имеет приоритет; STUN-оценка действует до пяти минут
и обновляется автоматически. Публичный порт для подключения берётся только
из наблюдения рабочего сокета: внешний порт STUN в него не подставляется.
Если внешний IP:порт совпадает с локальным, отдельно отмечается отсутствие
трансляции адреса. Доступность входящих всё равно проверяется.
Если данных недостаточно, соответствующая часть результата остаётся UNKNOWN.
Известный тип NAT можно сохранить, даже если входящие проверить не удалось.
Таймаут и отсутствие подходящих узлов сами по себе не означают strict.
Если один пир уже увидел публичный IP:порт, отличный от локального, этот адрес
сразу сохраняется как NAT с type=UNKNOWN. Он публикуется отдельно от адреса
интерфейса и остаётся кандидатом для пробного подключения: одного наблюдения
недостаточно, чтобы обещать тот же адрес для других IP или открытый вход.
Уже знакомый помощник пригоден для измерения типа NAT, хотя проверить незнакомый
вход через него нельзя. Потеря результата входящих не останавливает сравнение
адресов через доступные публичные узлы; ожидание одного этапа ограничено по времени.
Измеренный mapping сохраняется сразу, а проверка незнакомого входа продолжается
через остальных помощников. Порядок обхода фиксирован на его начало; фактический
source всегда проверяется по актуальной истории. При паузе по общему бюджету
незавершённая попытка повторяется с новыми идентификаторами, без сброса обхода.
Если попытка уже получила полный бюджет, его исчерпание считается таймаутом:
обход переходит дальше, а не повторяет одного помощника бесконечно.
Старый сохранённый результат входящих не считается успехом новой проверки.
Соединения по LAN работают независимо и не сбрасывают интернет-профиль.
Если доступны только локальные узлы, измерить тип интернет-NAT для UDP нельзя.
STUN на другом порту даёт предварительную оценку типа NAT для того же IP/интерфейса.
Работа TCP не заменяет проверку UDP.
Для TCP определяется только наличие NAT. Непубличный локальный IP сразу означает
NAT с неизвестным типом. Если локальный IP публичный, достаточно одного публичного
TCP-пира: по готовому защищённому соединению он сообщает увиденный IP.
IP совпали — трансляция IP не обнаружена; отличаются — NAT есть, тип UNKNOWN.
Порты при сравнении не учитываются. Публичный внешний IP сохраняется с портом 0:
входящий порт неизвестен, поэтому NCD не использует эту запись для подключения.
Активных проб типа NAT и TCP-входа нет; OPEN устанавливается по принятому
публичному подключению и сохраняется при последующих совпавших наблюдениях IP.
Через SOCKS внешний IP прокси
не записывается как собственный адрес.
Управляющий обмен и ресурсы
--------------------------
Команды идут через сервис NAT_DETECTION ETCP-router с подписью и шифрованием.
Используются узлы группы с рабочим маршрутом через READY-соседа; это не требует
прямого UDP-соединения A с C. CHAT-допуск требует полной неудалённой записи участника.
Источник и группа берутся из проверенного заголовка router, а не из next hop.
Версия управляющего формата — 4. Основное сообщение занимает 117 байт:
10 однобайтовых полей команды/сокетов/этапа/результата, шесть uint64
(owner, generation, round, token, observer, helper), uint16 timeout_ms
и три адреса по 19 байт (target, primary, observer_address).
RECHECK занимает 23 байта: cmd, command, version, socket_id и адрес 19 байт.
Многобайтовые числа кодируются вручную в big-endian; адреса — route_ping_addr_encode.
Это размеры сообщения после служебного заголовка router. Приём требует точного
размера; старые generation/round/token и чужие делегирования результата отклоняются.
Детектор работает с шагом 100 мс. Помощник отправляет до трёх проб на этап;
таймаут одной пробы — не меньше секунды и не меньше четырёх RTT наблюдателя.
Общий бюджет обхода — 30 секунд; незавершённый обход может продолжиться после паузы.
Полный результат перепроверяется через 30 минут, неполный — через 30 секунд.
Повтор по неудаче подключения ограничен одним запросом за пять минут на endpoint.
Сохранённый успешный ETCP-профиль не истекает по таймеру; изменение endpoint его сбрасывает.
История исходящих хранит до 256 IP для каждого локального endpoint (IP:порт).
Она сохраняется в SQLite. Переполнение или ошибка загрузки/записи помечают историю
неполной: UNKNOWN сохраняется до достоверного измерения, OPEN по незнакомому входу
с неполной историей не подтверждается. Кеш наблюдений ограничен 128 записями,
одновременно выполняется не более 64 задач помощи.
Детектор владеет таймерами, подписками и изолированными probe_only UDP-сокетами.
Эти сокеты не публикуются как рабочие адреса. Их отдельное удаление снимает ссылку
в состоянии родителя, отменяет зависимые пробы и допускает обычный последующий teardown.
Ошибка регистрации любой подписки или начального таймера отменяет весь запуск.
Ядро сохраняет контекст в instance->nat_det; останавливает его до ETCP/router
и обнуляет поле после destroy. Уничтожать ядро из callback детектора нельзя.
+118
View File
@@ -0,0 +1,118 @@
# Подписанная запись узла
`TOPO_NODE.timestamp` — версия всей подписанной записи, а не отдельного адреса.
Узел-владелец назначает Unix microseconds, строго больше предыдущей сохранённой
версии. Имя, тип, активность, адреса, метаданные сокетов, REALITY и подпись
заменяются вместе. Timestamp входит в подпись. Групповые пути и подсети
остаются в `TOPO_GROUP_NODE` и не являются частью общей записи узла.
В NODEINFO секции подсетей присутствуют только при `TOPO_FLAG_SEND_SUBNETS`.
Без флага оба счётчика подсетей равны нулю; противоречащий этому заголовок отклоняется.
Parser проверяет точную длину пакета и проходит все присутствующие секции независимо
от запрошенных `out_*`: например, `out_subnets=NULL` отключает создание объектов подсетей,
но сохраняет проверку префиксов и правильное положение следующего hop-list.
Выходы выдаются только после полного успеха; при ошибке они обнулены, частичные объекты
освобождены. Подпись и допустимость пути проверяются после структурного разбора.
Получатель проверяет подпись до изменения реестра или маршрутов. Более свежая
запись заменяет старую целиком, включая пустые списки адресов. Старые записи
игнорируются; разные подписанные записи с одинаковым timestamp — конфликт.
Повторное BGP-объявление может обновить путь, но не содержимое общей записи.
8-битный `ver` сохраняется как диагностический счётчик; свежесть определяется
только timestamp.
Локальный узел группы готовится до удаления прежнего `local_node`: сравниваются сами адреса
и префиксы подсетей, включая замену при прежнем количестве. При отказе памяти, подписи или
конфликте IPv4-маршрутов прежние узел и маршруты сохраняются. Подсети CHAT не публикуются.
`topo_group_update_my_nodeinfo` возвращает число IPv4-подсетей, для CHAT — 0, при ошибке — -1;
рассылку подготовленного NODEINFO выполняет вызывающий.
Живая телефонная занятость передаётся без изменения структуры NODEINFO: в существующем
`flags` бит `0x04` означает известное состояние, `0x08` — занятость звонком.
Это групповые флаги `TOPO_GROUP_NODE.phone_status`, как флаг рации `0x02`;
они не входят в подпись общей записи и используются только для цвета трубки в GUI.
Без разрешения Android, при выключенной публикации и после ухода узла статус неизвестен.
Решение о начале вызова принимает адресат по локальному состоянию ОС, а не по BGP-подсказке.
Изменения публикуются обычной новой версией NODEINFO во всех CHAT-группах; отдельного опроса и таймера нет.
Профиль endpoint в SOCKMETA содержит независимые `mapping` и `inbound`, источник `source`.
ID и время измерения не передаются: последний успешный профиль действует до
явного изменения endpoint или подтверждённого нового результата. Повторная
проверка с тем же результатом не меняет NODEINFO. Все поля SOCKMETA — отдельные байты;
границы списков metadata/адресов/REALITY также входят в каноническую подпись.
Алгоритм и условия неопределённости описаны в `doc/nat_detection.txt`.
`source=ETCP` обозначает результат детектора рабочего endpoint. Одно UDP-наблюдение может дать
адрес `TOPO_ADDR_NAT` при `mapping=UNKNOWN`, `inbound=UNKNOWN`: это NAT-кандидат
для пробного подключения, а не адрес интерфейса или подтверждённый открытый вход.
Общий внешний адрес подтверждает EIM; strict не публикует универсальный NAT-адрес.
Source входит в подпись; SOCKMETA занимает 5 байт. `source=STUN` означает предварительный
EIM? для того же локального IP/netif с неизвестным входом; измерение ETCP приоритетно.
Владелец снимает STUN-оценку после TTL. Внешний адрес тестового порта не публикуется:
NAT-кандидат по-прежнему требует наблюдения рабочего сокета.
Для TCP достаточно одного публичного пира: внешний IP с неизвестным входящим
портом публикуется как `TOPO_ADDR_NAT` с `port=0`, `mapping=UNKNOWN`, `inbound=UNKNOWN`.
NCD пропускает нулевые порты. Непубличный локальный TCP IP сразу означает NAT;
совпавший с наблюдением публичный IP даёт UNCHANGED без подтверждения открытого входа.
Таблица `node_snapshots` хранит полную сериализованную запись без группового
окружения. `node_addresses` остаётся проекцией для поиска и отображения, а не
источником для восстановления подписанной записи. При наличии сохранённого
snapshot загрузка использует его. Локальная публикация сохраняется до рассылки;
ошибка сохранения не разрешает публикацию новой версии.
В проекции адресов сохраняются `nat_mapping`, `nat_inbound`, `nat_source`, `address_kind`.
Уникальность определяется endpoint и видом адреса,
а не отображаемым классом NAT. Старый производный формат пересоздаётся;
bootstrap-загрузка без подписанного snapshot не подтверждает NAT-профиль.
Такая загрузка требует хотя бы одного адреса; отказ SQL/памяти освобождает все частичные списки
и возвращает NULL. Наличие валидного snapshot с пустым списком адресов, напротив, допустимо.
При удалении старых `nat_evidence`/`nat_checked` адреса и REALITY сохраняются для bootstrap,
старые профили сбрасываются, сериализованные snapshots инвалидируются.
Максимальные timestamp snapshots сохраняются, чтобы новая локальная публикация
не откатывала версию даже при коррекции часов. Чат и ключи миграция не изменяет.
Timestamp 0 обозначает bootstrap-сведения без подписи (конфиг, приглашение,
начальные адреса). Они могут помочь установить первое соединение, но не
заменяют принятую подписанную запись. По BGP принимаются подписанные записи
с ненулевым timestamp. Формат NODEINFO изменён без обратной совместимости.
После принятия BGP-записи `node_conn_direct_update_node` применяет адреса к
существующему NCD-соединению. Отсутствующее соединение этот вызов не создаёт.
Путь подготавливается до изменения общего реестра и групповых подсетей: отказ
памяти сохраняет прежнюю запись, ссылки и пути. После commit IPv4-таблица
перестраивается для этого узла, включая удаление старых подсетей при пустом
новом наборе. Конфликт подсетей или OOM установки маршрутов логируется,
новый набор не устанавливается, прежние маршруты удаляются; ошибка запрещает
READY сессии. Принятая подписанная запись и пути при этом могут сохраниться.
Линки дополняются как при UP, так и до установления соединения; повторная
доставка не создаёт дубликаты UDP/TCP. Работающие линки не закрываются только
из-за смены списка адресов. NCD удерживает ссылку на общую запись, пока
обслуживает соединение, и использует её при восстановлении.
`node_conn_direct_open_node` сверяет переданную запись с реестром/БД. Более
свежая подписанная запись принимается целиком, старая уступает уже известной.
Bootstrap-адреса не перекрывают подписанную запись даже при DOWN.
Конфигурационные подключения UTUN используют один адаптер
`init_client_connections` для старта и reload. Все адреса, TCP и REALITY
передаются NCD; адаптер не создаёт ETCP-линки. Новый набор handles открывается
до освобождения старого; при ошибке старые handles сохраняются. Конфигурация
удерживает один handle на клиента, независимо от количества его адресов.
Включение конфигурационного пира в UTUN-группу выполняет callback NCD UP.
Само событие транспортного UP не включает соединение в UTUN-группу автоматически.
Входящий JOIN остаётся явным запросом
участия. При пересоздании UTUN-группы учитываются её конфигурационные handles.
Удаление клиента отправляет `LEAVE_GROUP` и освобождает удержание UTUN-группы
с обеих сторон. Другие владельцы NCD сохраняют транспорт. Поздние NODEINFO и
WITHDRAW от отсоединённого UTUN-пира игнорируются, чтобы не вернуть его маршруты.
LEAVE ждёт backpressure через queue waiter и временно удерживает собственный
NCD handle. Закрытие транспорта отменяет ожидание; новое участие в группе
делает ожидающий LEAVE устаревшим, и он не отправляется.
Причина удаления пира передаётся явно: только потеря транспорта запускает
recovery каскадных маршрутов. Локальный/удалённый LEAVE и удаление битой
member-записи не должны восстанавливать намеренно закрытое участие.
REALITY использует полное SNI до 255 символов; размер поля в записи узла
согласован с транспортом. Если локального TCP listen-сокета нет, NCD может
создать исходящий TCP-линк с выбором локального адреса операционной системой.
+12
View File
@@ -0,0 +1,12 @@
Работа лички
1. отдели логику лички от логики групповых чатов. не смешивай.
Работа лички. сообщения и медиа доставляется напрямую если узел доступен через bgp.
если недоступен - то pending сообщения сохраняются на суперузле и pending media сохраняется на каком-либо медиаузле, а суперузлу сообщается где хранится медиа.
при появлении пира суперузел организует доставку медиа до получателя. после успешной доставки медиа/сообщений они удаляются с промежуточных узлов.
время хранения медиафайлов на промежуточных узлах ограничено - 1 неделя по умолчанию (задачется в конфиге этих узлов).
для доставки медиа поднимаем линк (conn_mgr).
и посмотри реализацию медиа для групповых чатов.
+69
View File
@@ -0,0 +1,69 @@
# Приглашение в группу через PM
В desktop chatgui и Android кнопка «+» доступна в строке другого участника
и в шапке личной беседы. Она открывает один диалог с фиксированным получателем
и выбором группы, в которой отправитель состоит. Группа не выбирается автоматически.
После нажатия «Пригласить» клиент создаёт свежую ссылку через `invite_build`,
ожидает подтверждения регистрации ключа и отправляет PM с `ct="invite"`.
Зашифрованное тело: `[group_name:UTF8][NUL][полная ссылка:UTF8]`, без конечного NUL.
Снимок для GUI содержит полную ссылку в `data` и название в `tags.group_name`.
GUI добавляет заголовок «Приглашение в группу «название»» из перевода интерфейса;
язык отправителя не фиксирует язык сообщения у получателя.
Успех означает сохранение сообщения и исходящей задачи в одной транзакции.
Доставку, повтор и шифрование обеспечивает существующий механизм DM.
После успешной отправки открывается личная беседа с получателем.
Срок действия ключа — **10 минут с момента последней успешной регистрации на каждом узле**.
Повторная регистрация того же ключа для того же канала тем же инвайтером продлевает срок.
Connection-узел сохраняет ключ при обработке регистрации, инвайтер — после её подтверждения;
при совпадении этих ролей запись одна. Для JOIN ключ должен действовать у обоих.
Получение PM срок не продлевает. Длительность указана в диалогах отправки и подключения. Поздняя offline-доставка
может принести уже просроченную ссылку; в этом случае нужно новое приглашение.
Ссылка не содержит время истечения, поэтому клиент показывает длительность,
без обратного отсчёта и вычисления оставшегося времени.
Нажатие на `utun://` в сообщении запускает существующий JOIN и показывает его результат.
Если клиент уже состоит в группе, открывается эта группа. Ссылка приглашения отображается
одной строкой с многоточием; нажатие и копирование используют полный адрес.
В Android долгое нажатие копирует ссылку, в desktop выделение сокращённой строки
копирует ссылку целиком без запуска JOIN. Обычные текстовые PM с вручную
вставленными ссылками сохраняют текстовое отображение.
## Контракт операции
`dm_invite_trampoline` выполняется в uasync-потоке. Запрос содержит `request_id`,
ID получателя, выбранную группу и исходную общую группу для создания PM.
Из шапки существующей PM исходная группа может быть пустой.
На instance допускается одно ожидание регистрации приглашения в PM.
Повтор во время ожидания возвращает BUSY. Закрытие диалога отменяет только
соответствующий `request_id`; уже отправленное сообщение не отзывается.
Отмена и остановка DM завершают принятый запрос ровно один раз.
`CHAT_EVT_DM_INVITE_RESULT` содержит 28 байт:
`[request_id:u64][result:i32][peer:u64][conv_id:u64]` в порядке байтов ядра.
Desktop и Android сопоставляют результат с запросом и получателем.
Ошибки регистрации или сохранения сообщения оставляют диалог открытым для повторной попытки.
JOIN во вторую группу может использовать уже открытый транспорт общей группы.
Поэтому обработчики JOIN_REQUEST и JOIN_READY явно планируют member_sync,
а JOIN_READY обновляет кеш каналов до запуска синхронизации.
## Проверка и диагностика
- `tests/test_dm_invite`: настоящая UDP-доставка ссылки в другую группу, JOIN,
репликация участника и отправка группового сообщения после вступления;
отмена, BUSY, неверная группа, rollback при ошибке commit и остановка DM;
передача названия с Unicode, кавычками и переводом строки.
- `tests/test_chat_join`, `tests/test_chat_join_e2e`, `tests/test_dm_e2e`:
существующие сценарии регистрации ключей, JOIN и PM-доставки.
- Qt `test_invite_links`: одна строка приглашения на разных ширинах, полный адрес
при нажатии и копировании, переносы двух обычных ссылок, их полное содержимое при нажатии,
выделение текста и выбор получателя кнопкой в строке участника.
- Qt `test_emoji_playback`: перетаскивание ссылки не активирует подключение.
Для сетевого теста подробности включаются через `UTUN_TEST_DEBUG=1`.
В приложении полезны категории `dm=debug`, `chat_sync=debug`, `member_sync=debug`.
Операция пишет request, peer, group, результат и TTL; сообщения диагностики
новой операции не выводят саму invite-ссылку.
+198
View File
@@ -0,0 +1,198 @@
Proxy: исправления и проверка архитектуры
Дата: 2026-10-24. Статус: реализовано и проверено на Linux.
Ниже сохранён согласованный проект; фактические изменения и результаты — в конце.
Подтверждённые факты до исправлений
1. udp_proxy.c не проверяет bind/sendto, результат socket_set_nonblocking,
часть выделений/регистраций/таймеров и отправку ответа через router.
Неудачный запуск flow может оставлять ресурс без рабочего таймера очистки.
2. server_init и client_create оба запускают общие UDP/ICMP-контексты.
client_destroy сохраняет их по server.enabled, server_destroy уничтожает всегда.
Простой rollback через destroy одного модуля не выражает владение общими ресурсами.
3. ICMP init публикует контекст и сообщает успех без raw socket. При этом тот же
контекст нужен для клиентских REPLY: отсутствие возможности exit не означает,
что клиентская сторона ICMP не может работать через другой exit.
4. В диагностическом прогоне tcp_io error_cb при HUP отменяет read_waiter,
включает чтение и напрямую вызывает read_cb вопреки read_paused.
Для fd=15 порог чтения 4; журнал фиксирует read_paused=1 и рост rq до 53.
Это нарушение ограничения очереди, а не только избыточная диагностика.
5. EPIPE/ENOTCONN в HTTPS CONNECT воспроизведены. Системная трасса четырёх TLS-потоков:
recv(destination)=0 -> поздняя TLS-запись 24 байта от клиента -> send(destination)=24
-> SO_ERROR=EPIPE либо shutdown(SHUT_WR)=ENOTCONN. Повторный shutdown не обнаружен.
Определение содержимого зашифрованной TLS-записи по трассе невозможно.
Сам по себе этот сценарий не доказывает дефект half-close в proxy.
6. После удаления exit-потока приходят поздние WINDOW: диспетчер пишет unhandled,
хотя это известная команда уже завершённого потока. Создавать поток по WINDOW нельзя.
7. enabled=yes в серверных тестовых конфигах не поддержан парсером. Сейчас сервер
включается при обработке любого параметра секции, включая неизвестный.
Предлагаемая архитектура
Владелец общих UDP/ICMP-контекстов — UTUN_INSTANCE. Их жизненный цикл координируется
в utun_instance.c после router_init и до router_destroy. TCP client/server управляют
только своими listeners, TCP-потоками и bindings. Общих счётчиков ссылок не требуется:
при существующем lifecycle они живут до остановки instance.
Контексты нужны при включённом exit или клиентском TUN; SOCKS/HTTP без TUN их не требуют.
server.enabled обозначает действующий TCP exit. Роль при запуске ICMP exit должна
передаваться явно, а не определяться случайным порядком вызовов init.
При отказе создания клиента rollback освобождает только его ресурсы и собственный binding.
Повторное создание клиента/сервера поверх активного состояния отклоняется.
Результат client_create публикуется в inst->tcp_proxy_client только после успеха.
Порядок остановки: клиент и exit-потоки -> общие UDP/ICMP -> router -> drain deferred free.
При destroy всего instance остановку proxy перенести перед остановкой UTUN-группы,
разборкой topo_groups и ETCP-сокетов/соединений. Сейчас proxy уничтожается в phase M,
после topo_groups и транспорта: для нового rollback нельзя сохранять этот поздний порядок.
Общий instance_init_common уже вызывает instance_destroy при отказе создания;
нужно использовать этот путь, не добавляя параллельную систему частичной очистки.
При create_from_config сохранить существующее правило: отказ возвращает config вызывающему коду.
Ресурсы закрываются в одном uasync-потоке; stop/destroy не вызывается из активного
callback уничтожаемого владельца. Таймеры/waiters отменяются до освобождения памяти.
Инициализация и ICMP
Разделить создание общего ICMP-контекста/регистрацию REPLY и запуск raw exit socket.
Это две разные операции: при отказе raw нельзя уничтожать работающую клиентскую сторону.
Например, icmp_proxy_init создаёт общий контекст, отдельный enable_exit открывает
raw socket и регистрирует чтение. Новый raw socket публикуется только после полного успеха.
Обе операции возвращают ошибки непосредственно своему вызывающему коду.
Рекомендуемая политика: EPERM/EACCES/отсутствие поддержки raw явно отключают только ICMP exit,
с WARN о причине и итогом запуска tcp=on/udp=on/icmp_exit=off. TCP/UDP продолжают работать.
Ошибки выделения/регистрации обязательных ресурсов прерывают запуск proxy с полной очисткой.
Если продукт требует обязательного ICMP, отсутствие raw должно быть фатальным на уровне
конфигурации; это отдельная политика, а не скрытый fallback внутри init.
В production виртуальные echo-ответы недопустимы. Тестовый loopback выбирается явно
и должен работать одинаково с правами raw и без них.
Заданные SOCKS/HTTP listeners обязательны: enabled с пустым/неверным адресом либо
неудачный bind/listen проваливают create. Нельзя вернуть готовый клиент без выбранного входа.
Проверки router bind оставляем, но при живом inst нынешний bind не выделяет память
и возвращает 0; не нужно изображать реальные OOM-пути там, где их нет.
UDP и ограниченные ресурсы
Новый flow сначала полностью создаётся локально: socket -> nonblocking -> bind ->
read registration -> обеспечение expire timer. Только затем добавляется в список.
Любой отказ снимает уже созданные регистрации/таймеры и закрывает именно новый сокет.
Если общий таймер не удаётся продлить, оставшиеся flows закрываются с диагностикой:
состояние без гарантированной очистки не сохраняется.
sendto проверяется по точному размеру. Для нулевой датаграммы n=0 означает успех.
EINTR допускает повтор операции; EAGAIN/ENOBUFS означает явное отбрасывание датаграммы
с диагностикой. Не добавляем неограниченную очередь и TCP-подобные повторы UDP.
Остальные ошибки также логируются с peer, tuple, длиной и errno; packet-specific ошибка
не должна автоматически уничтожать рабочий flow. Невалидный/сломанный сокет удаляется.
Проверяются ошибки router send, выделения ответов и доставки в TUN.
ICMP request тоже не публикуется без рабочего механизма истечения срока ожидания.
TCP HUP, FIN и границы ответственности
FIN относится только к одному направлению и следует после его DATA. Ошибка реальной
записи остаётся ошибкой даже после FIN противоположной стороны; ENOTCONN не превращается
в успешный on_fin_sent. Существующая регрессия test_fin_shutdown_error сохраняет этот контракт.
HUP — повод обработать завершение сокета, но не разрешение обойти high_water или явную pause_read.
При достижении high_water чтение останавливается до low_water. Если level-triggered HUP
не даёт event loop уснуть, регистрацию сокета нужно временно снять и восстановить по
queue waiter; простой return с оставленным HUP в epoll/poll создаёт busy loop.
Нужен один механизм восстановления регистрации в tcp_io, учитывающий connected,
fin_remote, write_buf, write_queue и уничтожение владельца. Состояние fd/пулы сохраняются
до окончательного destroy; запланированная запись не должна зависеть от паузы чтения.
Повторное добавление использует новую generation uasync, старые события игнорируются.
Явную паузу владельца следует отличать от паузы по high_water; HUP не снимает ни одну.
Это наиболее чувствительный этап: перед реализацией проверить переходы регистрации
при HUP с ожидающей записью, low_water, обеих FIN и destroy из callback.
Не менять глобальную семантику всех регистраций uasync ради одного tcp_io-сценария.
Поздние WINDOW/FIN/CLOSE/ERROR для отсутствующего потока игнорируются как события
завершённого sid с debug-диагностикой. DATA не воскрешает поток, неверное окно живого
потока по-прежнему является ошибкой. HTTP не обязан ждать FIN для ответа с известной длиной.
Проверить синхронный loopback: состояние перед отправкой control резервируется заранее;
после callback с возможным destroy нельзя обращаться к владельцу без проверки его жизни.
HTTPS-тест разделить на нормальное двустороннее TLS shutdown и явный abrupt close.
В первом случае ожидать отсутствие EPIPE/ENOTCONN; во втором — корректную ошибку,
сохранность уже доставленного ответа и освобождение ресурсов. Proxy не разбирает TLS.
Сначала проверить эту гипотезу управляемым тестовым сервером, затем менять fixture.
Проверка после реализации
- Fault injection: socket/nonblocking/bind/read registration/expire timer/sendto/OOM.
Проверять отсутствие fd/памяти/таймеров/handlers после каждого отказа и успешный повторный запуск.
- client only, exit only, обе роли на одном instance, два независимых instance;
отказ raw сохраняет ICMP REPLY клиента, остановка одного TCP-модуля не ломает общие контексты.
- UDP payload=0, краткая/ошибочная отправка, transient error и следующий успешный пакет.
- HUP при полном proxy window и остановленном consumer: очередь остаётся ограниченной,
event loop не крутится, после снятия блокировки все байты и FIN доходят в правильном порядке.
- pending write при HUP, manual pause, low_water/re-register, destroy в callback и stale events.
- FIN в обе стороны при backpressure, RST до/после ответа, нормальный/оборванный TLS,
поздний WINDOW, синхронный loopback и HTTP keep-alive со сменой sid.
- Текущие proxy9, test_tcp_io, тесты uasync/queue, затем ASAN/UBSan и полный check.sh.
Windows/FreeBSD требуют отдельной проверки poll/select; Linux PASS её не заменяет.
Доказательства исходного анализа
Диагностический test_socks_http_proxy под gdb: PASS, 16 byte-exact загрузок.
Включены существующие proxy/socket debug через gdb, проектные файлы не менялись.
Полный журнал: /tmp/proxy-design-debug.log; события lifecycle: /tmp/proxy-design-lifecycle.log.
Системные вызовы TLS: /tmp/proxy-design-close.744427,
сводка четырёх соединений: /tmp/proxy-design-close-summary.txt.
Эти журналы относятся к реализации до исправлений.
Реализация и проверка архитектуры
1. UTUN_INSTANCE единолично создаёт и уничтожает общие UDP/ICMP-контексты. TCP-модули
владеют только своими bindings/listeners/потоками. Их rollback не затрагивает общие
контексты. Занятой binding не перезаписывается, повторный init TCP отклоняется.
Proxy закрывается и deferred free обрабатываются до остановки групп/транспорта.
2. Все заданные listeners и TUN mappings обязательны. Частичный отказ возвращает NULL
и освобождает созданные ресурсы; create_from_config сохраняет config у вызывающего.
3. ICMP init создаёт контекст/binding, enable_exit атомарно создаёт raw socket/регистрацию.
Отсутствие прав/поддержки отключает только ICMP exit с явным WARN. Клиентские REPLY
сохраняются; остальные ошибки запуска фатальны. Test loopback включается явно.
4. UDP flow публикуется после всех обязательных шагов. Отказы создания/продления таймера
не оставляют бессрочных flows. Проверяются socket/nonblocking/bind/registration,
sendto, ответные выделения/router/TUN. EINTR повторяется, нулевая датаграмма допустима;
ошибки пакета логируются без очереди повторов, сломанный сокет закрывается.
5. TCP HUP сохраняет high/low-water и ручную остановку чтения. Без доступного I/O
регистрация временно снимается; low_water восстанавливает её с новой generation.
fd/очереди сохраняются; запись обслуживается независимо. Ошибка восстановления
регистрации корректно вызывает on_error, включая destroy владельца из callback.
6. Поздний WINDOW отсутствующего sid отбрасывается с DEBUG. Формат протокола и машина
CONNECTED/WINDOW/FIN сохранены. Реальные ошибки записи/SHUT_WR не превращаются в успех.
HTTPS fixture теперь завершает TLS двусторонне; abrupt close — отдельный режим.
7. Неподдержанный enabled в серверных конфигурациях профильных и TUN-тестов заменён
поддержанным connect_timeout. Контракты заголовков и документы модулей обновлены.
Проверено:
- test_proxy_lifecycle: 25/25 изолированных сценариев, контроль fd/памяти/регистраций/таймеров.
Покрыты socket/nonblock/bind/registration/timer/OOM, отправка/EINTR/нулевой UDP,
частичный отказ instance, повторный запуск, ICMP REPLY после EPERM, HUP re-register,
ожидающая запись и destroy из error callback. /tmp/proxy-fix-lifecycle2.log.
- test_tcp_io: 19/19, HUP high_water=4 на epoll/poll, парковка без busy loop,
точная доставка данных и FIN после low_water, ручная пауза и независимая запись.
- SOCKS/HTTP/HTTPS: оба режима по 16 byte-exact загрузок. Штатный TLS shutdown без
EPIPE/ENOTCONN; abrupt сохраняет ошибки TCP без повреждения доставленных ответов.
/tmp/proxy-fix-check1/test_socks_http_proxy.log, /tmp/proxy-fix-tls-abrupt.log.
- Основной make check: 185/185 PASS. /tmp/proxy-fix-full-check2.log.
Первый check.sh остановлен общим лимитом 141с; local_transport_packets отдельно PASS.
Повтор завершил основной набор, но TUN-этапу помешал чужой процесс на UDP 15002.
- Те же TUN fixtures и идентичный src/utun выполнены в отдельном network namespace:
proxy9/9, burst1/1, load8/8 PASS; load проверил134217728 байт.
/tmp/proxy-fix-network-check.log; копия fixtures/логов: /tmp/utun-proxy-fix-network/.
- Настоящий raw ICMP echo до127.0.0.1 через два ETCP-instance и явный loopback PASS.
UTUN_TEST_ICMP_RAW=1 требует raw socket и не переключается на виртуальный ответ.
/tmp/proxy-fix-icmp-raw.log, /tmp/proxy-fix-icmp-loopback.log.
- ASAN/UBSan/LSan: 16/16 PASS с detect_leaks=1, включая оба TLS-режима, lifecycle,
tcp_io/flush, пакеты, regressions, HTTP, TCP/UDP/ICMP, services, uasync/queue и STCP.
Настоящий raw ICMP дополнительно PASS под теми же санитайзерами с root.
Все встроенные зависимости собраны с sanitizer-флагами в отдельной копии текущих
исходников /tmp/utun-proxy-fix-sanitizers; обычные объекты проекта не менялись.
/tmp/proxy-fix-sanitizer-results.log, /tmp/proxy-fix-sanitizer-icmp-raw.log.
В проверенных Linux-сценариях противоречий владения/rollback/half-close не выявлено.
Сборка и runtime Windows/FreeBSD не проверялись; Linux epoll/poll не заменяют эту проверку.
+200
View File
@@ -0,0 +1,200 @@
# Proxy: TCP, UDP и ICMP
Клиент принимает IPv4 из отдельного TUN через lwIP либо TCP от локальных SOCKS5/HTTP
listeners. Exit открывает обычный IPv4 TCP/UDP или raw ICMP socket. Вся передача proxy
идёт через `etcp_route_send` в `TOPO_GROUP_UTUN`, mode=0. Протокол не имеет слоя
совместимости с прежними форматами.
## Сообщения для router и приёмные метаданные
Это форматы **сервисного API**, а не полные ETCP wire packets. На отправке первый байт —
svc_id, остальное — payload router. Router callback получает такой заголовок:
| Смещение | Размер | Поле |
|---|---|---|
| 0 | 1 | svc_id |
| 1 | 8 | src_node_id |
| 9 | 8 | dst_node_id |
| 17 | 1 | rx_flags |
| 18 | 8 | group_id |
| 26 | N | service payload |
Смещения определены `ROUTER_SVC_*_OFF` в `src/routing_layer/etcp_router.h`.
Заголовок callback имеет размер 26 байт. Сервис освобождает полученные entry/dgram
при любом исходе. На отправке router принимает владение entry/dgram даже при ошибке;
0 означает принятие сообщения router, а не доставку конечному TCP/UDP.
TCP-клиент проверяет src_node_id==via_node_id и group_id==TOPO_GROUP_UTUN, включая
пустые уведомления. UDP/ICMP допускают REQUEST при включённом exit, REPLY — только при
наличии клиента и совпадении src_node_id с его via_node_id; отдельной проверки group
в их callback нет. IP, TCP/UDP-порты и ICMP id/seq в proxy API передаются в network
byte order. TCP stream_id и WINDOW credit копируются как host-order uint32_t:
формат не нормализован между архитектурами с разным byte order.
## TCP и управление окном
Определения — `src/proxy/proxy_protocol.h`.
На отправке: `svc:1, cmd:1, stream_id:4, data` (заголовок 6 байт).
На приёме: router header(26), cmd(1), stream_id(4), data (заголовок 31 байт).
Клиент отправляет в `ETCP_RT_ID_TCP_PROXY_SERVER=0x04`, exit отвечает в
`ETCP_RT_ID_TCP_PROXY_CLIENT=0x08`. На exit поток определяется парой peer_node_id/stream_id;
повторный CONNECT этой пары отклоняется с ERROR.
| Команда | Код | Содержимое после stream_id |
|---|---|---|
| CONNECT | 1 | IPv4:4, порт:2 |
| CONNECTED | 2 | Пусто; exit завершил реальный TCP connect |
| DATA | 3 | 1..4096 байт |
| CLOSE | 4 | Пусто; прекращение потока с дренажом принятой записи на exit |
| ERROR | 5 | Пусто; отмена, на exit немедленно уничтожает TCP и очереди |
| FIN | 6 | Пусто; конец одного направления после всех его DATA |
| WINDOW | 7 | uint32_t дополнительного кредита, ровно 4 байта |
Каждое направление начинает с окна 65536 байт. proxy_flow init оставляет ready=0.
DATA ждёт CONNECTED; SOCKS success и HTTP CONNECT 200 также отправляются только после него.
Локальный TUN TCP handshake выполняет lwIP и сам по себе connect назначения не подтверждает.
При отправке DATA списывается tx_credit, при приёме — rx_credit.
WINDOW допускает только ненулевой кредит без увеличения tx_credit сверх 65536.
Кредит возвращается после потребления локальным TCP: на OS-сокетах после опустошения
write_queue **и write_buf**, в lwIP после принятия байтов ограниченным send-buffer
через tcp_write. Это не подтверждение чтения удалённым приложением.
Для HTTP учитывается число исходных upstream байтов независимо от преобразования
headers/chunks/trailers; кредит возвращается после доставки соответствующего результата в OS TCP.
Backpressure router и окно потока независимы. При ошибке DATA остаётся в очереди/буфере
владельца, регистрируется send-ready waiter и safety-net на 500 мс.
У TCP-клиента с lwIP timer повторяет DATA с force=1, у exit и SOCKS/HTTP — обычный force=0.
Force обходит лимит send_q router, но никогда не окно proxy.
При отсутствии CONNECTED/кредита (-2) отправка ждёт ready/WINDOW, а не только router waiter.
OS TCP сохраняет ограничение read_high_water и явную остановку чтения при HUP.
Если HUP не оставляет работы для чтения/записи, tcp_io временно снимает регистрацию fd;
low_water восстанавливает её с новой generation. Сокет и очереди сохраняются, запись
обслуживается независимо от чтения. Ошибка повторной регистрации закрывает соединение.
## Half-close и удаление потока
Владелец выставляет fin_pending только после отправки всех исходящих DATA.
proxy_flow не видит очередей владельца и сам это условие не проверяет.
FIN не должен ждать записи противоположного направления: обе половины независимы.
Приём FIN ставит FIN-маркер после ранее принятых DATA в OS write_queue; в lwIP FIN
откладывается до дренажа to_lwip и успешного tcp_shutdown.
Окончательное закрытие сырого TCP наступает после обоих FIN и необходимых локальных
очередей/shutdown. Дополнительный обмен CLOSE при штатном half-close не требуется.
На exit CLOSE паузит чтение, отбрасывает непрочитанный read_queue и закрывает сокет
после принятой write_queue; ERROR уничтожает поток сразу. На клиенте CLOSE/ERROR
прекращают поток; HTTP вне туннеля дополнительно проверяет завершённость ответа.
Общий retry повторяет pending CONNECTED → WINDOW → FIN через 5000 tb (500 мс),
используя force=1. CONNECT/CLOSE/ERROR в эту очередь не входят.
Клиентский close_pending может повторяться в poll/free пока поток жив; сервер после
ошибки TCP удаляет поток и делает попытку ERROR. Гарантированного повторения CLOSE/ERROR
после освобождения владельца нет.
Поздний WINDOW отсутствующего потока отбрасывается с debug-диагностикой, без создания потока.
Router close/restart уведомляет сервис entry длиной 26 байт без payload.
Proxy называет это CLOSE_ALL: exit удаляет потоки указанного peer, клиент — все свои
TUN/SOCKS/HTTP потоки к настроенному exit. Это не самостоятельная подкоманда или
9-байтовый пакет на проводе.
## SOCKS5 и HTTP
SOCKS реализует CONNECT к IPv4 или DNS-имени, метод аутентификации 0 (no auth).
IPv6 отклоняется с REP=8; BIND и UDP ASSOCIATE не реализованы.
DNS выполняется асинхронно на ingress, выбирается первая A-запись.
Совмещённые и разделённые greeting/request/data сохраняют непрочитанный хвост.
Ошибка DNS/connect до успеха даёт SOCKS REP=4 либо HTTP 502.
HTTP/1.0–1.1 forward proxy требует absolute http:// URL; upstream получает origin-form,
Host из URL и Via. Обычный запрос отправляется как HTTP/1.1 с Connection: close,
кроме Upgrade. Frontend HTTP/1.1 может сохранять keep-alive: каждый запрос получает
новый stream_id/upstream, pipelining обслуживается последовательно, включая разные цели.
Новый stream начинается после доставки полного ответа и отправки CLOSE/ERROR старого.
Codec проверяет headers до пересылки, поддерживает Content-Length, chunked
с extensions/trailers, промежуточные ответы, HEAD/204/304 и тело до EOF.
Hop-by-hop и proxy authentication поля удаляются, framing/Connection строятся заново.
HTTP/1.0 получает декодированное chunked тело и закрытие. CONNECT ждёт полного блока
headers и реального CONNECTED; согласованный 101 переводит соединение в сырой туннель.
Ранний final response прекращает upload и требует закрытия клиента.
Ошибка до final headers может дать собственный HTTP error; после них закрывает оборванный ответ.
Лимиты: headers/trailers 32 KiB, start line и chunk/trailer line 8 KiB, 512 header fields.
CL+TE, разные Content-Length, obs-fold, управляющие байты и неверные порты отклоняются.
Поддержан только transfer coding chunked; HTTP IPv6-назначения отклоняются с 502.
Headers имеют deadline 30 с, DNS/connect — 15 с, передача/idle/дренаж закрытия — 60 с.
Deadline headers не продлевается каждым байтом. Установленные CONNECT/Upgrade-туннели
используют TCP lifecycle без HTTP deadline. Детали codec — в `src/proxy/http_proxy.h`.
## UDP и ICMP
Контексты принадлежат конкретному UTUN_INSTANCE. REQUEST=1, REPLY=2.
На отправке UDP: `svc:1, cmd:1, src_ip:4, src_port:2, dst_ip:4, dst_port:2, payload`.
ICMP: `svc:1, cmd:1, dst_ip:4, orig_src_ip:4, echo_id:2, echo_seq:2, payload`.
Заголовки отправки — 14 байт, приёма — 39. Отдельного sender_node_id в payload нет.
UDP exit создаёт сокет на tuple client_node_id/src_ip/src_port/dst_ip/dst_port,
проверяет источник ответа, меняет src/dst местами в REPLY. Удаляет неактивные flows
при очередной периодической проверке с порогом >60 с. Клиент восстанавливает IPv4 checksum,
UDP checksum=0 (допустимо для IPv4).
Flow публикуется только после socket/nonblocking/bind/read registration и создания
таймера очистки. Ошибка продления общего таймера закрывает оставшиеся flows.
sendto проверяется по точной длине, включая успешный нулевой payload; EINTR повторяется.
EAGAIN/ENOBUFS и ошибки отдельного пакета логируются без очереди повторов; сломанный
сокет удаляется. Ошибки выделения ответа, router send и доставки в TUN диагностируются.
ICMP exit использует один raw socket; заменяет id/seq token и сопоставляет
wire_id/wire_seq/IP цели с pending. Исходные id/seq восстанавливаются для клиента.
Порог ожидания >5 с проверяется периодически. Raw reply проходит проверку ICMP checksum.
Нечётный хвост checksum обрабатывается без чтения за буфером.
`icmp_proxy_init` создаёт общий контекст и binding; `icmp_proxy_enable_exit` отдельно
открывает raw socket. EPERM/EACCES/отсутствие поддержки raw отключают только ICMP exit
с WARN; клиентские REPLY сохраняются. Прочие ошибки запуска прерывают создание instance.
Тестовый `test_loopback` включается явно, закрывает raw socket и даёт виртуальные ответы
независимо от прав процесса. Production его не включает.
Pending raw request создаётся только с рабочим таймером истечения срока ожидания.
Send API принимает payload 0..65507, TUN delivery — 0..65506 из-за однобайтового
префикса в uint16_t длине entry. UDP send требует локальный UDP-контекст, ICMP send
его не требует. Доставка ответа требует клиентский TUN; своего окна/повтора датаграмм нет.
## Жизненный цикл, диагностика и проверки
Все операции и callbacks — в uasync владельца. Proxy init запрашивает binding
после router init; client/server имеют разные service IDs. Общие UDP/ICMP создаёт
и уничтожает UTUN_INSTANCE; TCP-модули освобождают только собственные ресурсы.
Контексты нужны для включённого exit либо клиентского TUN; SOCKS/HTTP без TUN их не требуют.
Заданные listeners, TUN mappings и bindings обязательны: ошибка откатывает создание.
Повторный init активного модуля и попытка занять чужой binding отклоняются.
При уничтожении instance TCP-клиент/сервер и общие контексты закрываются до остановки
групп и транспорта; ua обрабатывает отложенные free. Точные контракты — в .h.
Категория proxy: info — подключения/закрытия/транзакции, debug — окна/паузы/повторы,
trace — трафик и секундная диагностика exit TCP-очередей/пулов/OS buffers.
Для поиска проблемы также полезны socket и etcp_route.
Регрессионный тест включает proxy=debug через UTUN_TEST_DEBUG=1.
| Тест | Что проверяет |
|---|---|
| test_http_proxy | HTTP codec, разбиения headers/chunks, framing, лимиты, source credit, Upgrade |
| test_proxy_packets | IPv4/UDP/ICMP, checksum и payload длиной 0..1501 |
| test_proxy_regressions | Handshake/CONNECTED, HTTP pipeline/EOF/deadlines, окна/FIN, peer+sid, reentrancy, TUN options/fragment validation, per-instance UDP/ICMP |
| test_proxy_lifecycle | 25 сценариев отказов/rollback, fd/память/таймеры, владение bindings, ICMP без raw, HUP re-register и destroy из callback |
| test_tcp_proxy_client | Create/destroy с тестовым TUN, без утечек |
| test_tcp_proxy_server | TCP echo через tcp_conn; сам по себе не проверяет протокол exit |
| test_socks_http_proxy | Реальные SOCKS IPv4/domain, HTTP и HTTPS CONNECT, byte-exact загрузки; штатный TLS shutdown либо UTUN_TEST_TLS_ABRUPT=1 |
| test_udp_proxy / test_icmp_proxy | Два instance через ETCP; ICMP: явный test_loopback, UTUN_TEST_ICMP_RAW=1 требует настоящий raw socket |
| test_tcp_io | 19 сценариев, включая HUP high/low-water на epoll/poll, ручную паузу и независимую запись |
| test_tcp_io_flush | Callback после последнего синхронного send |
Проверка 2026-10-24: профильные тесты проходят на Linux. ICMP проверен и через явный
test_loopback, и настоящим raw echo до 127.0.0.1 с правами root. Штатный TLS shutdown проходит без EPIPE/ENOTCONN;
аварийный сохраняет реальные ошибки записи, все 16 загрузок остаются byte-exact.
Тестовые конфиги используют поддержанный connect_timeout. Итоги полного прогона
и sanitizer-проверок — в [proxy_fix_plan.txt](proxy_fix_plan.txt).
Windows/FreeBSD runtime в этом окружении не проверены.
Создание proxy из конфигурации в utun_instance.c исключено под _WIN32.
IPv4-фрагменты на входе proxy TUN отклоняются; сборки фрагментов нет.
Описания модулей: `src/proxy/*_doc.md`; все семь заголовков `src/proxy/*.h` сверены с реализацией.
+409
View File
@@ -0,0 +1,409 @@
# Жизненный цикл ядра и сервисов
CLI выполняет daemon fork до создания `UASYNC`, SQLite, сокетов и фоновых потоков.
Сервисы и их workers создаются уже в дочернем процессе. Родитель ждёт подтверждения
успешной инициализации и записи PID-файла; ошибка старта возвращается вызывающему.
`test_daemon_worker` проверяет настоящий запуск без `-f`, наличие дискового потока,
штатный SIGTERM и ненулевой статус launcher при ошибке PID-файла.
Все операции выполняются в потоке единственного `UASYNC` экземпляра, вне callbacks
останавливаемых модулей. GUI передаёт команды в этот поток.
```c
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config);
if (!inst || utun_core_start(inst) < 0) /* обработать ошибку */;
if (chat_service_start(inst) < 0) /* обработать ошибку */;
if (utun_service_start(inst) < 0) /* обработать ошибку */;
chat_service_stop(inst); /* UTUN продолжает работать */
chat_service_start(inst); /* загружает сохранённые каналы */
utun_service_stop(inst); /* чат продолжает работать */
utun_instance_destroy(inst);
```
`utun_instance_init()` — сокращение для запуска ядра, UTUN и чата, если чат включён
в конфигурации. Desktop GUI запускает ядро, UTUN и чат явно; TUN создаётся при
`[global] tun_enabled=yes`. Без TUN и NAT запуск чата не требует прав администратора.
Desktop подтверждает start только после готовности всех сервисов и uasync poll;
ошибка TUN откатывает запуск и возвращается в GUI. Android запускает ядро и чат без UTUN.
Повторный start работающего сервиса и повторный stop допустимы. Ошибка старта
сервиса откатывает созданные им ресурсы. `utun_instance_stop()` останавливает цикл
приложения; освобождение ядра выполняет `utun_instance_destroy()`.
Для TUN в desktop `vibechat.cfg` задаются те же параметры, что и у CLI:
```ini
[global]
tun_enabled=yes
tun_ifname=vibechat
tun_ip=10.42.0.1
[routing]
my_subnet=10.42.0.1/32
route_subnet=10.42.0.0/24
```
Адреса и маршруты выбираются под свою сеть; изменения применяются после перезапуска.
На Windows нужна `wintun.dll` рядом с `vibechat.exe` и запуск с правами администратора.
CMake копирует DLL и лицензии из `lib/`; Windows-упаковка явно включает DLL, проверяет
её архитектуру x64 и зависимости, поскольку `LoadLibrary` не отражается в импортах EXE.
На Linux необходимы доступ к `/dev/net/tun` и права на настройку интерфейса и маршрутов.
Успех запуска требует успешной инициализации всех обязательных ресурсов: заданных
listen-сокетов, мониторинга, TUN и всех `route_subnet` при включённом TUN. Частичный
набор сокетов или маршрутов — ошибка запуска. `utun_started` устанавливается только
после завершения всех шагов. Системные маршруты хранятся отдельным списком успешно
созданных записей; stop и откат удаляют только этот список, в обратном порядке.
Маршрут, на добавлении которого ядро вернуло ошибку, не становится собственностью
UTUN и при откате не удаляется. Ошибка одного удаления не прекращает очистку остальных.
`auto_sockets=android` выбирает внешнее управление сетью: frontend регистрирует
ConnectivityManager до запроса запуска и передаёт конфигурацию сокетов через JNI.
Оба нативных монитора ОС в этом режиме не создаются. Рабочие listen-сокеты по-прежнему
должны открыться полностью. Android подтверждает запуск по `SERVICE_STARTED`, а не
по успешному созданию потока; после события передаётся свежий снимок адресов.
| Владелец | Ресурсы |
|---|---|
| Ядро | Идентичность узла, реестр узлов и групп, SQLite, транспортные сокеты, NCD, маршрутизатор, общая репликация, время, мониторинг |
| UTUN | Группа UTUN, запросы соединений из конфигурации, DATA binding, TUN, системные маршруты, NAT transport |
| Чат | CHAT-группы, запросы подключения и join, экземпляры синхронизации каналов, chat API, медиа, личные сообщения, звонки и headless API |
| Группа | Сессии пиров, её маршруты и логические каналы, планировщик подключения и recovery |
| Сессия пира группы | NCD handle с момента CONNECTING, согласованные эпохи, состояние обмена таблицей, очередь отправки |
Proxy настраивается при создании ядра. Instance владеет общими UDP/ICMP-контекстами;
TCP client/server владеют только своими bindings, listeners и TCP-потоками.
Общие контексты нужны для exit либо клиентского TUN; SOCKS/HTTP без TUN их не требуют.
Ошибка обязательного listener, TUN mapping или регистрации откатывает создание instance.
Недоступность raw ICMP по правам или поддержке отключает только ICMP exit с WARN;
клиентские ICMP REPLY и TCP/UDP продолжают работать.
`utun_instance_destroy()` закрывает proxy и обрабатывает отложенные free до остановки
UTUN/CHAT-групп и транспорта. Waiters/таймеры снимаются до освобождения их владельцев.
Остановка отдельного TCP proxy-модуля не уничтожает общие UDP/ICMP-контексты.
SQLite открывается ядром один раз: `db_path/chats.db`, либо `:memory:`, если путь
не задан. Остановка чата сохраняет каналы и сообщения в этой БД. В варианте
`:memory:` данные сохраняются только до уничтожения ядра.
Работающий общий транспорт сохраняется, пока он нужен хотя бы одному из пиров. Закрытие запроса одной группы
не разрывает соединение, нужное другой группе. При остановке группы отменяются
её планировщики, запросы, таймеры и ожидания очередей; отправляется LEAVE,
закрываются логические каналы маршрутизатора и удаляется её ожидающий транзит
на общих соединениях. Транзит других групп сохраняется. Отложенный LEAVE может временно
удерживать транспорт самостоятельно, не обращаясь к уже удалённой группе.
ETCP удаляет транспорт после 30 секунд непрерывного DOWN только при отсутствии локальных NCD handles.
Первоначальное ожидание UP тоже учитывает владение. Повторный DOWN не продлевает
таймаут; восстановление UP отменяет его. Если срок истёк при наличии handles, транспорт
сохраняется, а исходный deadline остаётся. Освобождение последнего handle во время DOWN
оставляет транспорт до этого deadline либо запускает удаление в следующем callback,
если срок уже истёк. Внешние ETCP refs задерживают только освобождение памяти.
Явный force-close и отмена неудачной попытки последнего владельца закрывают сразу.
Входящий UP без локальных handles сохраняется: он может быть нужен пиру. CLOSE/KEEP_ALIVE
привязан при запуске ядра, поэтому мягкое закрытие работает и на чисто входящем узле.
На CLOSE при наличии handles отвечаем KEEP_ALIVE; без них закрываем транспорт.
KEEP_ALIVE полностью отменяет FIN_WAIT. Ожидание CLOSE обычно занимает 5 секунд,
для спящего пира увеличивается до его RX deadline. Сам факт DOWN во время FIN_WAIT
завершает ранее запрошенное мягкое закрытие.
Контроль RX независим от отправки keepalive. Объявленный сон не отключает проверку
жизни: срок от последнего RX равен максимуму согласованного keepalive timeout и
объявленного остатка ACTIVE+SLEEP с запасом normal timeout. Фазовые таймеры и наши
отправки срок жизни пира не продлевают. Пробуждение сразу сокращает deadline до normal timeout.
После истечения последнего линка транспорт
становится DOWN, затем может удаляться через 30 секунд, когда локальных NCD handles нет. `test_etcp_server_lifecycle`
проверяет сроки, несколько путей, восстановление, владение, сон и CLOSE/KEEP_ALIVE
с управляемыми часами и контролем освобождения памяти/таймеров.
Остановка чата сначала прекращает внешние команды и фоновые задания, затем
закрывает медиа и сервисы, синхронизацию и группы, после чего освобождает chat core.
`media_async_destroy()` дожидается завершения native workers и вызывает каждый
ожидающий callback с `MEDIA_ASYNC_CANCELLED`, пока его контекст ещё существует.
Worker резервирует completion до запуска и передаёт его через `uasync_post_reserved`;
периодического polling нет. Destroy выполняется вне callbacks uasync, после join
отменяет pending post и завершает done ровно один раз. Uasync живёт до конца этой очистки.
Подготовленные блоки отменённых задач не регистрируются; учёт назначений подготовки
сохраняется для последующей очистки. Это синхронное ожидание: длительное
транскодирование или распознавание речи может задержать stop; принудительного
прерывания этих библиотек нет.
Владелец принимает не более 16 задач, включая уже завершённые workers до доставки
completion. Отказ запуска/неверные аргументы дают синхронный done с -1, остановка
владельца — с `MEDIA_ASYNC_CANCELLED`; при NULL done только логируется ошибка.
Примитивы `media_async.h` синхронные: отмена задачи не откатывает выполненные ими
действия. `ma_copy_file()` публикует копию внутри worker после проверенных записи и
закрытия temp; вызывающий отвечает за очистку своего результата при отмене.
Подробные файловые контракты, 64-битные размеры и ошибки RNG описаны в `media_async.h`.
Групповой crypto worker хранит один `file_io_temp` прямо в задаче вложения.
Общий валидатор MIME не выполняет I/O: send вызывает его в uasync до создания
задачи/ключа, файловый codec повторяет проверку в worker до открытия файлов.
`encrypted_file_write/read` только готовят закрытый durable temp; worker публикует
его одним rename, completion подтверждает результат в БД/UI. Отмена до completion
не публикует сообщение; cleanup учитывает реально созданные/заменённые файлы.
Ошибка синхронизации каталога после rename означает установленное имя без подтверждённой
durability; отложенный discard уже не удаляет результат. Общей транзакции ФС/БД нет.
До запуска workers и после их завершения recovery очищает собственные stale temp
в `media/decrypted`; общий file_io-реестр сохраняет активные temp всех instance.
`media_delivery_init()` вызывается после подготовки chat core и media_index, пока
общая БД и группы живы; подписка на роли обязательна. Репликация хранит отдельный
контекст и CM handle для каждой пары «CHAT-группа–пир». DOWN отменяет текущий RPC,
сохраняя handle для восстановления CM; TIMEOUT закрывает его перед повторным открытием.
Отдельный ETCP DOWN не означает потерю всех путей CM. До free контекста отменяются
его waiter, pump, watchdog и собственный handle; destroy отменяет также сверку ролей.
Обычный stop сохраняет каталог, его эпоху и RX-курсоры. Потеря локальной роли удаляет
чужие объявления вместе с RX-курсорами только этой группы одной транзакцией;
удаление канала удаляет также собственные объявления. Страница репликации и её
RX-курсор фиксируются одним самостоятельным commit; вложенная внешняя транзакция
не считается сохранённым прогрессом. Wire-контракт находится в `media_delivery_proto.h`.
Перед файловой подготовкой назначение записывается в `media_preparations`. Успешный
`media_index_commit()` регистрирует весь файл и снимает эту запись в одном savepoint;
после ошибки/отмены путь остаётся известен, даже если worker уже опубликовал копию.
Это гарантия записи БД; файловые изменения и внешний SQL COMMIT ей не охватываются.
При обычной ошибке откатывается только регистрация; SQLite может сама отменить всю
транзакцию при тяжёлой ошибке. Общая база берётся из `ui_state.media_base` и повторно
используется после stop/start. Загрузчик не меняет существующую базу.
Входящая загрузка и исходящая подготовка резервируют нормализованный путь в общем
media_delivery до начала записи. Отмена снимает загрузку с индекса, закрывает CM handles
и таймеры, освобождает резервацию; выполняющийся обработчик удерживает сам контекст.
Поэтому отмена и повторный start из progress callback допустимы, а done старой загрузки
вызывается после возврата из progress. Каждая попытка блока имеет отдельные UUID и temp:
поздние пакеты старого держателя/попытки не изменяют новый файл.
Producer и relay-выдачи независимо удерживают файл блока. После отмены producer
непроверенный источник завершается с ошибкой; проверенный может обслужить оставшихся
читателей. Последний владелец удаляет temp. При удалении канала отменяются и producer,
и все его выдачи; перед освобождением выдачи отменяются её waiter, pump и таймер.
DONE выдачи следует только после проверки блока и принятия всех его данных router.
После проверки всех блоков загрузчик собирает отдельный temp, проверяет операции
чтения/записи/close, полный размер и SHA-256, затем атомарно заменяет имя назначения.
Ошибка до публикации сохраняет прежний файл. Затем выполняется регистрация в SQLite:
при её ошибке опубликованный файл остаётся учтён в media_preparations для очистки.
Проверка и сборка блоков пока синхронны в uasync; контракт workers относится к исходящей подготовке.
Локальное удаление канала ждёт его исходящие video/register задачи. Последний
completion продолжает удаление; новые исходящие медиа канала в это время отклоняются,
подготовленные результаты ожидающих отправок не публикуются как новые сообщения.
После завершения задач отменяются загрузки/раздача и удаляются учтённые файлы, включая
назначения неуспешных подготовок. Общие файлы других каналов сохраняются. Очистка файлов
выполняется вне внешней транзакции: SQL-откат не восстанавливает удалённый файл.
При ошибке очистки записи оставшихся файлов сохраняются для повторного вызова;
удаление метаданных канала и неполная файловая очистка диагностируются отдельно.
Низкоуровневый `media_index_delete_channel()` сам workers не останавливает.
Точные ограничения путей и контракт повторной регистрации находятся в `media_index.h`.
Подготовка файлов в `src/video/video.h` выполняет remux подходящих H.264/AAC либо
перекодирование в MP4 H.264/YUV420P и AAC. Worker пишет уникальный соседний временный
файл, завершает декодеры/ресемплер/FIFO/кодеры, проверяет trailer, закрытие и метаданные.
Конечное имя заменяется только успешным completion в uasync. При отмене, включая
отмену уже подготовленного результата до доставки completion, временный файл удаляется,
старое назначение сохраняется. Совпадающие исходник/назначение и файловые алиасы
отклоняются; исходный файл должен оставаться неизменным до done. В процессе занята
одна задача до completion; атомарный захват исключает гонку между экземплярами ядра.
Перед done занятость освобождается, поэтому callback может запустить следующую задачу.
JPEG использует те же правила файлов, но извлекается синхронно в отдельном frontend worker.
Probe также синхронен и не обещает ограниченного времени чтения.
Live media хранится в RS-кольце с общим индексом PTS; каждый локальный handle владеет
отдельным cursor. Пауза player не останавливает сетевой ingest и не закрепляет старые
части против вытеснения. После FIN publisher может снять своё владение, сохранив
завершённый источник для оставшихся локальных readers. Последний close освобождает
источник, индекс и кольцо. Новая публикация отсоединяет readers прежней эпохи.
Android preview собственного эфира не создаёт reader: Camera2 пишет прямо в TextureView
и encoder Surface. UI attach/detach меняет лишь camera session; audio/encoder/origin сохраняются.
UI, ожидающие команды camera handler и каждая camera session удерживают preview target отдельно.
TextureView возвращает false при destroy и снимает ссылку UI; Surface/SurfaceTexture освобождаются
после закрытия последней session. CameraDevice.onClosed снимает также оставшиеся ссылки своих
sessions, если Android не прислал отдельные session.onClosed. Повторные callbacks безопасны.
Capture stop запрещает attach и снимает своё владение target; camera HandlerThread завершается
после camera close и освобождения outputs всех созданных sessions.
Отказ preview output возвращает encoder-only session и не завершает сам эфир.
При stop/destroy отменяются delivery и media-index callbacks до освобождения store;
destroy отсоединённого cursor не обращается к store. Frontend прекращает audio callback
и worker до освобождения своего DSP; borrowed handle/RS pointers между потоками не передаются.
Чтение video cursor запрашивает следующий кадр по готовности decoder, независимо от
PTS показа; audio cursor сохраняет временной lookahead. Viewers готовят ограниченный батч
на запрос: отдельно задаются число видеокадров, audio lookahead и число всех записей.
Desktop и Android принимают до 16 записей и обрабатывают треки по их готовности.
VIDEO_END/AUDIO_END следуют за последними пакетами своего трека, не ожидая другого трека;
общий END завершает вход, а окно ждёт опустошения обоих выходов. GAP/CLOSED отменяют старый батч.
Получение батча погашает запрос, включая повторные обращения до pull; скрытого чтения следующего батча нет.
Callback готовности только будит desktop worker и не вызывает API ядра/bridge. Stop снимает callback
под mutex viewer до освобождения его контекста; detach будит ожидающие workers до уничтожения manager.
На Android signal принадлежит audio worker и живёт до stream_stop, join video worker и close mixer input;
ядро только уведомляет его. Surface освобождается после join video worker.
В wait не удерживаются mutex bridge/ядра. Расход PCM будит тот же signal, уведомления сливаются без потери пробуждения.
GUI и audio callback будят worker при движении часов/освобождении места; сна между готовыми пакетами нет.
Каждый reader имеет отдельные audio/control и video workers. Audio worker читает батчи и готовит
до 250ms PCM через Opus/SoundTouch, не удерживая блокировку consumer. Запас пополняется при наличии
данных без обязательного prefill. SPSC передаёт PCM вместе с отображением исходных PTS; mixer/audio
callback только копирует и смешивает. Короткая блокировка защищает output map/clock и lifetime,
в ней нет декодирования, time-stretch или MediaCodec/FFmpeg. EOF продолжает prepare до опустошения DSP.
Android video worker получает ограниченный mailbox (два кадра) и держит один рабочий input и один готовый output MediaCodec; свободный
input buffer получает лишь для уже готовых данных и сразу возвращает codec.
Первый decoded frame разрешает движение clock/audio. Будущий output ждёт PTS и
приостанавливает новые video requests, сохраняя независимую обработку audio/control.
Если первый доступный звук позже KEY, cursor отсчитывает стартовый audio lookahead от
первого аудиопакета, сохраняя это основание неизменным. Opus/SoundTouch получает PCM до
запуска audio clock; неподвижные часы не разрешают читать дальше этого ограниченного окна.
PTS поданных кадров служат диагностике/проверке запаздывания, а не лимиту заполнения codec.
Смена revision заменяет только video decoder и его диагностические измерения;
GAP/EXPIRED начинают новую точку KEY. Нехватка сети сохраняет decoder references.
Reset увеличивает video generation; результаты прежнего поколения не публикуются. Video worker
после EOF остаётся доступен для reset до явного stop. Audio clock после старта не ждёт видео:
просроченные изображения декодируются для сохранения references, но не показываются.
Desktop FFmpeg держит один рабочий AVPacket и два готовых изображения; EAGAIN оставляет
packet до следующего прохода. FIN подаёт EOS и дренирует выход до decoder EOF;
конец GUI требует также показа оставшихся изображений и завершения audio tail.
Отсутствие прогресса decoder при наличии работы имеет отдельную диагностику и срок 10s.
ACK-история принадлежит ядру и обновляется только при первом найденном подтверждении
пакета. REINIT/close соединения не стирает уже подтверждённые за последнюю секунду байты.
Общий `stream_publisher` Android/desktop владеет отдельным 100ms таймером TX-снимков;
close/detach отменяют его в uasync до уничтожения instance. Bridges копируют снимок
под mutex, frontend не обращается к живым очередям транспорта. Android экран подписан
на отдельный StateFlow статистики; desktop обновляет статистику отдельно от сырого превью.
Publisher не регистрирует stream event callback и не подменяет callback viewer.
Bridge удерживает внешний mutex на время producer/open/stop и destroy общего publisher;
actor не вызывает frontend callbacks под своим mutex. Reserved post, media timer,
stats timer и stream handle освобождаются до контекста publisher.
Desktop `MainWindow` владеет `StreamPublishEngine`, а окно управления лишь отображает
его состояние. Закрытие окна сохраняет эфир, новый запуск запрещён до конца FIN drain.
Qt capture callback сохраняет последний `QVideoFrame` для preview и ограниченно ставит кадр
в очередь отдельного долгоживущего image worker. Он конвертирует `QVideoFrame` в `QImage`;
video worker получает готовый image и не создаёт Qt Multimedia TLS, которое могло задержать его exit на Windows.
Video/audio workers выполняют encode/DSP вне GUI и hardware callbacks. Отдельные поколения источника и encoder позволяют менять
видеокодек/bitrate/rotation без повторного выбора экрана в портале. Ошибки Qt capture
обрабатываются через queued callback, чтобы не уничтожать backend из собственного callback.
Stop запрещает producers, закрывает Qt capture и подписку на аудиозахват. Отдельный join worker
дожидается полного выхода video/audio threads, включая native TLS Qt; GUI продолжает обрабатывать события.
После join отправляется FIN с drain 2s; закрытие последнего микрофона может продолжаться параллельно.
Microphone lease и owner сохраняются до завершения обоих путей. Новый start ждёт также FIN drain.
При завершении приложения engine уничтожается до SoundManager и ядра; detach в uasync
закрывает оставшийся publisher. Destructor ждёт workers в локальном event loop без пользовательского ввода,
чтобы thread cleanup мог обратиться к GUI; start и внешние сигналы engine в это время запрещены.
Workers никогда не держат borrowed instance/handle.
Microphone lease публикации исключает call/recorder; рация согласует вход через `AudioCapture`.
Одинаковый выбранный вход имеет одну аппаратную сессию и профиль трансляции. Разные входы независимы.
Подключение/отключение рации не останавливает устройство трансляции: атомарный слот и счётчик readers
дожидаются только callback удаляемого потребителя. Последняя подписка закрывает устройство.
Если первой работала рация, публикация заменяет профиль захвата своим; после остановки публикации
оставшаяся рация продолжает тот же захват. Recovery останавливает callbacks, workers TX и один раз
открывает общий вход; RX рации сохраняется. Каждый потребитель обрабатывает собственную копию PCM.
На Android `MediaAudioController` владеет общим выбором устройства, AudioManager и одним аппаратным reader.
Маршрутизация использует `CallAudioRouter`; AudioTrack рации и микшера просмотра регистрируются у одного владельца.
`MicrophoneCapture` раздаёт независимые ограниченные очереди постоянного PCM 48k stereo; моно апмиксуется.
После подключения публикации встроенный микрофон использует CAMCORDER, сохраняемый до последней подписки.
Смена профиля заменяет только аппаратный AudioRecord: камера, видеокодер и source_epoch продолжают работать.
Общая сетка по capture timestamps имеет запас 200ms; пропуски заполняются тишиной, старый PCM не переносится вперёд.
Stop публикации удаляет её подписку до join Opus worker и FIN, сохраняя захват для оставшейся рации.
PTT и PCM рации проходят через одну очередь. Последняя подписка завершает общий reader;
контроллер закрывает аппаратный вход, сохраняя выходы. Перед Telecom все владельцы снимаются,
а `MediaAudioController.awaitIdle()` дожидается освобождения входа и глобального маршрута.
Ограничения фонового захвата рации, её разрешения и foreground-сервис продолжают действовать.
Fatal ошибка audio worker передаётся в GUI с session ID и завершает всю публикацию;
откат видеоконфигурации применяется только к video/capture. Поздний отказ старого publisher
не завершает новую сессию. Причина ошибки сохраняется в окне после окончания drain.
Desktop-звонок и рация проверяют поступление корректного PCM, включая первый callback.
Секунда без данных отключает TX и фиксирует понятную ошибку в GUI; цифровая тишина является данными.
RX продолжает работать, рация снимает PTT, автоматического переоткрытия неисправного входа нет.
Выбор другого микрофона или новая сессия снимают ошибку. Неисправное устройство закрывается
в отдельном потоке; stop не присоединяет уже запущенный close worker. Новый захват ждёт его завершения.
В звонке microphone lease удерживается до конца закрытия даже после stop. Общий AudioCapture
рации сначала отсоединяет callback потребителя; последний владелец освобождает backend в worker.
Сброс контекста и уничтожение владельца дожидаются worker до освобождения miniaudio-контекста.
Штатное закрытие исправного устройства сохраняет синхронный порядок.
`AudioDevice` подстраивает буфер по подтверждённым backend XRUN: WASAPI DATA_DISCONTINUITY
на входе, ALSA EPIPE, PulseAudio capture hole/overflow и playback underflow. Notification
только увеличивает атомарный счётчик, решение и лог выполняются в GUI recovery timer.
Исходные параметры, включая Linux capture period=10мс, сохраняются до повторных сбоев.
Первая секунда после start исключена; нужны ошибки в двух опросах за 5с. Период увеличивается
на 5мс, не чаще раза в 5с, до общей запрошенной ёмкости 100мс с учётом числа периодов.
Фактический размер согласует backend; исходный или уже достигнутый больший размер не уменьшается.
При достижении предела повторные сбои логируются без новых перезапусков. Подобранный запрос
сохраняется при recovery, сбрасывается при смене устройства/сессии; стабильный звук не вызывает reopen.
Подстройка микрофона закрывает устройство асинхронно, останавливая TX worker и сохраняя RX.
Общая capture-сессия отсоединяет callbacks до close и заново подготавливает потребителей после init.
Новая подписка во время close откладывается, stop удаляет её без ожидания backend.
Первое переоткрытие после подстройки не использует backoff; при ошибке открытия действует обычный retry.
В desktop-видеозвонке Windows/Linux камера живёт в отдельном процессе того же
`vibechat --call-camera-worker`. Служебный вход выполняется до конфига, БД и
single-instance lock. Перечисление устройств, Qt/V4L2, преобразование и освобождение
аппаратных кадров происходят только там. GUI получает собственный `QImage`; TX не
обращается к драйверу. Смена codec не перезапускает захват и не ждёт следующего GOP.
Пиксели одного кадра передаются через mapping (Windows — pagefile-backed объект ОС,
Linux — временный файл), метаданные/команды — через локальный сокет.
Worker записывает mapping до FRAME; следующий write разрешён
только после ACK, который GUI посылает после копирования в свою память. Общего mutex
нет. В worker ожидает только последний raw-кадр; поколения отсекают старые результаты.
Preview обновляется по готовности, TX получает condition-variable wake. PTS сохраняет
время захвата; кадры старше 250мс отбрасываются до encode и перед send. Отброшенный
encoded frame требует следующего KEY, поскольку encoder уже продвинул reference chain.
Отключение камеры и удаление окна не ждут worker. Независимый Session сохраняет
QProcess и mapping до finished, затем удаляет себя после отсоединения владельца.
Зависший stop получает kill через 1.5с, отсутствие прогресса — через 5с; таймеры
живут в родительском процессе. При выходе приложения kill отправляется без wait;
GUI не уничтожает живой QProcess. При отказе новый процесс запускается лишь по
явной команде пользователя и после finished прежнего. Лог категории call показывает
последнюю стадию enumerate/start/convert/release/stop и возраст кадра.
`test_call_camera_capture` проверяет настоящую IPC-цепочку с подставным устройством:
блокировки start/map/stop, поздние кадры после смены поколения, задержку 720p,
повтор после отказа, отсутствие рестарта камеры при смене codec, удаление окна,
выход приложения и независимое освобождение mapping в каждом процессе.
Статус транспорта UP подтверждает восстановление физического пути. READY сессии
группы означает согласованный JOIN/ACCEPT и завершённый обмен её таблицей; именно
его ждёт recovery. REINIT меняет эпохи обмена, сохраняя рабочие маршруты. Эпохи
защищают от сообщений старой сессии и не задают срок жизни маршрута.
Проверки: `test_services` проверяет независимый stop/start, общий NCD в CONNECTING,
сохранение идентичности/сокетов/БД, повторную загрузку каналов, отмену workers,
заблокированного медиа-потока, транзита и ожиданий заменённой очереди. `test_chat_join_e2e`
проверяет join и распространение membership между узлами без UTUN-сервиса.
В Android звонковой сессией владеет `CallController` уровня приложения; события
ядра и команды Telecom обрабатываются один раз в main thread, без повторного
проигрывания событий при создании ViewModel. UI получает `StateFlow` и передаёт
пользовательские команды. `CallService` владеет `CallAudioEngine` и частичным
wake lock: перед исходящим вызовом или принятием входящего запускается foreground
режим `phoneCall|microphone`, затем сигналинг и аудио. Завершение сначала
останавливает I/O и дожидается аудиопотоков, затем освобождает native-аудио,
wake lock и сервис. Перед `NativeLib.stop/restart/destroy` выполняется тот же
порядок; новый звонок разрешается после `SERVICE_STARTED` нового ядра.
Активность ядра задаётся явно: видимая Activity или незавершённый звонок удерживают
ACTIVE и выключают Android standby. Состояние не истекает по таймеру. После
завершения звонка в фоне возвращается standby; экран может гаснуть во время
разговора независимо от частичного wake lock сервиса.
Если работают рация, публикация или просмотр трансляции, исходящий Android-звонок
требует подтверждения их остановки; входящий показывает предупреждение до принятия.
После подтверждения/принятия `CallController` выключает участие в рации, завершает
публикацию и просмотр, асинхронно ждёт освобождения их аудио и только затем запускает
сигналинг и звонковое аудио. Foreground-сервис резервируется сразу из видимой Activity.
Отмена во время освобождения не запускает устаревший звонок.
До окончания звонка повторное включение рации/трансляции заблокировано.
После закрытия звонкового аудио и Telecom Connection ранее включённая рация
возвращается в прежний канал; удержания PTT сброшены. Удалённый канал и остановленное
ядро не восстанавливаются. Публикация и просмотр трансляции автоматически не возобновляются.
`utun_set_client_activity()` в сборке с `UTUN_HAVE_STANDBY` применяет фактический
режим duty-cycle до рассылки событий активности; JNI не переключает его отдельно.
Повторная команда не перезапускает фазу и анонсы, но первый фоновый запуск включает
standby даже при начальном `client_activity=STANDBY`. Keepalive подписан на режим
и фазы самого standby; новые UDP/TCP-линки сразу анонсируют текущую фазу после
handshake. Пробуждение по таймеру и внешней сети сначала сообщает ACTIVE-окно
пирам, затем запускает ожидающие задачи. При выходе сначала восстанавливаются
линки; deinit отменяет ожидающие задачи и очищает подписки старого ядра.
+1295
View File
File diff suppressed because it is too large. Load diff
+104
View File
@@ -0,0 +1,104 @@
# Независимая STUN-диагностика NAT
Модуль `src/transport_layer/stun_nat.c/h` измеряет NAT на уникальных путях существующих IPv4 UDP-сокетов.
Ключ группы — семейство, фактический IP и netif; имена и рабочие порты не участвуют.
Для каждой группы создаётся один собственный неблокирующий UDP-сокет с тем же локальным IP,
привязкой к устройству и Linux SO_MARK / FreeBSD FIB. Тестовый порт выбирается случайно из 49152–65535.
Модуль сам регистрирует чтение в uasync, принимает STUN и закрывает свой сокет после измерения.
STUN не меняет исходный ETCP fd, его порт и обработчики. Через `stun_nat` API модуль
`nat_detection` получает предварительную оценку для рабочих сокетов того же IP/netif
с совпадающими mark/FIB/device. Если два разных IP видят одинаковый стабильный внешний
IP:порт и есть трансляция адреса, рабочий профиль получает EIM? с source=STUN и inbound=UNKNOWN.
Это действует и для обычных серверов без запроса с незнакомого IP. Filtering тестового порта
не переносится; измерение mapping рабочего сокета всегда имеет приоритет.
Оценка снимается после TTL, отключения STUN, смены пути или противоречащего внешнего IP.
Тестовый mapped endpoint не копируется в `nat_addr` и NODEINFO: адрес для подключения
должен быть получен наблюдением самого рабочего сокета. Детектор описан в `doc/nat_detection.txt`.
## Конфигурация
```ini
[stun]
enabled=yes
server=stun-a.example.org:3478
server=stun-b.example.org:3478
# Дополнительные строки — резервные серверы.
[debug]
nat=debug
```
Без `enabled=yes` модуль выключен. Без порта используется 3478. Список сохраняет порядок конфига.
При включении нужны минимум две строки `server`; для измерения выбираются два ответивших сервера с различными IP.
Совпадающие IP после DNS пропускаются. DNS асинхронный, с использованием существующего IPv4 A-резолвера.
IPv6, SOCKS UDP и `only_local` пропускаются с явным статусом; TCP не проверяется.
## Результат
`stun_nat_get_result(instance, "имя исходного сокета", &result)` копирует локальный снимок:
Имя сначала разрешается в текущий рабочий сокет, затем в группу. API ядра
`stun_nat_get_socket_result(instance, socket, &result)` дополнительно проверяет
текущие IP/netif и параметры маршрутизации. Несколько рабочих портов получают общий снимок.
- `local` — тестовый IP:port; `servers[2]` и `mapped[2]` — реальные серверы и наблюдавшиеся внешние адреса;
- `translated`: -1 неизвестно, 0 трансляция не наблюдалась, 1 внешний адрес отличается;
- `mapping`: UNKNOWN, MATCH, DEPENDENT, EIM, ADM, APDM;
- `filtering`: UNKNOWN, EIF, ADF, APDF;
- причины неполного результата, статус, время измерения и обнаруженная поддержка RFC 5780.
- ключ группы, generation, evidence_id, measured_at_us, количество рабочих сокетов и признак refresh.
Обычный STUN даёт MATCH при одинаковых адресах и DEPENDENT при разных. MATCH не доказывает EIM,
а обычные Binding-ответы не позволяют определить filtering. Первоначальный сервер опрашивается повторно:
смена его mapping во время проверки инвалидирует классификацию как нестабильную.
## RFC 5780
Корректные OTHER-ADDRESS и RESPONSE-ORIGIN включают автоматическую попытку Behavior Discovery.
Нужны две публичные IP и два порта на серверной стороне, доступные во всех четырёх комбинациях.
1. Обычный Binding получает внешний адрес и альтернативный endpoint.
2. CHANGE-REQUEST с флагами IP+port проверяет EIF.
3. Если ответ не пришёл, контрольный Binding проверяет живость сервера и стабильность mapping.
4. CHANGE-REQUEST только port различает ADF и APDF; после таймаута снова выполняется контрольный Binding.
5. Перед публикацией ADF/APDF альтернативные endpoints проверяются обычными Binding-запросами.
Недоступный альтернативный endpoint даёт UNKNOWN, а не ложную строгую фильтрацию.
6. После filtering проверяется mapping: альтернативный IP с первоначальным портом, затем при необходимости
альтернативный IP с альтернативным портом. Сравнение различает EIM, ADM и APDM.
7. Второй сервер и повтор первого проверяют независимую пару и стабильность результата.
Все filtering-тесты выполняются до открывающих фильтр отправок на альтернативные endpoints.
Собственные предыдущие STUN-отправки учитываются: при пересечении адресов следующего сервера
сомнительный тест filtering пропускается. ERROR 420 и игнорирование CHANGE-REQUEST дают UNKNOWN.
Аутентификация STUN не реализована: требующий её сервер фиксируется как ошибка, затем используется резервный.
Запрос содержит случайный 96-битный transaction ID и FINGERPRINT. Проверяются источник ответа,
transaction ID, границы/выравнивание атрибутов, XOR-MAPPED-ADDRESS, RESPONSE-ORIGIN и FINGERPRINT при его наличии.
Повторы одной транзакции сохраняют ID: RTO от 500 мс удваивается, всего семь отправок, последнее ожидание 8 секунд.
Новые транзакции всех проб ограничены десятью в секунду; ожидания не блокируют event loop.
## Жизненный цикл и проверки
Ядро запускает модуль при `utun_core_start`; остановка уничтожает его до сетевых ресурсов и config.
Перезагрузка конфига отменяет старые DNS/таймеры и создаёт новый модуль.
Раз в три секунды выполняется локальная сверка групп: новые/изменённые проверяются,
группа удаляется после исчезновения последнего рабочего сокета. Смена имени/представителя
или удаление дубликата не теряет кэш. Несовпадение mark/FIB/device внутри одного ключа
даёт UNKNOWN с причиной incompatible routing policy.
TTL результата 300 секунд, refresh 180 секунд; неполные/неудачные проверки повторяются через 30 секунд.
Во время штатного refresh доступен прежний результат до TTL; его возраст не обновляется.
Использованные тестовые порты не переиспользуются в течение жизни ядра, включая reload STUN.
История портов освобождается только при окончательном destroy ядра.
`stun_nat_recheck(instance)` явно повторяет измерение на новых портах, в том числе после смены маршрута без смены IP.
Публичные операции выполняются в uasync-потоке экземпляра.
INFO содержит начало измерения и итог с адресами, классификацией и причинами UNKNOWN.
DEBUG содержит этапы, transaction ID, повторы, RTT, DNS и очистку; WARN/ERROR описывают аномалии.
Linux-тест `test_stun_nat` использует реальные локальные UDP-сокеты и ускоренные часы:
обычный STUN, все классы RFC 5780, недоступные альтернативы, потеря запроса, неверные/поздние ответы,
резервные серверы, DNS, нестабильный mapping, отмена и повторная проверка, изменение состава сокетов.
Сценарии проверяют сохранность рабочего fd/порта, предварительный EIM?, приоритет ETCP,
снятие оценки после TTL, совпадение IP/netif и освобождение таймеров.
Ссылки: [RFC 5780](https://www.rfc-editor.org/rfc/rfc5780), [STUN RFC 8489](https://www.rfc-editor.org/rfc/rfc8489).
+943
View File
@@ -0,0 +1,943 @@
# Задачи по проекту
Сюда пишется список задач с короткой аннотацией. Если задача большая и имеет ТЗ - то ТЗ оформляется отдельным файлом, а сюда помещается аннотация и ссылка на ТЗ.
## Текущие задачи
[ ] **member_files: cleanup удаляет чужой temp при отказе создания загрузки** — воспроизведено 24.10.2026 на a198cfb9.
test_member_files_network падает в фазе23: локальный O_EXCL open возвращает MF_EXISTS,
затем TRANSFER_FINISH без проверки владения вызывает mf_disk_local_remove(t->temp).
Прежнее назначение37байт сохраняется, заранее созданный чужой .part исчезает.
Проверены общий check.sh и отдельный повтор: одинаковый FAIL preservation case=0, target=1, foreign_temp=0;
отсутствие .part дополнительно проверено в обоих оставшихся тестовых каталогах.
Нужен учёт успешного создания собственного temp, включая отмену до PREPARE и отказ max_bytes;
проверять/удалять можно только созданный этой передачей файл. Код этого модуля в ETCP-ветке не менялся.
Логи: /tmp/etcp-solo-native-full-check.log, /tmp/etcp-solo-member-network-repeat.log;
подробный первый лог: /tmp/etcp-solo-member-network-suite-failed.log.
[+] **media_async/media_delivery: завершить сверку контрактов и исправлений** — завершено 24.10.2026.
Проверены все11 заголовков двух каталогов по полным реализациям; исправлены alias файлов,
граница voice duration, переанонс SQLite и расхождение MIME. Результаты каждого фикса приведены ниже.
Устаревший _desc.txt заменён описанием текущей архитектуры; уточнены sync.md, encrypted_attachments.md
и media-раздел service_lifecycle.md. Общий lifecycle за пределами media остаётся частично проверенным.
Итог, границы и доказательства: [media_async_audit_findings.txt](media_async_audit_findings.txt).
[+] **encrypted_file: одинаково проверять MIME при записи и чтении контейнера** — исправлено 24.10.2026.
Прежний write проверял только strlen(type)<32 и принимал пустое значение/недопустимые символы,
тогда как read их отвергает. Пять реальных write/read с теми же ключом/UUID/подписанным hash:
write=0, read=-1; контрольный application/octet-stream проходит полный roundtrip.
Прежний chat_attachment_send также проверял только длину и мог принять такой MIME до записи ключа/запуска worker.
Воспроизведение: /tmp/media-content-type-audit.c и .mk, /tmp/media-content-type-audit.log;
исходники/контейнеры: /tmp/media-content-type-audit-FdtMC3/. Исходный файл не меняется.
Реализован один bounded encrypted_file_validate_type для send/write/read; 1..31 байт,
ASCII33..126 без двойной кавычки/обратной косой черты — действующие ограничения read.
Отказ до создания ключа/файлов; формат, криптография и файловое владение сохранены.
Регрессии: все256 значений байта,92 допустимых;8 ранних отказов writer/send,3 roundtrip,
7 отказов чтения аутентифицированных плохих метаданных. Проверены прежние выходы, файлы, fd/temp и отсутствие ключей/worker.
./build.sh --no-gui -j4 PASS; профиль10/10 и ASAN/UBSan/LSan4/4 PASS, detect_leaks=1.
Логи: /tmp/media-mime-fix-build.log, /tmp/media-mime-fix-profile-results.log,
/tmp/media-mime-sanitizer-results.log. Полный check и Windows/FreeBSD для этого фикса не запускались.
Подробности: [media_async_audit_findings.txt](media_async_audit_findings.txt). Сверка описаний завершена.
[+] **media_delivery: проверять записи SQLite перед переанонсом локальных блоков** — исправлено 24.10.2026.
Прежний announce_local_blocks не проверял длины media_id/block_id, диапазон chunk и полный chat_id.
Реальный обработчик отправил все7 повреждённых записей: UUID длиной1/17 байт, chunk=-1/2^32, chat_id=111junk.
При UUID длиной1 байт остальные15 байт сетевого поля взяты за границей BLOB;
chunk=-1 превращается в4294967295, 2^32 — в0, 111junk — в группу111.
Стенд использует настоящие SQLite/Ed25519; отправка перехвачена, пакеты в реальную сеть не уходят.
Воспроизведение: /tmp/media-announcement-audit.c и .mk; лог: /tmp/media-announcement-audit.log.
Реализованы проверки типов/точных длин UUID, chunk через int64 и канонического ненулевого decimal chat_id
до копирования/кастов/отправки. Плохие строки пропускаются с WARN; prepare/bind/step/finalize проверяются.
UUID копируются до вложенных SQL. Сохранены void API, поштучные анонсы и формат wire.
test_media_delivery_sql: 36 случаев полей (32 отказа/4 корректных), смесь2/3 и5 SQL faults с recovery.
Проверяются точные wire UUID/group/chunk, отсутствие оставшихся statements и ERROR вместо успешного лога при отказе SQL.
Сборка PASS; профильные12/12 и ASAN/UBSan/LSan4/4 PASS, detect_leaks=1, все встроенные зависимости инструментированы.
Логи: /tmp/media-announcement-fix-profile-results.log, /tmp/media-announcement-fix-sql-final.log,
/tmp/media-announcement-sanitizer-results.log. Полный check и Windows/FreeBSD не запускались.
Архитектура, границы воспроизведения и регрессии: [media_async_audit_findings.txt](media_async_audit_findings.txt).
Последующая сверка описаний двух каталогов завершена; итог указан в начале списка.
[+] **media_async voice_file: согласовать верхнюю границу длительности с метаданными** — исправлено 24.10.2026.
Прежняя проверка samples/channels/rate >86400 пропускала полные кадры 24ч+20..980мс,
хотя attachment_validate отклоняет duration_ms >86400000. В штатном pipeline это означает
лишнюю подготовку файла перед отказом публикации; дефект относится к граничным записям длиннее суток.
Диагностический вызов реального voice_file_encode с остановкой на создании Opus:
60 случаев, все пять частот и 1/2 канала; 20 подтверждённых расхождений проверки.
/tmp/media-voice-duration-repro.c, /tmp/media-voice-duration-repro.mk, /tmp/media-voice-duration-repro.log.
Реализован общий предел миллисекунд в attachment.h; voice_file проверяет 25..4320000 полных кадров
до создания encoder/открытия output. Валидатор и video completion используют тот же предел.
test_attachment: 120 граничных случаев; 70 ранних отказов, прежний файл и metadata сохранены.
Настоящий Opus encode/decode: 11 записей, включая 500мс с хвостом для всех пяти частот и 1/2 канала.
Границы метаданных voice/video 86400000/86400001мс проверены через validate/encode/decode.
./build.sh --no-gui -j4 PASS; профильные тесты 10/10 PASS, включая DM, encrypted attachments и video.
Логи: /tmp/media-voice-fix-build.log, /tmp/media-voice-fix-profile-results.log, /tmp/media-voice-fix-profile-logs/.
Подробности и ограничения воспроизведения: [media_async_audit_findings.txt](media_async_audit_findings.txt).
На этом этапе новые полный check и sanitizer-прогоны не выполнялись; последующие проверки и итог аудита указаны выше.
[+] **media_async encrypted_file: не уничтожать source через alias назначения** — исправлено 24.10.2026.
write/read отвергают только одинаковые строки; /./ и hardlink обходят проверку одного файла.
write возвращает0, заменяя исходные30 байт контейнером пустого файла; read возвращает0,
заменяя входной ciphertext182 байта plaintext30 байт. Проверены реальные функции, inode и SHA256.
Реализован один file_io_temp в ak_file; codec готовит закрытый проверенный temp,
worker публикует его одним rename. Target не открывается с усечением; alias проверяется до work и перед publish.
Durability задаётся на весь срок temp; ошибка directory sync после rename не считается откатом.
Cleanup удаляет только собственные артефакты; crash recovery учитывает temp в media и decrypted.
DM не вызывает codec: сохранить его контракт общих sync/publish helpers при изменениях file_io.
Детали, проверка архитектуры исправления и незавершённый аудит: [media_async_audit_findings.txt](media_async_audit_findings.txt).
Воспроизведение: /tmp/media-encrypted-alias-repro.c, /tmp/media-encrypted-alias-repro.log.
Добавлены восемь alias-сценариев, 23 отказа I/O с контролем fd/temp/метаданных,
отмена настоящего worker после rename до completion, startup/restart обоих деревьев.
Профильные21/21 и основной make check185/185 PASS; proxy9/9, burst1/1, load8/8 PASS
в отдельном network namespace,134217728 проверенных байт. ASAN/UBSan/LSan21/21 PASS, detect_leaks=1.
Логи: /tmp/media-fix-full-check.log, /tmp/media-fix-network-check.log, /tmp/media-fix-sanitizer-results.log.
Последующий аудит остальных контрактов media_async/media_delivery завершён; Windows/FreeBSD не проверялись.
[+] **Proxy: исправить HUP/backpressure, владение и rollback** — завершено 24.10.2026.
Согласованный проект, причины и результаты: [doc/proxy_fix_plan.txt](proxy_fix_plan.txt).
HUP больше не обходит read_high_water/ручную паузу; idle HUP снимается с poll,
low_water восстанавливает регистрацию с новой generation, запись остаётся независимой.
Общими UDP/ICMP владеет instance; TCP-модули освобождают только свои ресурсы.
Proxy закрывается до групп/транспорта. Listeners/mappings обязательны, частичный запуск откатывается.
Raw ICMP запускается отдельно: EPERM сохраняет клиентские REPLY и TCP/UDP; остальные ошибки запуска фатальны.
UDP проверяет создание flow, таймеры и точную отправку; нулевой payload и EINTR обработаны.
Поздний WINDOW завершённого sid — DEBUG drop. Неверный enabled убран из серверных тестовых конфигов.
Штатный TLS shutdown проходит без EPIPE/ENOTCONN; abrupt сохраняет реальные ошибки TCP.
Оба TLS-режима: 16 byte-exact загрузок. Настоящий raw ICMP и явный loopback PASS.
test_proxy_lifecycle: 25/25 сценариев отказов без утечек fd/памяти; test_tcp_io: 19/19, epoll/poll HUP.
Основной make check: 185/185 PASS. Общий check.sh сначала не уложился в141с,
затем TUN-этапу помешал занятый другим процессом UDP15002. TUN-тесты повторены
с тем же бинарником в отдельном network namespace: proxy9/9, burst1/1, load8/8 PASS,
134217728 проверенных байт. ASAN/UBSan/LSan: 16/16 и отдельный raw ICMP PASS, detect_leaks=1.
Логи: /tmp/proxy-fix-full-check2.log, /tmp/proxy-fix-network-check.log,
/tmp/proxy-fix-sanitizer-results.log, /tmp/proxy-fix-icmp-raw.log.
Windows/FreeBSD runtime не проверялся.
[ ] **ETCP: полезная утилизация нескольких каналов и доставка при зависании части линков** — общая задача открыта.
После push исследован одиночный дуплекс2Мбит/с на изолированной базе a198cfb9.
Точные7763 ACK показывают задержку обратной очереди: пример DATA15.909мс, hold0.029мс,
обратный ACK32.219мс; DOWN продолжает ждать flight7644 при цели5915байт.
Контрольный20-секундный замер1.726/1.658Мбит/с, retries=0; простой ACK-резерв/округление/уменьшение бюджета
не дают устойчивого прохождения. Предел окна DOWN сокращает фазу до6..22мс, но короткий FAIL1.656 остаётся.
Прототипы не перенесены в рабочий ETCP/BBR. Добавлены сценарий duplex-slow и UTUN_MULTIPATH_SAMPLE_MS=1000..60000
для длительных замеров; прежние пороги сохранены. Далее — причины остановки DATA в CRUISE/REFILL:
физическое окно, общий admission budget и pacer; подробные данные/команды — doc/etcp_multipath_plan.txt.
24.10.2026: исправлены RTT. min_rtt наследует метку короткого минимума, вместо омоложения кеша каждым ACK;
прежний минимум20мс заменяется после10с своего возраста (прежде15с). Метки/hold PROBE_RTT используют полные uint64 TB;
проверены 32-битная граница, нулевые младшие32бита срока, длительный idle, hold>=200мс и ACK-раунд.
Исправлено замораживание DATA-измерений при скачке RTT: RTO берёт max(DATA EWMA, timestamp-RTT своего линка).
Формула1.2*RTT+1.5*DATA_jitter+3мс и clamp10мс..1с сохранены; BBR обновляют только действующие DATA ACK.
Новый профиль rtt-change:20->100->20мс, проверяет обновление RTT, снятие stalled, старение/снижение minRTT и доставку.
DATA-измерения возобновились примерно за203мс, после перехода повторов нет; minRTT103.794мс, после возврата23.941мс.
До фикса стенд дал2035 повторов и остановку DATA ACK-модели; отдельная регрессия настоящего retrans-таймера также FAIL.
Пересчёт attempts-таймера вызывается и при сокращении timestamp-RTT без новых DATA/ACK: регрессия123->27мс
до фикса сохраняла96мс лишнего ожидания. Пересчёт идёт до внешнего RTT callback, который может закрыть conn/линк;
OOM таймера также безопасно закрывает conn из RX. Негативный ASAN-тест ловит UAF при обратном порядке.
BBR24/24; test_etcp_windows63 сценария в обоих режимах часов PASS; четыре ASAN/UBSan-теста и ASAN rtt-change без ошибок/утечек.
Check.sh186/186, proxy9/9, burst1/1, load8/8 PASS,134217728 проверенных байт,189с после последней правки.
Полная матрица: первые6 скоростных профилей, все9 отказов и rtt-change PASS; остался1 FAIL2Мбит/с:1.662<1.700.
Одиночный8Мбит/с7.076/7.019, агрегат8.737/8.717 из10. Далее —2Мбит/с, STCP backpressure, смена RTT нескольких путей/BW и повторный отказ.
Логи/CSV /tmp/etcp-rtt-complete-* и /tmp/etcp-rtt-close-*;8652 среза проверены, отдельные332 RTT: /tmp/etcp-rtt-complete-rtt-change.csv/.html.
Детали: doc/etcp_multipath_plan.txt. Предыдущий этап — исправление BW:
24.10.2026: исправлена недооценка BW растянутыми ACK: достоверный RX clock заменяет ACK clock,
interval=max(send_us,ceil(delivered*delta_rx_us/delta_rx_bytes)); без RX-базы/прогресса max(send_us,ack_us).
Сохранены send-граница, проверка пары/переполнения/idle и логические ACK; wire/RTT/RTO/окно не менялись.
DEBUG ACK показывает исходные интервалы и выбранные часы. До фикса точная регрессия FAIL, после PASS.
test_etcp_windows: 61 сценарий с обоими режимами часов PASS; BBR 20/20, восемь ASAN/UBSan-тестов без ошибок/утечек.
Check.sh 186/186, proxy 9/9, burst 1/1, load 8/8 PASS, 134217728 проверенных байт, 201с.
Полная последовательная матрица: все 7 скоростных и 9 отказных профилей PASS; дуплексный агрегат 8.768/8.789 из10.
Три прогона рабочего кода: одиночный 8Мбит/с даёт 7.045..7.081 в обе стороны, агрегат проходит каждый раз.
Ранних ACK-пакетов 0; steady retries=0; ACK на одиночном 8Мбит/с пути 0.170..0.180Мбит/с.
Остаётся нестабильность одиночного дуплекса 2Мбит/с: один повтор дал 1.672<1.700Мбит/с.
Далее — этот недобор, STCP backpressure, отдельные проверки смены RTT/BW и повторного отказа.
Обнаруженный на этом этапе дефект возраста min_rtt исправлен и проверен следующим этапом выше.
Логи/CSV: /tmp/etcp-rx-clock-*; 8372 среза проверены, HTML /tmp/etcp-rx-clock-matrix.html.
Подробности и предыдущие этапы: doc/etcp_multipath_plan.txt.
Предыдущий этап — диагностика до исправления ACK:
Продолжение 24.10.2026: диагностика срезов 100 мс (настройка 10..1000 мс), 104 поля; CSV и лог буферизуются до drain.
Видны очереди, callbacks/deferred waiter, DATA/ACK, flight/cwnd, BBR/RTT/RTO, долг/готовность pacer и канал эмулятора.
tools/etcp_diag_report.py проверяет CSV и строит автономные интерактивные графики с исходными срезами.
Дуплекс: 598 срезов; two-stuck/ASAN: 5208/5196 срезов, без ошибок sanitizer/утечек; CHECK сохраняет накопленный лог.
Итоговый check.sh 184/184, proxy 9/9, burst 1/1, load 8/8 PASS, 166 с; JS smoke-проверка графиков/фильтров PASS.
На одиночном 8 Мбит/с пути ACK занимают 0.347/0.390 Мбит/с, среднее ожидание ACK-only 230/271 мкс вместо окна 2 мс.
В срезах side=1 path=1 DATA готовы, а BBR задаёт около 6.3 Мбит/с; PROBE_RTT временно уменьшает окно до 32.9 тыс. байт.
Дедлайн обходит посторонний wake sender. Дальше разбирать абсолютные deadlines ACK; простого !timer недостаточно.
Алгоритмы протокола на этом этапе не менялись; полный дуплекс всё ещё не проходит скоростные пороги (5 нарушений, 8.513/8.337).
Логи/CSV: /tmp/etcp-duplex-diag-*; просмотр: /tmp/etcp-duplex-diag-view.html; детали — doc/etcp_multipath_plan.txt.
24.10.2026: реализованы сканирование send_q с приоритетом готовых повторов и ограниченная подгрузка новых DATA.
Заблокированный пакет пропускается. История путей пакета (32 байта) исключает новый обход до перебора допустимых;
история/RR фиксируются после принятого send, удаление линка очищает слот до callbacks/повторной выдачи ID.
После 3 RTO без ACK-прогресса tx_stalled исключает путь из повторов при наличии незастрявшего UP-пути.
Все застрявшие пути допускаются, новые DATA сохраняют прежний выбор; действующий ACK отправки после флага снимает его.
Старый/чужой ACK не восстанавливает путь; reset очищает состояние. Новых очередей/таймеров/копий нет, RTO сохранён.
Windows: 51 сценарий PASS с управляемыми/реальными часами; восемь ASAN/UBSan-тестов без ошибок/утечек.
Контроль занятого на 1 мс рабочего пути: latency 377.012 вместо 736.623 мс, ready_wait 2.318 мс, профиль PASS.
Отрицательный контроль задержки готового повтора 200 мс остаётся FAIL по очереди и общей задержке.
Полная последовательная multipath-матрица: все 9 профилей отказов PASS, первые 6 скоростных профилей PASS.
Two-stuck: latency 379.997 мс при пределе 462.219 мс; ещё 3/3 повтора — 374.981..378.557 мс.
Load-drop/load-hold: паузы 39.234/39.355 мс, steady goodput 7.221/7.468 из 8 Мбит/с.
Max latency под насыщенной нагрузкой 161.287/86.306 мс записана; отдельным порогом она здесь не ограничена.
Two-stuck/load-drop также проходят ASAN/UBSan с detect_leaks=1 без ошибок/утечек.
Остаётся дуплекс: 8.412/8.435 из 10 Мбит/с, все 7 нарушений матрицы — его скоростные пороги.
Далее — дуплекс, STCP backpressure, отдельные проверки смены RTT/BW и повторного отказа.
Полный check.sh: 184/184, proxy 9/9, burst 1/1, load 8/8 PASS, 134217728 проверенных байт, 197 с со сборкой.
Длинная multipath-матрица запускается отдельно; её общий результат остаётся FAIL из-за дуплекса.
Логи: /tmp/etcp-send-scan-*.log; подробности — doc/etcp_multipath_plan.txt.
Предыдущая диагностика:
24.10.2026: тесты отдельно проверяют обработку RTO (опоздание<=20мс), ожидание готового повтора (<=100мс)
и доставку сверх заданной задержки канала (<=100мс). TX учитывается после принятия send.
Быстрые профили сохраняют общий предел100мс; длинные учитывают по одному RTO отказавших путей плюс100мс.
Остальные8 профилей PASS; two-stuck нестабилен: обычный прогон дал738.317мс при пределе459.332мс,
история фрагмента324 —1->2->1->2->0. Готовый повтор не застревает: лишнее ожидание создаёт ещё один круг RTO.
Контроль с занятостью рабочего пути всего1мс воспроизводит736.623мс latency и534.548мс паузы;
точный лог показывает свободное окно рабочего пути и ожидание pacer при выборе2->1.
Контроль задержки уже готового повтора на200мс проваливает новую проверку очереди, а не маскируется бюджетом RTO.
Slow-drop/two-stuck/load-drop проходят ASAN/UBSan с detect_leaks=1 без ошибок/утечек.
Полный check.sh184/184, proxy9/9, burst1/1, load8/8 PASS,134217728 байт,161с; multipath запускается отдельно.
Перед изменением транспорта отдельно проработать выбор путей без DATA-прогресса, ограниченные пробы
и восстановление, сохраняя физический UP и защиту от поздних ACK. Ранние копии исключены; дуплекс не менялся.
Логи: /tmp/etcp-recovery-stages-*.log/.csv; подробности — doc/etcp_multipath_plan.txt.
Предыдущий этап:
24.10.2026: повтор ждёт другого initialized+UP канала, даже когда прежний уже готов.
При отсутствии таких альтернатив разрешён прежний; DOWN последней альтернативы будит sender.
Новых состояний/ресурсов/таймеров нет. ACK/control и burst-FILLER не блокируются ожиданием DATA.
Регрессия до исправления воспроизводит возврат на прежний путь; после — windows44 сценария
с управляемыми/реальными часами PASS, восемь ASAN/UBSan-тестов без ошибок/утечек.
Полный check.sh184/184, proxy9/9, burst1/1, load8/8 PASS,134217728 проверенных байт,161с.
Профили отказов:7/9 PASS. load-drop/load-hold проходят каждый3/3, паузы39.796..44.982мс,
steady goodput7.189..7.476 из8Мбит/с оставшегося канала. Пороги сохранены.
Slow-drop остаётся FAIL: пауза238.569мс, latency264.727мс; two-stuck219.338/380.177мс.
В них4 нарушения порогов; доставка/порядок/ресурсы проходят. Полная скоростная матрица не повторялась.
Трейс показал ожидание первого RTO на медленном пути и повторный выбор прежнего пути под нагрузкой;
ожидание в хвосте send_q в этих опытах не объясняет основную паузу (максимум2.6/3.5мс).
Ранние копии до RTO исключены пользователем24.10.2026. При UP повтор ждёт RTO своего пути;
закрытие линка по-прежнему возвращает недоставленные DATA без ожидания RTO.
При RTT200мс RTO уже243мс без jitter: порог паузы100мс этого ожидания не учитывает.
Пороги тестов не менялись; дальше отдельно оценивать ожидание RTO, готовность повтора и доставку.
Остаются здоровье/пробы каналов, STCP backpressure и дуплекс; спекулятивных ранних копий в плане нет.
Логи: /tmp/etcp-retry-alternative-*.log/.csv; диагностический observer — /tmp/etcp-recovery-probe.c/.mk.
Предыдущий контроль после исправления pacer: таймер ждёт первого разрешённого TB, а не погашения всего долга;
кредит1мс, BBR/cwnd/min_rtt, RTO и wire-формат сохранены. Нового состояния и ресурсов нет.
До исправления регрессия получает10TB вместо1TB; после него18 сочетаний долга/дроби проходят.
Проверены INT_MAX, ранний/поздний callback, ограниченный burst, OOM повторного таймера,
rate change и reset/close из настоящего uasync callback. Windows42 сценария PASS с обоими режимами часов;
восемь профильных тестов ASAN/UBSan с detect_leaks=1 без ошибок/утечек.
check.sh184/184, proxy9/9, burst1/1, load8/8 PASS (134217728 байт),162с.
Полная последовательная матрица c7a53009 с прежними порогами: FAIL,14 нарушений —
8 скоростных проверок дуплекса и6 проверок пауз/задержек slow-drop,two-stuck,load-drop/load-hold.
Equal14.438/16, bandwidth9.052/10, RTT20/200:14.299/16Мбит/с; первые6 скоростных профилей PASS.
Дуплекс8.394/8.458 из10Мбит/с остаётся FAIL. Drop-forward PASS: пауза58.001мс, задержка83.656мс.
Паузы slow-drop/two-stuck/load-drop/load-hold:239.421/201.381/213.644/208.899мс;
задержки slow-drop/two-stuck:264.044/482.693мс. Доставка, порядок и контроль ресурсов проходят.
Это один прогон; сравнение с исходными15 нарушениями не доказывает устойчивое улучшение всей матрицы.
Далее отдельно разбирать ограничения BBR/cwnd и PROBE_RTT, здоровье линков и STCP backpressure.
Результаты/план: doc/etcp_multipath_plan.txt; /tmp/etcp-pacer-ready-matrix.log/.csv,
/tmp/etcp-pacer-ready-{windows-final,real-time,full-check}.log и /tmp/etcp-pacer-ready-asan-test_*.log.
Реализованы начальный pacing от 4*MSS/RTT, точные DATA B/s и ограниченный кредит shaper,
разделение логического seq/payload и физических attempt_id, ACK/RTT/RTO каждого пути,
ротация повторов A→B→C→A при UP-каналах, rollback отказавшего send и запрос потерянного till.
Wire attempt_no сокращён до2 байт, общий для всех каналов одного seq; внутренний ключ — (seq<<16)|attempt_no.
DATA header7 вместо13 байт, ACK entry10 вместо16. Номер1..65535; исчерпание номера/seq меняет эпоху через REINIT.
Normalizer не менялся. Пара seq/номер не повторяется в одной эпохе; старые ACK отсекаются до разбора.
Проверка компактного формата: windows38 сценариев, session/BBR/transport/init_liveness PASS;
те же5 тестов ASAN/UBSan с detect_leaks=1 без ошибок/утечек. check.sh184/184, proxy9/9, burst1/1, load8/8 PASS.
bandwidth2+8 RTT20/20:8.715Мбит/с, retries=0, профиль PASS. Логи: /tmp/etcp-attempt16-*.log/.csv.
RTO=clamp(ceil(1.2*RTT+1.5*jitter+3мс),10мс,1с), округление до мкс. Пределы: попытки 16384/линк,
32768/соединение, ACK 16384/линк. Без RTT начальный RTO=1с.
STARTUP завершается после целого раунда устойчивой очереди выше бюджета, без искусственного изменения BW.
BBR20/20, windows26 сценариев PASS; восемь тестов ASAN/UBSan с detect_leaks=1 PASS; основной набор183/183 PASS.
check.sh завершён успешно; дополнительные tcp_proxy_full/burst/load PASS (8 соединений,134217728 проверенных байт).
Полная штатная multipath-матрица до смены формулы RTO: FAIL, 20 нарушений. Есть недобор RTT20/200мс и bidirectional;
slow-drop даёт паузу401мс, два зависших пути676мс, DROP/HOLD под нагрузкой229–243мс.
После смены RTO один полный прогон даёт 15 нарушений: slow-drop244мс, two-stuck278мс,
load-drop/load-hold96/83мс; установившиеся интервалы исправных каналов без повторов.
windows31 (20 наборов проверки реального RTO), BBR20/20, session/transport PASS;
windows/session/transport ASAN/UBSan с detect_leaks=1 PASS. Логи: /tmp/etcp-rto-margin-*.log.
Jitter-контроль RTT20/200мс: при случайной добавке 0..10/30мс новая формула даёт отдельные лишние повторы,
старая в контрольном прогоне — без повторов. При jitter30мс скорость снижена у обеих формул;
коэффициенты не объявлены оптимальными. Логи: /tmp/etcp-rto-jitter-review.log, /tmp/etcp-rto-jitter-old.log.
Последний check.sh после смены RTO: 182/183, FAIL только test_bgp_route_exchange (повтор отдельно с debug PASS).
test_encrypted_attachment в этом прогоне PASS; воспроизведённый ранее дефект media остаётся открытым.
Логи: /tmp/etcp-rto-margin-full-check.log, /tmp/etcp-rto-margin-bgp-repeat.log.
Три прогона скоростей: 8+8 RTT20/20 — 13.868..14.083Мбит/с; RTT20/200 — 13.567..13.951;
2+8+2 RTT20/80/200 — 10.566..10.749 из12; 2+8 одновременно в обе стороны — 8.080..8.379 из10 на направление.
DATA-повторов в измеряемых исправных интервалах нет. Подробные результаты: /tmp/etcp-multilink-quality-20261023.csv/.txt.
BGP-падение из-за INIT/remote_keepalive исправлено; отдельная задача ниже содержит итог проверки.
Дуплекс проверен18 последовательными опытами в /tmp/etcp-duplex-native/, исходники проекта не менялись.
ACK compression завышает BW:170502 байта приняты за186.36мс, sender sample использует150.73мс.
Нативный прототип counter/time в ACK сокращает пики очереди до23.1мс вместо52.9мс, но все скоростные пороги не проходит.
Коэффициент282/256+3мс и запас10мс при busy-флаге не дали устойчивого выигрыша, пики очереди выросли до35.5/33.7мс.
В штатном агрегате ACK занимает0.515..0.559Мбит/с на направление (5.15..5.59% полосы), большая часть — ACK-only.
Реализована только коррекция BW по точке приёма:16 байт добавляются при count>0, пустой ACK остаётся8-байтовым.
Перестановка/дубли ACK, независимые часы, простой/reset, переполнения и границы MTU/формата проверены.
Windows35 сценариев и ещё4 профильных теста PASS, те же5 тестов ASAN/UBSan без ошибок/утечек;
check.sh184/184, proxy9/9, burst1/1, load8/8 PASS (/tmp/etcp-bw-correction-full-check.log).
Отдельный дуплекс2+8 RTT20/80:8.223/8.182Мбит/с, пики очереди агрегата22.8мс, retries=0;
профиль остаётся FAIL по5 прежним порогам скорости. bandwidth2+8 RTT20/20:8.844Мбит/с, PASS.
RTT20/200,8+8:13.453Мбит/с, очередь<=24мс, retries=0, FAIL по3 прежним порогам скорости.
Логи: /tmp/etcp-bw-duplex-final.log/.csv, /tmp/etcp-bw-{bandwidth,rtt}.log/.csv, /tmp/etcp-bw-asan-*.log.
Объединение ACK в пределах прежнего дедлайна2мс отложено; окно/RTT/RTO и политика ACK не менялись.
Дуплексный запас и busy-флаг не добавлялись и не считаются подтверждённым исправлением.
One-way->duplex->one-way:3/3 PASS; ASAN/UBSan не нашли ошибок/утечек, скоростной профиль остаётся FAIL.
Подробности/уточнённый план: doc/etcp_multipath_plan.txt; результаты: /tmp/etcp-duplex-native/results.json,
/tmp/etcp-duplex-native/summary.csv и ack_bandwidth.csv. Политика окна прототипов в репозиторий не переносилась.
Здоровье/пробы каналов и STCP backpressure пока не реализованы; ранние копии исключены, пороги тестов сохранены.
OOM после принятия данных исправлен аварийной сменой эпохи с сохранением физических линков.
Старый поток отбрасывается без воспроизведения; после согласования владельцы получают REINIT.
Очистка reset не выделяет память; убрана реконструкция inflight, новых OOM-очередей/таймеров нет.
Проверены отказы RX/TX, flush/waiter/session-таймеров, RNG, reset/close из callback и ноль выделений при reset.
windows31 сценарий, queue_regressions77/77 и шифрованное восстановление обеих сторон PASS;
десять тестов ASAN/UBSan с detect_leaks=1 PASS. Далее — здоровье каналов; ранние повторы исключены.
После OOM-правок полный check.sh: 181/183; FAIL BGP route exchange и encrypted attachment.
BGP отдельно с диагностикой PASS; encrypted attachment повторно FAIL из-за удаления активного temp кешем (задача ниже).
Отдельно proxy9/9, burst1/1 и load8/8 PASS (134217728 проверенных байт); /tmp/etcp-oom-reinit-proxy-check.log.
План и подробные результаты: [doc/etcp_multipath_plan.txt](etcp_multipath_plan.txt).
Логи: /tmp/etcp-attempts-matrix.log, /tmp/etcp-attempts-asan-test_*.log,
/tmp/etcp-oom-reinit-asan-test_*.log, /tmp/etcp-abort-encrypted-recovery.log.
[+] **Crypto: повторное освобождение ключа после отказа инициализации потоковой подписи** — исправлено 24.10.2026:
Оба init создают ключ и SHA-512-контекст локально, заполняя state только после полного успеха.
Перед первым использованием state={0}; отказ оставляет его пустым. Повторный init активного state
отклоняется без потери ресурсов и накопленного хеша. Cleanup обнуляет все поля и допускает повторы.
Final проверяет capacity до финализации; недостаточный буфер позволяет повторить вызов с сохранённым хешем.
Контракты и обработка ошибок отражены в .h и примере документации; формат подписи прежний.
Регрессия до исправления падает на непустом state после отказа EVP_MD_CTX_new.
Проверены 6 отказов init, 8 отказов final/verify, 4 сочетания повторного init, capacity0/63/64,
повторная очистка/использование и совпадение с независимой one-shot подписью SHA512 сообщения.
Тесты не перехватывают free. Восемь профильных тестов ASAN/UBSan с detect_leaks=1 PASS;
check.sh184/184, proxy9/9, burst1/1, load8/8 PASS. Пример документации компилируется с -Werror.
Логи: /tmp/sc-sign-regression-before.log, /tmp/sc-sign-fix-full-check.log, /tmp/sc-sign-fix-asan-*.log.
[+] **Media: очистка кеша удаляла временный файл активной загрузки** — исправлено 24.10.2026:
file_io хранит общий защищённый mutex реестр владельцев temp по фактическим нормализованным путям.
Владение сохраняется от создания до publish/discard, включая интервал после finish; очистка кеша
проверяет владение и удаляет сироту под тем же mutex. Угадывание суффиксов не используется.
Регрессии проверяют открытый/закрытый temp, барьеры создания/finish и очистку после реального BLOCK_CHUNK.
Полный check.sh: 184/184, proxy9/9, burst1/1, load8/8 PASS; пять профильных тестов ASAN/UBSan PASS,
test_encrypted_attachment дополнительно повторён пять раз без отказов.
Логи: /tmp/media-orphan-full-check.log, /tmp/media-orphan-asan-*.log, /tmp/media-orphan-repeat-*.log.
[+] **ETCP: входящий INIT сохранял устаревший признак обратной достижимости** — исправлено 23.10.2026:
Принятый INIT обновляет RX и remote_keepalive до переиспользования буфера для ответа; flag_up=0 снимает ложный UP.
INIT/INIT_RESPONSE и обычный RX публикуют переходы живости с защитой от close/удаления линка из callback.
При DOWN handshake продолжает ответ/keepalive; исправный второй линк сохраняет UP соединения.
INIT_RESPONSE проверяет длину тела (>=64 байт без timestamp/flag_up) и node_id до изменения состояния.
Раньше проверка общей длины принимала усечённое тело 61..63 байта. Новых таймеров/очередей/полей нет.
test_etcp_init_liveness: 130 отказов без изменения link/conn и передачи владения pkt, 12 допустимых ответов;
оба направления односторонней потери, оба порядка ключей, восстановление без смены эпохи, резервный линк,
close и удаление link из INIT/keepalive callback PASS, также с обычными часами.
Семь профильных тестов ASAN/UBSan с detect_leaks=1 PASS; дополненный тест удаления link повторён отдельно.
BGP route exchange: 100/100 PASS (A<B:48, A>B:52; последние пять с debug). Основной набор: 184/184 PASS.
Логи: /tmp/etcp-init-boundary-before.log, /tmp/etcp-init-final-{liveness,realtime,asan-liveness}.log,
/tmp/etcp-init-fix-asan-test_*.log, /tmp/etcp-init-bgp-stable-*.log, /tmp/etcp-init-fix-full-check.log.
[+] **ETCP/BBR: бюджет очереди в установившемся режиме** — реализовано 23.10.2026 по выбору пользователя:
В PROBE_BW окно ограничено max(4*MSS, BDP + BW*20мс), с насыщением арифметики и учётом ACK/PROBE_UP резервов
внутри общего предела. Ограничение применяется также на fast path и до callback. 0 отключает политику.
STARTUP/DRAIN не ограничены этим бюджетом; это мягкая цель модели, а не гарантия физической очереди <=20мс.
До политики bandwidth давал очередь 47.1–47.9мс >40мс при корректных единицах и сложении резервов;
после политики профиль проходит по скорости и очереди, equal также PASS. Разные RTT и отказы не закрыты.
BBR 18/18, windows 16 сценариев PASS; BBR/windows/session/transport/STCP traffic/NCD ASAN/UBSan
с detect_leaks=1: 6/6 PASS. Полный check.sh: 180/183, FAIL NAT detection/fault и encrypted_attachment;
связь падений с BBR не установлена, отдельная открытая задача сохраняется.
Логи: /tmp/etcp-queue-budget-fastpath.log, /tmp/etcp-queue-budget-windows.log,
/tmp/etcp-queue-limited-asan-results.log, /tmp/etcp-queue-limited-asan-test_*.log,
/tmp/etcp-queue-limited-full-check.log. Политика описана в etcp_bbr.h, etcp_bbr_doc.md и doc/etcp_protocol.txt.
[+] **ETCP/BBR: единое окно DATA и корректное осушение PROBE_RTT** — исправлено 23.10.2026:
BBR использует MSS=normalizer->frag_size, а не wire-MTU/размер STCP-кадра. Начальное окно UDP/STCP равно 4*MSS;
MTU пересчитывается после включения нового линка в список. init_cwnd инициализируется четырьмя фрагментами.
Транспорт применяет положительное окно с верхним ограничением bbr_max_cwnd, не поднимая его до 8192.
INFLIGHT_LIM_MIN=8192 остаётся нижней допустимой границей настройки максимального окна; нулевое окно отклоняется с логом.
PROBE_RTT осушает до max(4*MSS, 0.5*BDP), ждёт >=200мс и раунд ACK, затем восстанавливает предыдущее окно.
Отправленные при осушении DATA помечаются app_limited. Ограничения после потерь больше не уменьшают окно ниже 4*MSS.
BBR 15/15, отправитель 16 сценариев; ETCP session/transport, STCP traffic и NCD PASS.
Те же шесть тестов ASAN/UBSan с detect_leaks=1: 6/6 PASS, ошибок и утечек не обнаружено.
Общий check.sh: 181/183, FAIL test_nat_detection_faults/test_nat_detection (contact_history/UNKNOWN);
test_encrypted_attachment в этом прогоне PASS. NAT-падения и отдельный повтор отмечены следующей открытой задачей.
Логи: /tmp/etcp-probe-test_*.log, /tmp/etcp-probe-asan-results.log, /tmp/etcp-probe-asan-test_*.log;
полный прогон: /tmp/etcp-probe-full-check.log.
исходные нарушения: /tmp/etcp-probe-contract-{bbr,windows}-before.log, /tmp/etcp-probe-lossfloor-before.log.
[+] **ETCP/BBR: байтовые единицы, delivery clock и независимость ACK от локального DATA-pacer** — исправлено 23.10.2026:
Устранено повторное умножение на MSS при расчёте pacing/BDP: доставка 1MB/s давала 570240000B/s при MSS=576,
теперь cruise pacing=990000B/s независимо от MSS. Q16 байт/мкс сохраняет многогигабитные оценки;
выходы pacing/BDP/cwnd и рост inflight_hi насыщаются вместо переполнения.
PROBE_UP растит inflight_hi на MSS за подтверждённое окно; резерв равен 2*MSS, Reno rounds считаются в пакетах.
Счётчики ACK aggregation расширены до 32 бит, подтверждения свыше 64KiB больше не усекаются.
Delivery sample использует снимки delivered/time/send-time пакета и max(send_elapsed, ack_elapsed);
несколько ACK в одном tick больше не создают ложный пик скорости. Суммарный delivered растёт только на новые ACKed байты.
ACK без DATA обходит локальный DATA-cwnd и DATA-pacer и не начисляет их нагрузку: низкая исходящая DATA-скорость
получателя не тормозит обратные подтверждения. Физическая очередь канала остаётся общей для DATA/ACK.
dummynet измеряет максимальное время очереди/сериализации отдельно от заданной propagation delay;
multipath проверяет его предел base_RTT+20ms наряду с полезной утилизацией, FILLER/повторы не засчитываются.
BBR 14/14, window/sender 14 сценариев PASS. ASAN/UBSan с detect_leaks=1: BBR/windows/calibration/session/transport/STCP/NCD 7/7;
после последнего исправления переполнения BBR/windows повторены отдельно. Основная multipath-задача остаётся открытой.
Общий check.sh перед последним исправлением переполнения: 181/183; падения вынесены отдельной задачей ниже.
Логи: /tmp/etcp-recovery-overflow-{before,after,windows}.log, /tmp/etcp-recovery-asan-results.log,
/tmp/etcp-recovery-asan-overflow-{bbr,windows}.log, /tmp/etcp-multipath-recovery-check.log.
[ ] **Разобрать падения NAT detection/fault и encrypted attachment в общем прогоне** — обнаружено 23.10.2026:
check.sh: test_nat_detection_faults завершился assert в run_alias (ожидался inbound OPEN, получен UNKNOWN/contact_history);
отдельный повтор тоже упал, уже в run_case mode=5: mapping DEPENDENT корректен, inbound UNKNOWN вместо ожидаемого.
test_encrypted_attachment достиг phase=3/12001 heartbeat и упал; отдельный повтор завершился phase=10/560 heartbeat PASS.
После исправления окна BBR общий прогон снова 181/183: encrypted_attachment PASS, но FAIL оба NAT-теста.
test_nat_detection_faults упал в run_case mode=1 (UNKNOWN/contact_history); test_nat_detection не дождался профиля
за 18с на socket=udp. Его отдельный повтор тоже упал, уже на socket=second, с той же причиной contact_history/UNKNOWN.
После бюджета очереди полный check.sh: 180/183, повторились оба NAT-падения и encrypted_attachment.
Связь с изменениями BBR не установлена. Разобрать фикстуры/таймеры по логам, не ослаблять ожидания.
Логи: tests/logs/test_nat_detection_faults.log, tests/logs/test_encrypted_attachment.log,
/tmp/etcp-recovery-nat-retry.log, /tmp/etcp-recovery-attachment-retry.log,
/tmp/etcp-probe-full-check.log, /tmp/etcp-probe-nat-retry.log, /tmp/etcp-queue-limited-full-check.log.
[+] **ETCP: единый установщик прямого транспорта через NCD** — исправлено 22.10.2026:
Удалён неиспользуемый рабочим кодом etcp_connect с EARLY/LATE, отдельными линками и таймерами;
три тестовых файла переведены на NCD handles с начала подключения. Сохранены проверки независимых владельцев,
отмены отложенного UP, TIMEOUT, выхода из UTUN с сохранением транспорта и shutdown с pending LEAVE.
Состояние реестра, физический UP, готовность ETCP-сессии и READY группы разделены в контрактах.
Начальный отправитель не создаёт преждевременный UP. Merkle-фикстура ждёт готовности обоих потоков.
[+] **ETCP: индивидуальные окна DATA, ACK и корректный учёт повторов** — исправлено 22.10.2026:
Выбор линка проверяет место для целого DATA, ACK не блокируется DATA-cwnd; burst без места использует FILLER.
WAIT_ACK занимает окно, RTO освобождает его до постановки в WAIT_SEND; поздний ACK не вычитает байты повторно.
BBR получает факт ограничения окном до вычитания ACK; app_limited учитывает подготовленные данные и normalizer.
Один call_soon объединяет пробуждения без таймерной задержки/рекурсии; OOM retry через 10ms, reset/close отменяет token.
Невозможность запланировать отправку закрывает транспорт отложенно, сохраняя вызывающий стек.
Проверяются размер max_cwnd, переполнение суммы окон; ошибки [global] теперь отклоняют конфигурацию.
Новый test_etcp_windows: 12 сценариев настоящих очередей/отправителя, сверка счётчиков и внедрение отказов.
STCP-тест проверяет каждый байт двустороннего 1MiB трафика при окнах 8KiB и независимых тестовых командах.
ASAN/UBSan с detect_leaks=1: windows/session/transport/connect/stcp_traffic/SIGPIPE/NCD, 7/7;
результаты /tmp/etcp-asan-complete-results.log, логи /tmp/etcp-asan-complete-logs/.
Финальный check.sh: 182/182, TCP proxy 9/9, burst/load PASS (210s);
полный лог /tmp/etcp-full-check-complete.log. Ветка fix/etcp-connect-link-windows.
[+] **STCP: запись в закрытый TCP-сокет завершала библиотечный процесс SIGPIPE** — исправлено 22.10.2026:
В общем прогоне STCP traffic завершался сигналом 13 после передачи данных, при teardown;
воспроизведено первым же повтором ASAN. Оба send-пути STCP используют socket_send с MSG_NOSIGNAL
либо SO_NOSIGPIPE; обработчики сигналов процесса не меняются. Ошибка EPIPE обрабатывается штатным путём.
test_stcp_sigpipe проверяет raw/buffered send в дочерних процессах с SIGPIPE=SIG_DFL.
20 повторов STCP traffic под ASAN/UBSan с detect_leaks=1 прошли без сигналов/ошибок/утечек;
логи /tmp/etcp-asan-traffic-fixed-*.log, детерминированная проверка включена в общий Linux-набор.
[ ] **Аудит .h ETCP/STCP: оставшиеся расхождения контрактов** — проверено 22.10.2026:
stcp.h: do_close уже очищает оба sc_stream и планирует free для allocated/free_on_close; комментарий утверждает обратное.
Разделить контракты finish (drain/запрет новых данных/до 1с) и do_close (немедленное закрытие/потеря очередей/callback).
Описать владение внешними rx_queue/tx_queue, преусловия размера frame и callback/reentrancy/lifetime.
stcp_client.h: ping автоматически освобождается после завершения; NULL на ошибке запуска не вызывает ping callback.
RTT пинга — uint16_t в 0.1мс, от отправки STCP handshake, без TCP/SOCKS/REALITY dial; явно описать срок out_cli/handle.
inst также нужен для NTP-времени и профиля REALITY, а не только padding автономного STCP.
stcp_link.h/stcp_server.h: дополнить параметры, возвраты, время жизни callbacks/inst и различие handles
серверного адаптера stcp_server_listen и базового stcp_server_create (одно имя opaque-типа, разные реализации).
etcp.h: ref_count удерживает объект/phase-2, но close уже освобождает normalizer и линки в phase-1;
reset_done не блокирует reinit (в реализации только запись/диагностика); last_rx_id не обновляется при RX.
etcp_connection_ready объявлена без реализации/вызовов; рабочая функция — etcp_conn_ready.
etcp_connections.h проверен частично: у data_len исключены все 3 байта timestamp/flag, не только timestamp;
send_hook уступает SOCKS UDP; etcp_send_tcp_ping при отказе запуска возвращает отрицательный код без callback.
etcp_session.h и etcp_padding.h сверены с реализацией без подтверждённых смысловых расхождений.
Подробности: /tmp/protocol-header-audit.txt. Продолжить протоколы перед routing/BGP; media/file_transfer отложен.
[+] **Android: приём рации через SCO после отпускания PTT** — исправлено 22.10.2026:
Общий MediaAudioController сохраняет разговорный тракт для SCO-only выхода, даже без подписчиков микрофона.
AudioTrack рации снова использует USAGE_VOICE_COMMUNICATION: Android учитывает playback как разговорную активность.
Добавлены логи фактического routedDevice/mode/SCO и аппаратная проверка после закрытия последнего input.
Samsung + C1 PRO: PHONE → Bluetooth → PHONE, 35с приёма без микрофона и холодный запуск SCO без PTT прошли.
На Q8 прошли общий захват/переключения/сохранение playback. JVM 145/145, сборки ядра и APK успешны.
[ ] **Android: просмотр трансляции через SCO без работающей рации** — воспроизведено 22.10.2026:
Media-выход с USAGE_MEDIA после закрытия микрофона теряет SCO: mode=0, sco=false,
хотя общий контроллер продолжает владеть выходом и сам больше не закрывает разговорный тракт.
AudioService учитывает VOICE_COMMUNICATION playback/record для активности MODE_IN_COMMUNICATION.
Повтор: adb -s RZ8R72MG64B shell am instrument -w -e output media com.utun.chat/com.utun.chat.data.MediaAudioProbe.
Радио с USAGE_VOICE_COMMUNICATION проходит ту же проверку, media-вариант её не проходит.
Сохранить media-назначение/громкость обычных выходов; для SCO переключать аппаратный AudioTrack на VOICE_COMMUNICATION.
При пересоздании только аудиовыхода сохранить недописанный PCM, учесть уже записанный буфер и непрерывный played;
видео и его кодеки не перезапускать. Проверить оба направления смены выхода и длительный playback без микрофона.
[+] **media_async: файловые примитивы и точные контракты .h** — исправлено 22.10.2026:
ma_copy_file проверяет существование/идентичность, сохраняет один regular-файл при ./ или hardlink;
ссылки назначения отклоняются, копирование идёт через соседний temp с проверенными записью/close/publish.
Общие примитивы lib/file_io используются также video, сохраняя публикацию видео только в completion.
ma_uuid возвращает ошибку RNG и обнуляет выход; media_index не регистрирует блоки при отказе media/block UUID.
ma_file_size и переменные вызывающего кода — int64_t; проверяются смещения, число блоков и размеры выделений.
Обход передаёт ошибки, пропускает ссылки/reparse points, проверяет длину/глубину; Windows использует wide API.
Контракты .h и service_lifecycle описывают синхронные отказы, лимит 16 до completion и отсутствие отката work при отмене.
Неверные аргументы дают -1, closing/destroy — CANCELLED; повторный вход в destroy диагностируется и отклоняется.
Новый test_media_primitives проверяет сохранность при I/O/fdopen ошибках, RNG/seek без записей блоков в БД,
sparse-размеры на границах 2/4 ГиБ, ошибки обхода, ссылки/глубину и ownership задач с внедрением ошибок.
Все шесть тестов media_primitives/video/media_index/attachment_send/services/dm_worker прошли ASAN/UBSan с detect_leaks=1.
Общий check.sh: 176/176 и proxy/burst/load. Windows runtime/сборка не проверялись: кросс-компилятора в окружении нет.
Логи: /tmp/media-primitives-full-check-final.log, /tmp/media-primitives-final.log, /tmp/media-asan-test_*.log.
[+] **media_index: пути без обрезания и единая media_base** — исправлено 22.10.2026:
Общие location/path проверяют границу базы, компоненты и пределы UTF-8 путей (1024/512 с NUL).
Путь в 550 байт полностью сохраняется, читается и удаляется. Проверки предшествуют файловой подготовке.
Выдача использует UTF-8 file_io; загрузчик проверяет общую базу, stop/start сохраняет выбранную базу БД.
Писатели, backfill, выдача и удаление используют один формат; контракты находятся в media_index.h.
[+] **media_index_commit: атомарная регистрация и проверяемые повторы** — исправлено 22.10.2026:
Единый commit регистрирует весь авторский/скачанный файл через SAVEPOINT; key=NULL означает скачанный файл.
Проверяются геометрия/массивы, bind/step/finalize и управление транзакцией.
Ошибка второго блока откатывает оба пути регистрации; обычная ошибка сохраняет внешнюю транзакцию.
SQLite может сама откатить её целиком — это обнаруживается и диагностируется.
Совпадающий повтор сохраняет node_sign и timestamp; противоречащие метаданные дают ошибку без замены старых.
Файловые изменения worker не входят в SQL-атомарность.
[+] **Удаление канала: дождаться исходящих медиа и учесть незавершённые файлы** — исправлено 22.10.2026:
Канал ждёт свои video/register completions; новые исходящие задачи отклоняются до завершения удаления.
Последний completion продолжает удаление. Подготовленные ожидающие отправки не становятся новыми сообщениями.
Контекст остаётся у владельца во время синхронного MSG_SUBMITTED; повторный вход обработчика проверен.
До worker путь учитывается в media_preparations; успешный commit снимает запись в той же транзакции.
После ошибки хеширования/отмены опубликованная копия остаётся известна для очистки канала.
Проверены две ожидающие задачи, повтор удаления, создание того же channel_id и удаление из обработчика события.
[+] **media_index_delete_channel: ошибки и повторяемая частичная очистка** — исправлено 22.10.2026:
Полный снимок и проверка путей предшествуют удалению; внешняя транзакция отклоняется.
Общие файлы других каналов сохраняются; ENOENT считается успехом.
SQL/файловые ошибки возвращают -1, записи неочищенных путей сохраняются для повтора.
Проверен отказ DELETE после unlink: повтор завершает очистку без восстановления файла.
Очистка включает media_preparations; ошибки очистки и удаление метаданных канала сообщаются раздельно.
[+] **media_index_init: ошибки всех индексов доходят до запуска чата** — исправлено 22.10.2026:
Проверяется каждый CREATE INDEX. Ошибка прерывает chat_core_init с освобождением собственных ресурсов;
общая БД ядра сохраняется. Проверены запрет каждого индекса и успешный повтор старта после снятия запрета.
Дополнительно исправлены дубликат start (отказ без callback) и доступ к dl после синхронного завершения QUERY.
Тесты используют реальную схему БД и проверяют новый контракт владения.
Итог: check.sh — 177/177, proxy/burst/load PASS, 159с.
Девять тестов index_regressions/index/delivery_download/download_timeout/services/delivery_full/delivery_chat/attachment_send/video
прошли ASAN/UBSan с detect_leaks=1. Windows runtime/сборка не проверялись: кросс-компилятора нет.
Логи: /tmp/media-index-full-check-final.log, /tmp/media-index-final-test_media_index_regressions.log,
/tmp/media-index-asan-test_*.log. Новый тест: tests/test_media_index_regressions.c.
[+] **media_delivery QUERY/QUERY_RESP: проверять длины до чтения записей** — исправлено 22.10.2026:
Проверяются точный размер, счётчики и все записи до SQL/изменений состояния. Ответ связан с media_id,
UUID попытки, группой и end-to-end отправителем router; дубликаты, истёкшие и старые ответы игнорируются.
test_media_query защищает точную границу payload под ASAN и проверяет все усечения, чужие ответы,
повреждение последней записи без изменения первой, отмену и повторный start того же media_id.
[+] **media_delivery QUERY: устранить потерю блоков и держателей из-за разных лимитов** — исправлено 22.10.2026:
Одна пачка до 32 block_id — один полный ответ, до четырёх кандидатов на каждый блок (максимум 1604 байта).
Нулевой результат и ошибка БД различаются. Неполная пачка не принимается; держатели других пачек и текущие CM сохраняются.
QUERY имеет отдельный дедлайн и курсор источников каждого блока; чанки не продлевают поиск остальных блоков.
Источники своей CHAT-группы, автор последний; HAVE_BLOCK не зависит от курсора поиска.
Peer-слоты переиспользуются после освобождения; SLOT/SEARCH/RETRY различаются, OVERLOADED соблюдает retry_after.
Отменяется поздний CM UP, события обрабатываются после open. Устранено двойное освобождение при отказе router.
Проверены 65 блоков/260 записей, старт 4100 блоков, сборка с 13 держателями при 10 слотах,
настоящий watchdog при входящих чанках, SQL-ошибка второго блока, RNG/send failures и отсутствие повторного done_cb.
Сетевой integration проверяет содержимое QUERY_RESP вместо безусловного smoke PASS.
Восемь тестов query/delivery_download/download_timeout/delivery_sql/delivery_integration/delivery_chat/delivery_full/services
прошли ASAN/UBSan с detect_leaks=1. Итоговый check.sh: 178/178, proxy/burst/load PASS, 172с.
Логи: /tmp/media-query-regressions.log, /tmp/media-query-full-check-final.log, /tmp/media-query-asan-final-test_*.log.
Windows runtime/сборка не проверялись: кросс-компилятора нет. Контракты — в соответствующих .h.
[+] **media_delivery QUERY_RESP: устранить неверное поле chunk** — исправлено 22.10.2026:
Неиспользуемые chunk/rtt удалены из нового формата QUERY_RESP. Блок однозначно задаётся block_id,
а его номер загрузчик берёт из сохранённых метаданных; позиция в выборочной пачке не считается номером блока.
[+] **media_delivery CHUNK: прогресс только после проверенного приёма и записи** — исправлено 22.10.2026:
Реальные router callbacks/SQLite, корректные подпись блока и ключ автора. CHUNK с data_len=1 без payload
оставляет файл пустым, но устанавливает bytes_received=1, RECV и обновляет last_progress_tb.
То же при внедрённом fwrite=0/ENOSPC. Текущий unit-тест допускает 456 байт при expected_size=100.
Проверять полный payload, геометрию/непрерывность, текущего держателя и попытку до файловых операций;
не считать дубли и неудачную запись прогрессом. Нужна привязка пакетов к отдельной попытке передачи блока.
Стенд /tmp/media-block-audit.c; логи /tmp/media-block-audit-truncated.log, /tmp/media-block-audit-write.log.
Реализованы строгие размеры/непрерывность, tuple группы/держателя/блока/UUID и отдельный temp каждой попытки.
Прогресс публикуется после проверенного close. Регрессии: tests/test_media_blocks.c, включая реальные I/O-сбои.
[+] **media_delivery DONE: проверка ключа и конечный результат ошибки хеша** — исправлено 22.10.2026:
Для настоящей Ed25519 пары с pubkey[0]=0 корректно подписанный блок отвергается: наличие ключа определяется
первым байтом. При отсутствии ключа проверка заменяется сравнением wire block_sig с метаданными.
Отдельно: при верной подписи блока и несовпадающем хеше файла state=DONE, validated=0/1, active=1;
три проверки watchdog возвращают 0, done не вызывается. Итоговая ошибка должна завершать загрузку,
а публикация назначения и регистрация допустимы только после проверенных чтения/записи/close/SHA256.
Обязателен ключ ровно 32 байта; проверяется подпись метаданных по фактическим байтам и ожидаемый размер.
Сборка идёт в собственный temp, SHA-256 проверяется всегда, затем publish и регистрация SQLite.
Ошибка до publish сохраняет старое назначение; ошибка SQLite сохраняет подготовленный опубликованный файл.
Регрессии с настоящим pubkey[0]=00, неверной подписью/хешем, сбоями I/O/rename/SQLite: tests/test_media_blocks.c.
[+] **media_delivery progress_cb: отмена освобождает dl внутри обработчика DONE** — исправлено 22.10.2026:
progress_cb вызывает media_download_cancel, md_dl_finish освобождает dl; media_download_handle_done затем
вызывает md_dl_send_have_block с освобождённым указателем. ASAN heap-use-after-free, чтение 8 байт.
Обработчик явно удерживает контекст. Terminal снимает загрузку с индекса и отменяет её timers/CM/waiters;
освобождение и done выполняются после выхода последнего удерживающего обработчика, ровно один раз.
После progress проверяется terminal: отменённая загрузка не продолжает финализацию. Один перенос callback
в конец функции/отложенный финализатор не заменяет владение. Relay-получатели отдельно удерживают файл блока.
Проверить также отмену с повторным start того же media_id, отмену на последнем progress и поздние пакеты.
Лог /tmp/media-block-audit-cancel-asan.log; стенд вне репозитория /tmp/media-block-audit.c.
Реализовано явное удержание контекста обработчиком; отдельные ссылки producer/выдач удерживают файл блока.
Единый TX pump готовых/растущих источников соблюдает backpressure для DATA и DONE; remote CANCEL привязан к UUID.
Проверены отмена/повторный start из последнего progress и старые relay-читатели при успехе/отмене новой загрузки.
Полный ./check.sh: 179/179, proxy/burst/load PASS (/tmp/media-block-full-check-final.log).
Десять целевых наборов ASAN+UBSan с detect_leaks=1: PASS (/tmp/media-block-asan-test_*.log).
Windows runtime/сборка не проверялись. Синхронные проверка и сборка блоков остаются в uasync.
[+] **media_delivery: исправить репликацию суперузлов** — исправлено 22.10.2026. Воспроизведения прежней реализации:
SUPER_REPL с num_entries=1 и одним заголовком: ASAN use-after-poison при чтении id в media_delivery.c:622.
ACK от известного пира в другой router-группе без ожидающей пачки: inflight 0→255, tx_cursor=77, запускается timer.
HELLO двух пустых узлов: после 20 доставок остаётся следующий HELLO, обмен сам не завершается.
Для 9 записей sender ставит 4 одинаковые пачки первых 4 записей. По таймауту 20000→40000 при inflight=4
отправляет 0 повторов, хотя timer снова вооружён. На потерю ACK эта схема не восстанавливается.
При id=4294967297 первая запись остаётся 64-битной, но seq максимума пачки усечён до 4.
Ошибка INSERT второй строки оставляет первую применённой, сохраняет RX cursor=2 и отправляет ACK=2: теряются данные.
Группа из тела HELLO переопределяет router-группу (111→333); ответ включает записи групп 111 и 222.
Старая пачка уменьшает сохранённый RX cursor 2→1. После supernode stop/start чужие записи удалены,
но cursor=2 сохранён и рекламируется в HELLO. Принятая запись не будит выдачу третьему готовому супер-пиру.
Уточнение прежней записи: смешения направлений курсоров в БД нет — md_ss_set вызывается только при приёме REPL;
ACK обновляет peer_last_recv_id в памяти. Исправлять нужно подтверждение ожидающей пачки и жизненный цикл курсоров.
Стенд вне репозитория /tmp/media-super-audit.c; обычные логи /tmp/media-super-audit-{ack,hello,pages,timeout,wide,sql,group,purge,replay,forward}.log.
ASAN: /tmp/media-super-audit-truncated-asan.log, exit=1 ожидаем из-за подтверждённой ошибки, не успешный regression.
Реализованы receiver-driven SYNC_REQ/SYNC_RESP и CHANGED вместо HELLO/ACK/inflight: одна страница с UUID на (group,peer).
Группа/источник только из router, актуальные подписанные роли обоих участников; CM владеет прямым/непрямым путём.
Страницы до 32 строк, little endian, 64-битные ID; high-water из sqlite_sequence, сохранённая эпоха каталога.
Весь RX packet и курсор фиксируются собственной транзакцией после полной проверки, включая ошибки commit/rollback SQLite.
Идемпотентный максимум (status,timestamp) выдаёт новый локальный ID только при изменении; собственные блоки сеются из media_files.
Таймаут повторяет прежний запрос, CHANGED/dirty и периодическая сверка не теряют обновление около последней страницы.
Репликация соблюдает backpressure; ограниченный слот ответа и watchdog восстанавливаются после reset router send_q.
Потеря роли/удаление канала очищают строки вместе с RX-курсорами только своей группы; обычный stop сохраняет БД.
test_media_replication: 10000 строк, id>2^32, усечения/дубли/чужие ответы, SQL read/write/commit faults,
потеря запросов/ответов/CHANGED, DOWN/UP, новая эпоха, две группы, смена роли, три узла, заполнение/reset очередей.
SQL-тест использует настоящую схему; сетевые full/chat/integration — CHAT-группы и подписанные роли member_sync.
Финальный ./check.sh: 180/180, proxy 9/9, burst/load PASS (/tmp/media-repl-full-check-final.log).
11 целевых наборов ASAN+UBSan с detect_leaks=1: PASS (/tmp/media-repl-asan-final-test_*.log).
Контракты обновлены в media_delivery.h/media_delivery_proto.h, sync.md и doc/service_lifecycle.md. Windows не проверялся.
[+] **test_stream_service: убрать зависимость bootstrap-проверки от гонки TCP-handshake** — исправлено 22.10.2026:
Падение на строке 409: REINIT=0 при probes=1/subflows=1/failures=0 и доставленной первой пачке.
Встречный TCP-линк успевает сообщить peer_reset_id до ETCP CONFIRM: начальная эпоха уже известна,
поэтому etcp_session_receive не вызывает REINIT. При другом порядке событий REINIT=1 и тест проходит.
Холодная подписка проверяет одну пробу/выдачу и доставку без ошибок, число начальных REINIT диагностическое.
Отдельная автоматическая подписка удерживает INFO_RESP, выполняет настоящий REINIT и продолжает ту же пробу.
Проверяются новые epoch/nonce, отсутствие лишних проб/CLOSED и пустой локальный буфер перед получением DATA.
Ручные проверки старых ответов и исходного deadline сохранены; ожидания привязаны к поколениям и событиям.
Добавлен --bootstrap-only: 8 повторов с ускоренными и 2 с обычными часами прошли.
Полный test_stream_service прошёл ASAN/UBSan с detect_leaks=1; check.sh — 175/175 и proxy/burst/load.
Изолированная потеря pending-пробы вызвала ожидаемый отказ новой автоматической проверки; мутация убрана.
Логи: /tmp/stream-service-original-failure.log, /tmp/stream-bootstrap-repeat-*.log,
/tmp/stream-service-asan-final.log, /tmp/stream-bootstrap-mutant.log, /tmp/stream-bootstrap-full-check-final.log.
[+] **video: сохранность файлов, JPEG и подготовка MP4** — исправлено 22.10.2026:
Одинаковые файлы/алиасы и недопустимые пути отклоняются без изменения исходника.
JPEG и MP4 пишутся в эксклюзивный временный файл; запись, drain, trailer и close проверяются.
Только успешный completion публикует результат; отмена до callback сохраняет старое назначение.
Превью получает задержанные B-кадры. MOV/H.264/AAC проходит remux в MP4; остальные профили
перекодируются с audio FIFO, сохранением временного смещения и хвоста ресемплера.
Атомарный global slot удерживается до completion, ошибки имеют точную фазу и контекст в логе.
Контракты описаны в src/video/video.h и doc/service_lifecycle.md.
test_video проверяет реальные кадры/сигнал, 1080p→720p, PCM/MP3/AAC, resample 12345→12000 Гц,
файловые ошибки, отмену, повтор из callback и конкурентные start; test_video_no_ffmpeg — стабы.
Оба теста, test_services и test_attachment_send прошли ASAN/UBSan с detect_leaks=1.
Отдельная конфигурация всего ядра/тестов без FFmpeg собрана, оба video-теста прошли.
Логи: /tmp/video-asan-final.log, /tmp/video-services-asan.log, /tmp/video-attachment-send-asan.log,
/tmp/video-without-ffmpeg-build-final.log; общий прогон: /tmp/video-full-check-final.log.
[+] **tcp_io: владение connect-таймером и повторный FIN** — 21.10.2026:
Замена таймаута сохраняет старый таймер при ошибке создания нового; <=0 отменяет таймер.
Возвращаемые ошибки и диапазон INT_MAX/10 проверяются API и TCP-прокси; connect/error/destroy отменяют таймер.
FIN различает fin_pending и fin_local, ошибка shutdown идёт в on_error без ложного on_fin_sent.
Уточнены отложенное освобождение, синхронные callbacks очередей и платформенный выбор IPv6/маршрута.
Проверено: 15 сценариев test_tcp_io, test_tcp_io_flush, test_proxy_regressions и test_socks_http_proxy
(16 загрузок с побайтной сверкой). test_tcp_io и flush также прошли ASAN с detect_leaks=1 без ошибок.
Логи: /tmp/tcp-io-fix-unit.log, /tmp/tcp-io-fix-asan-unit.log, /tmp/tcp-io-fix-asan-flush.log,
/tmp/tcp-io-fix-proxy-regressions.log, /tmp/tcp-io-fix-socks-http.log.
[ ] **Desktop publisher: проверить восстановление после недопустимого FPS в Xvfb-тесте** — 20.10.2026:
Широкий screenAndWindowCapture прошёл закрытие управления с active=1 и продолжением публикации,
затем не получил видео revision>=3 после запроса fps=61 и возврата engine.config().video.fps к 15.
Перед этим применены VP9/rotation=90; worker продолжает кодировать generation=4.
Причина и связь с UI-изменениями не установлены; отдельная проверка закрытия управления PASS.
Лог /tmp/desktop-stream-controls-xvfb.log, тест tools/chatgui/tests/test_stream_publish.cpp.
[ ] **Проверить одновременную собственную и удалённую трансляции в multiview** — 20.10.2026:
Собственный preview и удалённые readers по отдельности проверены; их совместный живой сценарий в одном канале не проверялся.
На desktop/Android проверить переход в управление своей публикацией и возврат, fullscreen/Back/Esc,
сохранение удалённых readers и общего звука, отсутствие местного звука и остановку собственной публикации.
[ ] **Multiview desktop: terminal CLOSED оставляет аудиовход и timer** — проверка 20.10.2026:
fail(reason, false) ставит complete=true/failed=false, но tick снимает audio только по ended/failed.
После STREAM_CLOSE_GROUP воспроизведено complete=1, failed=0, attached=1, timer=1.
При valid=0 каталог сохраняет плитки, поэтому состояние живёт до ручного закрытия окна.
Отделить завершение от разрешения retry и освобождать output при любом terminal CLOSED.
Временный regression-проверочный стенд и лог: /tmp/multiview-review/test_stream_view.cpp,
/tmp/multiview-review/lifecycle.log. Исходники реализации при проверке не менялись.
[ ] **Проверить ожидание запросов ключей в test_encrypted_attachment** — 21.10.2026:
До rebase исправлений control_server: check.sh вне sandbox 165/166; отдельный повтор также
phase=5/heartbeats=12001, ожидание двух pending incoming-запросов после перезапуска автора.
Логи: /tmp/control-server-full-check.log, /tmp/control-server-attachment-recheck.log;
артефакты общего прогона: /tmp/utun_encrypted_attachment_6JMO0c. Код вложений в этой работе не менялся.
[ ] **Разобрать нестабильную загрузку storage-узлов в test_media_delivery_chat** — 19.10.2026:
В том же общем прогоне storage st1/st2 не собрали файл, n2 исчерпал попытки блока 0 (15/19 внутренних проверок).
Отдельный повтор прошёл 19/19. Причина не установлена; сравнить маршруты и готовность storage под общей нагрузкой.
Логи: tests/logs/test_media_delivery_chat.log, /tmp/phone-busy-media-recheck.log.
Повторилось 21.10 при аудите ID сервисов: 164/165, те же 15/19 внутренних проверок.
Отдельный запуск и 16 параллельных повторов прошли. Причина остаётся открытой;
тест теперь сохраняет подробный media/router лог и БД при отказе в /tmp/utun_mdc_*/.
Логи /tmp/service-ids-full-check.log, /tmp/service-ids-media-chat-debug.log, /tmp/service-ids-media-chat-stress/.
[ ] **Разобрать нестабильное ожидание ACK в test_local_transport** — 19.10.2026:
Общий check.sh: 161/162, chat_adapter упал на строке 173 при ожидании LOCAL_DELIVERY_FORBIDDEN для request=508.
В логе получатель дважды отклонил приглашение и отправил ACK; отправитель до проверки получил только late ACK request=507.
Отдельный повтор прошёл. Причина не установлена; проверить доставку UDP и шаги ускоренных часов под параллельной нагрузкой.
Логи: /tmp/config-name-local-transport-failure.log, /tmp/config-name-local-transport-retry.log.
Ещё один общий прогон 19.10: 161/162, chat_adapter упал на строке 145 при ожидании доставки request=501.
Получатель принял приглашение и дважды отправил ACK; до проверки в логе отправителя нет приёма этого ACK. Отдельный повтор прошёл.
Логи: /tmp/utun-stream-preview-local-transport-failure.log, /tmp/utun-stream-preview-local-transport-retry.log.
[ ] **Разобрать нестабильную проверку аудиопауз desktop-трансляции** — 17–20.10.2026:
test_stream_view::realH264RenderingAndClose нестабильно получает silenceGaps=3–4 вместо 2.
Зафиксированы pcm_short с got=96/480 и 288/480; видео продолжало выводиться.
Сбой повторялся с общей нагрузкой и после её завершения; изолированные повторы проходили.
Проверить актуальные PCM workers/часы под нагрузкой; единичный PASS не подтверждает устранение.
Логи: /tmp/stream-zoom-full-view-audio-gap.log, /tmp/stream-zoom-full-view.log,
/tmp/multiview-review/desktop-review.log, /tmp/multiview-review/audio-original.log.
[ ] **Устранить коллизию порта при перезапуске узла в test_dm_e2e** — 16.10.2026:
Общий check.sh при проверке Windows-курсора: 155/156, test_dm_e2e упал в phase=9 на создании B2.
Повторный bind 127.0.0.1:33843 вернул Address already in use; отдельный повтор теста проходит.
Проверить владение/резервирование порта между остановкой B и созданием B2 при параллельном прогоне.
Логи: /tmp/win-stream-cursor-dm-e2e-failure.log, /tmp/win-stream-cursor-dm-e2e-retry.log.
[ ] **Проверить Windows-трансляцию на исходной машине после исправлений playback/stop** — 16.10.2026:
Цикл ожидания позднего аудио исправлен; join и закрытие микрофона выполняются асинхронно,
QVideoFrame::toImage() вынесен в долгоживущий image worker. Linux/Xvfb-регрессии проходят.
Осталась свежая трансляция Windows → Samsung и stop после пересборки Windows: исходный Qt backend ещё не проверен.
На RYS HFR USB2.0 Camera проверить сообщение об отсутствии PCM, отзывчивость при завершении WASAPI,
отказ/согласие на CPU после отказа NVENC. Причина frames=0 микрофона пока не установлена;
подробный AudioDiag реализован только для Linux. libx265 3440×1440 на исходном CPU выдаёт около 2–3fps.
Логи: 4.log, /tmp/stream-viewer-new-run.log, /tmp/late-audio-test.log,
/tmp/stream-persistent-image-xvfb.log, /tmp/stream-audio-stop-full-check.log.
[ ] **Проверить появление значка свежей трансляции на SM после сна через colo2** — 16.10.2026:
Пробуждение и перезапуск SM во время сна уже проверены на обновлённом colo2:
новый линк снимает прежний таймер, Merkle завершается без ожидания полного сна 120с.
Осталась визуальная проверка значка новой desktop-трансляции после возврата SM в foreground;
в прежнем прогоне активных источников не было (count=0).
Логи: /tmp/stream-wake-live-{sm,colo2}-*.log.
[ ] **Проверить остаточные паузы видеозвонка на актуальном playout** — 15.10.2026:
После исправления TX flush живой звонок 12694689407894286842 дал SM 29.87fps без drops,
desktop 29.91fps/3 late drops за последние 20с; отдельные промежутки SM доходили до 63.8мс.
В старой трассе краткие invalid audio clock сбрасывали якорь, точная причина invalid не была видна.
После переработки playout/ACK и чтения аудиочасов 19–20.10 повторить оба направления на устройствах,
сопоставить gap_pts_us/gap_rx_us/gap_dispatch_us и причины недоступности часов, если паузы сохраняются.
Текущий контракт и результаты поздних проверок: doc/call_video.txt.
[ ] **Доступность разговорного динамика в UI звонка** — при подключённой проводной гарнитуре Samsung убирает
EARPIECE из Telecom endpoints, но кнопка остаётся доступной по наличию физического встроенного устройства.
Нажатие создаёт отложенный запрос, который исполняется после отключения гарнитуры. Согласовать UI с доступными endpoints.
Также выбор проводной гарнитуры в CallScreen сейчас запрашивает BLUETOOTH_CONNECT при отсутствии этого разрешения;
проверку разрешения нужно привязать к выбранному типу гарнитуры.
[ ] **Ручная приёмка видеозвонков Android и ChatGUI после последних исправлений** — 14–20.10.2026:
Сигналинг, CAPS, реальные codecs/камера/render и A/V подключены; живые звонки уже проводились.
Осталась полная проверка после изменений Full HD/50–60fps, HEVC High tier, Surface ACK и фоновых codec probes:
Windows ↔ Samsung/Q8, камеры и оба направления, первый и повторный звонок, смена камеры/разрешения/кодека/битрейта,
A/V, завершение и освобождение Surface/видеопорта. Аппаратные probes не заменяют эту проверку.
Архитектура, контракты и выполненные проверки: doc/call_video.txt, doc/chat_call_arch.md.
[ ] **Тесты: нестабильное завершение test_stcp_traffic** — 14.10.2026:
В полном прогоне после NCD CLOSE server получил 1029466/1048576 bytes (done=0), client — 1048576 (done=1).
Упала проверка test_stcp_traffic.c:216; отдельный повтор прошёл. Причина не установлена, сетевой код в задаче zoom не менялся.
Проверить завершение двусторонней передачи относительно закрытия handle; первичный лог сохранён: /tmp/stream-zoom-stcp-first.log.
[ ] **Тесты: диагностировать нестабильную проверку TX snapshot в test_android_stream** — 14.10.2026:
В полном прогоне один раз упала строка 177: snapshot успешен и 0 < video_bytes < 500000 после test_clock_run_for(ua, 1000).
Сам publisher обновляет snapshot таймером 1000; текущий CHECK не выводит result/sample_us/video_bytes.
Отдельный повтор и следующий полный прогон прошли. Добавить значения и проверить ожидание конкретного снимка;
причина не установлена, Kotlin-аудиовывод этот C-тест не вызывает.
Логи прогонов: /tmp/stream-audio-check-host.log, /tmp/stream-audio-native-retry.log, /tmp/stream-audio-check-final.log.
[ ] **Общий AGC: клиппинг после резкого роста сигнала при max_gain=45dB** —
на SM при проверке CAMCORDER с настройками рации rise=6/release=30 наблюдались
4581 обрезанный отсчёт за интервал около 1s. Это общий алгоритм radio/stream,
плавное снижение gain может не успеть за скачком входа. Проверить ограничение пиков
без щелчков и ухудшения адаптации. Лог: `/tmp/stream-camcorder-sm-live.log`.
[ ] **Desktop: выход после SIGTERM оставляет GUI после остановки ядра** — 13.10.2026:
при обновлении live-плеера старый vibechat завершил ядро/worker, но процесс остался
в QCoreApplication::exec с работающим audio output. В логах также DM SELECT после
уничтожения SQLite (`bad parameter or other API misuse`). Проверить отмену Quit
в MainWindow::closeEvent (всегда ignore), порядок уведомлений и отключение GUI от БД.
Снимок: `/tmp/stream-desktop-shutdown-backtrace.log`, лог vibechat 13:04:09.
[ ] **Стриминг SM → colo2 → desktop: восстановление частично остановившихся субпотоков** —
13.10.2026: помощь подключилась за ~8 с, все 8 субпотоков через colo2 приняты с общего индекса 5844.
Позже шесть субпотоков перестали продвигаться около seq=111625; четыре restart не восстановили
просмотр, desktop завершился по таймауту в 11:10:48 (colo2: 12:10:48). На SM публикация продолжилась,
colo2 снял свою подписку через 3 с. В том же интервале сервер видит RX timeout/переподключения TCP к SM;
первопричина остановки выдач пока не установлена: stream на colo2 наследовал error, детальных логов не было.
Повторить с заранее включённой диагностикой stream/etcp на SM и colo2, проверить upstream handles,
аренды, позиции и повторное согласование. Логи: `/tmp/stream-live-gui-final.log`,
`/tmp/stream-live-colo2-final.log`, `/tmp/stream-live-sm-final.log`.
Повтор 13.10 со stereo: до 15:20 desktop показывал ~30fps, затем video/audio очереди опустели;
decodeFps упал до 7–18 и viewer завершился по таймауту в 15:21:13 после пяти restart.
SM при этом сохранял ~30fps без dropped. Новые логи: `/tmp/stream-stereo-desktop-delivery-stall.log`,
`/tmp/stream-stereo-colo2-delivery-stall.log`, `/tmp/stream-stereo-sm-delivery-stall.log`.
[ ] **Android: отмена асинхронного запуска до готовности ядра** — при проверке
мониторинга 12.10.2026 обнаружено: `NativeLib.stop()` пропускает native stop,
если `isReady()==false`, а `instance_lite_stop()` возвращается при `g_inst==NULL`.
Поэтому запрос stop до завершения конструктора может не отменить запускаемый
поток. Согласовать отмену с состоянием потока и единым сигналом завершения;
проверить stop до SERVICE_STARTED, отказ конструктора и последующий start.
[ ] **Android: повторные отказы persisted node snapshots** — замечено 12.10.2026
на устройстве D2AR10P3600820 при проверке live media. В сохранённой БД ряд узлов
регулярно получает `invalid persisted snapshot` в `topo_node_sqlite_snapshot_load`,
затем NCD сообщает `node not found`; повторные попытки засоряют лог.
Лог: `/tmp/stream-av-device-initial.log`. Причина пока не установлена: текущий
текст объединяет отказ deserialize, несовпадение ID и отказ verify. Разделить
диагностику этих ветвей и проверить повторные попытки загрузки отвергнутого снимка.
Существующую БД устройства при проверке стриминга не очищали.
[ ] **STUN IPv6** — отдельный этап: IPv6 endpoint/config/DNS и XOR-MAPPED,
scope/netif, тесты IPv6/NAT66. IPv4-оценки не переносить на IPv6 или TCP.
[ ] **check.sh: изоляция сетевых integration-тестов** — после основного набора
автоматически запускает check-proxy/burst/load при доступном sudo. Скрипты
меняют rp_filter, таблицу 100 и iptables главного namespace; cleanup выставляет
rp_filter=1 вместо сохранённого значения. Перенести тесты в отдельный namespace
и проверять неизменность родительской сети, как в tests/stream_netns.
[ ] **Стриминг: оставшиеся длительные и масштабные сетевые проверки** — 10–12.10.2026:
Реализация ядра, публикации, приёма и автоматического выбора поставщиков завершена; контракт — doc/stream.txt.
Есть 16-процессный namespace-стенд с NAT/reverse и выбором UDP/TCP: tests/stream_netns/README.md.
Осталось подтвердить покрытие длительной нагрузки, смены адресов/мультилинка, одновременной перестройки
нескольких ретрансляторов, нескольких автоматических источников/групп и mobile standby на устройстве
(doc/stream.txt, раздел 16). Сохранённые проверки отдельных этапов не закрывают всю матрицу.
[ ] **Member files: исчерпание кеша результатов массовых изменений** — 10.10.2026:
Воспроизведено test_member_files_browser::recursiveDeletion(server-busy): из 76 объектов удаляются 64,
следующая команда получает MF_BUSY, остаются 12. storage.c: STORE_RECEIPTS=64, RECEIPT_TB=1200000 (120 секунд).
Лимит общий для успешных COMMIT/MKDIR/RENAME/REMOVE; затрагивает также рекурсивное копирование.
Нужен отдельный этап управления временем жизни receipts с сохранением идемпотентности повторных запросов;
простое вытеснение старых записей нарушает гарантию повторного ответа. После исправления изменить регрессию
на успешное удаление более 64 объектов и проверить повторные запросы/потерю ответа.
[ ] **Desktop: провалы вероятности VAD при компрессии** — диагностика 09.10.2026:
Воспроизведение пользователя: длительная гласная, затем временная нечувствительность; ближе к концу лога несколько повторов.
Новый лог 14:32:14–14:34:38: на 14:34:07–18 и 14:34:20–38 все 556 окон с RMS > -30dBFS имеют p < 0.5;
PCM непрерывен, errors=0, RX-gate не мешает, clips=5 не растёт. Сигнал не исчезает, порог остаётся 0.5.
--probe-stream: C-обёртка + реальный AGC + две Silero на одинаковом PCM, отличие — один reset состояния второй модели.
Синтезированный flite ah растянут до 24.576с: без AGC/клиппинга p первой секунды следующей речи 0.027 против 0.674 после reset.
При AGC 1dB (clips=0) — 0.012 против 0.662. Влияние истории модели подтверждено, провал на десятки секунд целиком не воспроизведён.
Лог стенда: /tmp/vad-vowel-context.log; запуск: test_silero_vad lib/silero_vad.onnx --probe-stream /tmp/vad-vowel-context48.pcm 993.
Вход стенда: mono s16le/48кГц, reset_window — индекс окна 512/16кГц; режим проверяет без AGC, 1dB и 15dB.
Автоматические сбросы в рабочем VAD не добавлялись; test_silero_vad PASS.
Уточнение: низкая вероятность держится десятки секунд, затем распознавание восстанавливается; краткий клиппинг это не объясняет.
Локальный лог 14:00:10–14:00:58: 15 сбросов AEC при пропусках референса, 16 clipped samples в VAD; vad DEBUG не был включён.
Добавлены секундные уровни raw/clean/reference и причина bypass/reset AEC; отдельное предупреждение о клиппинге VAD.
Секундный отчёт захвата работает также без PCM: возраст кадра, очередь, максимальная задержка и предупреждение о простое.
TRACE каждого окна содержит DC, число нулей и gain вместе с вероятностью/уровнем/паузой между окнами; видны сбросы и RX-gate.
В локальном build/vibechat.cfg включены vad=trace, aec/radio/call=debug, general=info; после диагностики вернуть обычные уровни.
Стенд --probe повторяет одинаковый PCM без reset: AGC 15/30dB после паузы обрезает пики (468/5625 samples на чистой записи).
Полного провала всей фразы почти до нуля стенд не воспроизвёл; нужен повтор живой речи с новыми логами для связи с AEC/AGC.
Алгоритмы обработки сигнала пока не менялись; исправление причины выбирается по результатам повтора.
[ ] **Linux headless CMake: актуализировать зависимости общего ядра** — 07.10.2026:
чистая сборка tools/chatgui-android/build_headless останавливается на lib/speex_aec.c:
speex/speex_echo.h не найден. utun_setup_sources включает этот исходник, но UTUN_INCLUDE_DIRS не содержит
lib/speexdsp/include. Проверить полный состав Speex/voice зависимостей отдельного каркаса;
основная Autotools C-сборка и Android NDK-сборка проходят.
[ ] **Завершить проверку реакций и голосов на SM** — 07.10.2026:
Исправления подписей корней и их сохранения реализованы, проверены тестами; local и копия SM проверены.
Последующие APK уже устанавливались на SM, поэтому прежнее ожидание подключения ADB неактуально.
Осталась явная проверка реакции/голоса на устройстве с текущей БД и сборкой.
Разовый скрипт и резервная копия подписей: /home/user/utun2-backups/root-certificates-20261007.
[ ] **rlottie: разбор предупреждений GCC 15 в импортированных исходниках** — 05.10.2026:
при сборке `tools/chatgui/rlottie_src` GCC сообщает `-Wuninitialized` в
`VArenaAlloc::reset()`: fFirstBlock/fFirstSize/fFirstHeapAllocationSize читаются
после явного вызова деструктора. Проверить сохранение значений до разрушения
и повторный reset с реальными выделениями. Также проверить `-Wfree-nonheap-object`
в `vcow_ptr`: статическая модель default_s сохраняет базовую ссылку.
Лог: `/tmp/utun-rlottie-vendor-build.log`. Сборка библиотеки и подключение её
экспортированного CMake-пакета к ChatGUI на Linux прошли; Windows ещё не проверен.
[ ] **ChatGUI Windows: подтверждение обновления runtime компилятора** — 05.10.2026:
`cc-probe.log`: cc1 запускается из корня, но в `tools/chatgui/build` завершается
с `0xC0000139`; локальные DLL GCC/math/runtime отличаются от UCRT64.
Перед обеими точками сборки добавлено обновление существующих копий этих DLL;
раскладка FFmpeg использует для них UCRT64. Проверены обновление, повторный вызов,
сохранение кодеков и ошибки. Требуется Windows-прогон `check_cc.bat`: cc1 после
обновления, повтор команды Ninja и сборка Opus должны завершиться с кодом 0.
[ ] **ChatGUI Windows: обновление остальных DLL приложения после обновления MSYS2** — 05–06.10.2026:
Пропуск раскладки при наличии основного комплекта уже убран: зависимости проверяются на каждой сборке,
Qt DLL/плагины и собственный комплект FFmpeg копируются заново; runtime GCC обновляется отдельно.
Осталось определить обновление прочих уже существующих транзитивных DLL: рекурсивный проход
по-прежнему копирует только отсутствующие файлы. Сохранить приоритет собственной сборки FFmpeg
и проверить запуск приложения после обновления MSYS2.
[ ] **Android: время жизни config provider** — 03.10.2026: `utun_bridge_init()`
передаёт адрес локального `utun_config_provider_t` в `utun_config_set_provider()`,
который хранит указатель. После возврата из init он недействителен; перед
использованием `cfg_get_*` обеспечить постоянное хранение provider.
[ ] **Общий лимит check.sh после clean** — 01.10.2026: расчёт по времени предыдущего
прогона дал 205с вместе с полной сборкой и оборвал проверку после успешных тестов
до `test_uasync_socket_race`. Повторный запуск на готовой сборке с большим лимитом
прошёл за 206с, включая proxy/burst/load. Разделить бюджет сборки и запуска тестов
либо учитывать clean при расчёте.
[ ] **Повторно проверить паузы desktop-аудио при звонке с SM** — 01.10.2026:
В звонках 939a3ea36f3eeac3/b2a423ed2d36ed83 частота callback падала до 4–40/с,
входящий PCM продолжал приходить, очередь росла до нескольких секунд без dropped.
Диагностика backend, интервалов callback и capture/playback уже добавлена; позднее менялись аудиовывод и его буферы.
Нужен повтор на текущей сборке с CALL_STATS и временными метриками, чтобы проверить сохранение исходного сбоя.
Сетевую задержку оценивать отдельно: в одном из прежних звонков DIRECT RTT был 2–3мс.
[ ] **Измерить оставшуюся задержку запуска аудио SM после исправлений** — 01.10.2026:
В исходном звонке 2b9ad4e21a54d8ba первый capture приходил через 1140мс от startAudio;
router.start=781мс, startRecording → первый кадр=328мс, AudioTrack.play=327мс.
Независимый запуск capture/playback, отмена частичного старта и подробные замеры уже реализованы;
позднее добавлен выбор AAudio/AudioTrack. Осталось измерить текущую сборку на SM:
callbackMs/btProfileMs/applyMs/refreshMs, первый AudioRecord.read и первая запись playback,
начальное накопление очереди. Старые длительности не считать характеристиками текущей реализации.
[ ] **Гонка invite-файла в test_chat_join_e2e** — `wait_file()` проверяет только
существование файла, а `wf()` создаёт его до записи. Читатель иногда получает
пустую ссылку и ошибку префикса `utun://`. Публиковать готовый файл атомарно.
[+] **Linux ONNX Runtime: повторная регистрация схем при запуске теста** — проверено 21.10.2026:
Причина в системной сборке `1.23.2+dfsg-6ubuntu1`: Runtime вручную регистрирует схемы,
затем динамическая `libonnx.so.1` повторяет регистрацию в `OpSchemaRegistry::map()`.
Подтверждено GDB (`/tmp/silero-schema-gdb.log`) и исходниками обеих библиотек.
Текущая Linux x64 CMake-сборка переключена на [официальный Runtime 1.23.2](https://github.com/microsoft/onnxruntime/releases/tag/v1.23.2).
Архив `onnxruntime-linux-x64-1.23.2.tgz` распакован в `.cache/onnxruntime/`;
SHA-256: `1fa4dcaef22f6f7d5cd81b28c2800414350c10116f5fdd46a2160082551c5f9b`.
Для существующего include `onnxruntime/onnxruntime_c_api.h` создана ссылка
`.cache/onnxruntime/include/onnxruntime` → `../onnxruntime-linux-x64-1.23.2/include`.
В CMake явно заданы абсолютные пути: `ONNXRUNTIME_INCLUDE_DIR=<repo>/.cache/onnxruntime/include`,
`ONNXRUNTIME_LIB=<repo>/.cache/onnxruntime/onnxruntime-linux-x64-1.23.2/lib/libonnxruntime.so`.
Выбор сохраняется в `tools/chatgui/build/CMakeCache.txt`; после удаления cache его нужно повторить.
`ldd` подтверждает официальный Runtime без отдельной `libonnx`; весь `test_silero_vad` проходит без `Schema error`.
Лог проверки: `/tmp/silero-runtime-test.log`, полный вывод — `tools/chatgui/build/Testing/Temporary/LastTest.log`.
[ ] **debug_parse_config: формат и индексы категорий** — parser ожидает `:`, документация
описывает `=`; преобразование битовой маски осталось после перехода enum на индексы.
[Разбор](etcp_full_suite_findings.md).
[ ] **Повторно проверить crypto-шум при автоподключении в сценарии A–C–B**:
В старом test_dm_e2e попытка прямого A↔B через topo_group_connect давала packet undecryptable примерно раз в секунду.
Наблюдение относилось к прежним настройкам приёма и архитектуре подключений; устранение не подтверждено.
Воспроизвести на текущих NCD/ETCP и установить точную причину по логам до изменения политики подключения.
Отсутствие [client] само по себе не считать основанием запрета: CHAT использует собственных NCD-владельцев.
[ ] **lwip/proxy: утечка pcb в FIN_WAIT_2** — `tcp_shutdown(pcb,0,1)` (half-close по FIN от
exit) не ставит `TF_RXCLOSED`, поэтому таймаут FIN_WAIT_2 в `tcp_slowtmr` не срабатывает:
если браузер никогда не пришлёт FIN, pcb навсегда остаётся в FIN_WAIT_2 (утечка, не crash).
Двойной free по TIME_WAIT уже закрыт (detach по локальному закрытию + guard abort).
## Открытые флаки
[ ] **test_etcp_transport: таймаут восстановления REALITY-сессии** — 18.10.2026:
в общем `./check.sh` сценарий `reality=1 simultaneous=0 restart=0` завершился
`Session recovery timed out: phase=1 stream=15 control=6`. Перед таймаутом —
`record socket read failed: err=104`, клиент ждал server-handshake.
Отдельный повтор того же бинарника прошёл. Причина нестабильности пока не установлена;
изменения Android JNI/UI транспортные исходники не затрагивали.
[ ] **test_etcp_reconnect** — флаки под параллельной нагрузкой `make check -j4`:
phase 4 (reconnect после server restart) таймаутит (`sent=480 recv=0`), при одиночном
запуске стабильно `TEST PASSED`. Наш код (chat_join/chat_sync) его не трогает.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-present Silero Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+39 -2
View File
@@ -15,6 +15,8 @@ libuasync_a_SOURCES = \
socket_compat.h \
platform_compat.c \
platform_compat.h \
file_io.c \
file_io.h \
getmyip.c \
getmyip.h \
swm_min.c \
@@ -25,6 +27,10 @@ libuasync_a_SOURCES = \
radix.h \
serialize.c \
serialize.h \
video_plane.c \
video_plane.h \
reed_solomon.c \
reed_solomon.h \
json_flat.c \
json_flat.h \
tcp_io.c \
@@ -40,17 +46,48 @@ libuasync_a_SOURCES = \
dns.c \
dns.h \
async_dns.c \
async_dns.h
async_dns.h \
speex_aec.c \
speex_aec.h \
speexdsp/mdf.c \
speexdsp/fftwrap.c \
speexdsp/kiss_fft.c \
speexdsp/kiss_fftr.c
if HAVE_SILERO_VAD
libuasync_a_SOURCES += \
silero_vad.c \
silero_vad.h
endif
libuasync_a_CFLAGS = \
-D_ISOC99_SOURCE \
-DDEBUG_OUTPUT_STDERR \
-DSQLITE_THREADSAFE=1 \
-DDNS_RANDOM=RAND_bytes \
-DFLOATING_POINT \
-DUSE_KISS_FFT \
-DEXPORT= \
-g \
-I$(top_srcdir)/src \
-I$(top_srcdir)/lib \
-I$(top_srcdir)/lib/libopus/include
-I$(top_srcdir)/lib/libopus/include \
-I$(top_srcdir)/lib/speexdsp \
-I$(top_srcdir)/lib/speexdsp/include
if HAVE_SILERO_VAD
libuasync_a_CFLAGS += -DHAVE_SILERO_VAD
endif
# Вшитая модель Silero VAD (включается из silero_vad.c)
EXTRA_DIST = silero_vad_model.inc \
COPYING.silero-vad \
wintun-LICENSE.txt \
wintun-api-LICENSE.txt \
speexdsp/COPYING \
liblmdb/LICENSE \
liblmdb/COPYRIGHT \
soundtouch-master/COPYING.TXT
# Clean build directory
clean-local:
+119 -2
View File
@@ -20,6 +20,7 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#ifdef _WIN32
#include <iphlpapi.h>
@@ -37,6 +38,9 @@ struct adns_query {
void* timer; /* handle таймаута (NULL когда нет) */
int finished;
char* name;
int fast; /* 1 = готовый /etc/hosts-ответ, без libdns */
struct adns_result fast_result;
void* call_soon_id; /* отложенный вызов (fast-путь и первый драйв slow-пути) */
};
static void adns_drive(struct adns_query* q);
@@ -44,6 +48,8 @@ static void adns_finish(struct adns_query* q, int status, struct sockaddr_in* ad
static void adns_read_cb(socket_t sock, void* arg);
static void adns_write_cb(socket_t sock, void* arg);
static void adns_timer_cb(void* arg);
static void adns_fast_ready_cb(void* arg);
static int adns_hosts_lookup(const char* name, struct sockaddr_in* out);
/* ─── system DNS servers discovery ─── */
@@ -124,6 +130,49 @@ int adns_system_servers(struct sockaddr_in* out, int max) {
}
#endif
/* ─── синхронный /etc/hosts-лукап (быстрый путь, без libdns) ─── */
// Ищет name в /etc/hosts. Возвращает 1 если найден IPv4-адрес (out заполнен).
// Блокирующее чтение небольшого файла — только для локальных имён (localhost и т.п.).
static int adns_hosts_lookup(const char* name, struct sockaddr_in* out) {
#ifndef _WIN32
if (!name || !out) return 0;
FILE* f = fopen("/etc/hosts", "r");
if (!f) return 0;
char line[512];
int found = 0;
while (!found && fgets(line, sizeof(line), f)) {
char ip[64]; int n = 0;
const char* p = line;
while (*p && !isspace(*p) && n < 63) ip[n++] = *p++;
ip[n] = '\0';
if (!ip[0] || ip[0] == '#') continue;
struct in_addr a;
if (inet_pton(AF_INET, ip, &a) != 1) continue;
while (*p && isspace(*p)) p++;
while (*p && *p != '\n') {
if (*p == '#') break;
char h[256]; int hn = 0;
while (*p && !isspace(*p) && *p != '#' && hn < 255) h[hn++] = *p++;
h[hn] = '\0';
if (h[0] && strcasecmp(h, name) == 0) {
memset(out, 0, sizeof(*out));
out->sin_family = AF_INET;
out->sin_addr = a;
found = 1;
break;
}
while (*p && isspace(*p)) p++;
}
}
fclose(f);
return found;
#else
(void)name; (void)out;
return 0;
#endif
}
/* ─── resolver setup ─── */
static struct dns_resolver* adns_open_resolver(const struct sockaddr_in* servers, int count,
@@ -155,6 +204,11 @@ static struct dns_resolver* adns_open_resolver(const struct sockaddr_in* servers
dns_resconf_close(rc);
return NULL;
}
#ifndef _WIN32
// Локальные имена из /etc/hosts (localhost и т.п.). Не фатально, если файла нет.
if (dns_hosts_loadpath(hosts, "/etc/hosts") != 0)
DEBUG_DEBUG(ADNS_DEBUG_CAT, "adns: /etc/hosts not loaded");
#endif
struct dns_hints* hints = dns_hints_local(rc, &error);
if (!hints) {
@@ -197,7 +251,7 @@ static void adns_rearm(struct adns_query* q) {
}
q->cur_fd = -1;
if (fd >= 0) {
q->socket_id = uasync_add_socket_t(q->ua, (socket_t)fd, adns_read_cb, adns_write_cb, NULL, q);
q->socket_id = uasync_add_socket_t(q->ua, (socket_t)fd, adns_read_cb, adns_write_cb, NULL, "dns_query", q);
if (q->socket_id) q->cur_fd = fd;
else DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: uasync_add_socket_t(%d) failed", fd);
}
@@ -326,6 +380,34 @@ static void adns_finish(struct adns_query* q, int status, struct sockaddr_in* ad
u_free(name);
}
// Отложенный вызов коллбэка для fast-пути (/etc/hosts): сохраняет async-контракт
// (adns_resolve возвращает handle, коллбэк вызывается в потоке event loop).
static void adns_fast_ready_cb(void* arg) {
struct adns_query* q = (struct adns_query*)arg;
q->call_soon_id = NULL;
if (q->finished) return; // отменён до срабатывания
q->finished = 1;
adns_done_cb cb = q->cb;
void* a = q->arg;
struct adns_result res = q->fast_result;
char* nm = q->name;
q->name = NULL;
u_free(q);
if (cb) cb(&res, a);
u_free(nm);
}
// Отложенный первый драйв slow-пути (libdns): adns_resolve возвращает живой handle,
// а реальный запуск запроса происходит в event loop. Это исключает ситуацию, когда
// dns_res_check завершается синхронно прямо внутри adns_resolve() и освобождает q
// до того, как вызывающий код получил и сохранил handle.
static void adns_drive_trampoline(void* arg) {
struct adns_query* q = (struct adns_query*)arg;
q->call_soon_id = NULL;
if (q->finished) return; // отменён до срабатывания
adns_drive(q);
}
/* ─── public API ─── */
struct adns_query* adns_resolve(struct UASYNC* ua, const char* name,
@@ -334,6 +416,28 @@ struct adns_query* adns_resolve(struct UASYNC* ua, const char* name,
if (!ua || !name || !name[0] || !cb) return NULL;
if (strlen(name) > 255) { DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: name too long"); return NULL; }
// Быстрый путь: имя из /etc/hosts (localhost и т.п.) — без libdns и сети.
// libdns по умолчанию идёт в DNS первым (lookup="bf"), поэтому локальные имена
// ждали бы таймаут DNS-запроса (2×2s); обрабатываем их сразу.
{
struct sockaddr_in ha;
if (adns_hosts_lookup(name, &ha)) {
struct adns_query* q = u_calloc(1, sizeof(*q));
if (!q) { DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: calloc failed"); return NULL; }
q->ua = ua; q->cb = cb; q->arg = arg;
q->name = u_strdup(name);
if (!q->name) { u_free(q); DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: strdup failed"); return NULL; }
q->fast = 1;
q->fast_result.status = ADNS_OK;
q->fast_result.count = 1;
q->fast_result.addrs[0] = ha;
q->call_soon_id = uasync_call_soon(ua, q, adns_fast_ready_cb);
if (!q->call_soon_id) { u_free(q->name); u_free(q); return NULL; }
DEBUG_DEBUG(ADNS_DEBUG_CAT, "adns: '%s' resolved from /etc/hosts (fast)", name);
return q;
}
}
struct sockaddr_in servers[3];
int count;
if (opts && opts->server.sin_family != 0) {
@@ -373,13 +477,26 @@ struct adns_query* adns_resolve(struct UASYNC* ua, const char* name,
DEBUG_DEBUG(ADNS_DEBUG_CAT, "adns: resolving '%s' via %d server(s), timeout=%dms attempts=%d",
name, count, timeout_ms, attempts);
adns_drive(q);
q->call_soon_id = uasync_call_soon(ua, q, adns_drive_trampoline);
if (!q->call_soon_id) {
DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: uasync_call_soon failed for '%s'", name);
dns_res_close(q->res);
u_free(q->name);
u_free(q);
return NULL;
}
return q;
}
void adns_cancel(struct adns_query* q) {
if (!q || q->finished) return;
q->finished = 1;
if (q->call_soon_id) { uasync_call_soon_cancel(q->ua, q->call_soon_id); q->call_soon_id = NULL; }
if (q->fast) {
u_free(q->name);
u_free(q);
return;
}
adns_teardown(q);
u_free(q->name);
u_free(q);
+7 -3
View File
@@ -34,23 +34,27 @@ struct adns_result {
struct sockaddr_in addrs[ADNS_MAX_ADDRS]; /* AF_INET, sin_port = 0 */
};
/* res заимствован до возврата callback; handle запроса к этому моменту уже освобождён. */
typedef void (*adns_done_cb)(const struct adns_result* res, void* arg);
struct adns_opts {
struct sockaddr_in server; /* sin_family != 0 → использовать этот сервер (иначе системные) */
int timeout_ms; /* таймаут на сервер, по умолчанию 2000 */
int timeout_ms; /* Таймаут на сервер в мс; <=0 → 2000, округляется вверх до целых секунд. */
int max_attempts; /* попыток на сервер, по умолчанию 2 */
};
struct adns_query; /* opaque */
/* Запускает асинхронный A-запрос. Коллбэк вызывается в потоке event loop.
Возвращает handle (NULL при немедленной ошибке: невалидные аргументы/нет памяти). */
* Возвращает handle, автоматически освобождаемый до callback либо при отмене.
* NULL: неверные аргументы, нехватка памяти, отсутствие DNS-серверов, ошибка создания
* резолвера, постановки запроса или регистрации отложенного запуска; callback не вызывается. */
struct adns_query* adns_resolve(struct UASYNC* ua, const char* name,
const struct adns_opts* opts,
adns_done_cb cb, void* arg);
/* Отмена ожидающего запроса: коллбэк НЕ вызывается. Только из потока loop. */
/* Отмена ожидающего запроса: освобождает handle, коллбэк НЕ вызывается. Только из потока loop.
* После завершения/отмены handle недействителен; NULL допустим. */
void adns_cancel(struct adns_query* q);
/* Системные DNS-серверы (IPv4). Возвращает количество; out[].sin_port = htons(53).
+183 -222
View File
@@ -3,223 +3,144 @@
#include "mem.h"
#include <math.h>
#include <string.h>
#include <stdlib.h>
#define BLOCK_LEVELS_CHUNK 64
#define PENDING_CHUNK 32
#include <stdint.h>
/* История и задержанные блоки ограничены окном настройки. Только запись накапливает output. */
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;
int enabled, configured, failed, block_samples, lookback_blocks, lookahead_blocks;
float rise_factor_per_block, release_factor_per_block, max_gain, gain_smoothed;
float* block_levels;
size_t levels_cap, blocks_seen;
int16_t* pending;
size_t pending_cap, pending_head, pending_count, accum_count;
int16_t* output;
size_t output_size;
size_t output_cap;
int dbg_counter;
size_t output_size, output_cap;
uint32_t clip_count;
unsigned dbg_counter;
};
/* Начальное усиление в середине диапазона в дБ. */
static float initial_gain(float max_gain) { return max_gain > 0.0f ? sqrtf(max_gain) : 1.0f; }
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;
}
if (!ac) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "compressor: state allocation failed"); 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));
u_free(ac->pending); u_free(ac->block_levels); u_free(ac->output); u_free(ac);
}
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;
ac->pending_head = ac->pending_count = ac->accum_count = ac->blocks_seen = ac->output_size = 0;
ac->gain_smoothed = initial_gain(ac->max_gain);
ac->clip_count = ac->dbg_counter = 0;
ac->failed = 0;
}
void audio_compressor_set_enabled(struct audio_compressor* ac, int enabled) {
if (!ac) return;
ac->enabled = enabled ? 1 : 0;
/* Ноль lookahead означает отсутствие задержки; defaults остальных полей задаются явно здесь. */
int audio_compressor_configure(struct audio_compressor* ac, const audio_compressor_config_t* config) {
if (!ac || !config) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "compressor: missing configuration"); return -1; }
audio_compressor_config_t cfg = *config;
if (!cfg.sample_rate) cfg.sample_rate = 48000;
if (!cfg.channels) cfg.channels = 1;
if (!cfg.block_duration_ms) cfg.block_duration_ms = 20;
if (!cfg.lookback_ms) cfg.lookback_ms = 200;
if (!cfg.target_level) cfg.target_level = 0.25f;
if (!cfg.rise_rate_per_sec) cfg.rise_rate_per_sec = 4.0f;
if (!cfg.release_rate_per_sec) cfg.release_rate_per_sec = cfg.rise_rate_per_sec * 5.0f;
if (cfg.sample_rate < 8000 || cfg.sample_rate > 192000 || cfg.channels < 1 || cfg.channels > 2 ||
cfg.block_duration_ms < 1 || cfg.block_duration_ms > 1000 || cfg.lookback_ms < 0 || cfg.lookback_ms > 5000 ||
cfg.lookahead_ms < 0 || cfg.lookahead_ms > 5000 || !isfinite(cfg.target_level) || cfg.target_level <= 0 ||
!isfinite(cfg.max_gain_db) || cfg.max_gain_db < 0 || cfg.max_gain_db > 60 ||
!isfinite(cfg.rise_rate_per_sec) || cfg.rise_rate_per_sec <= 0 ||
!isfinite(cfg.release_rate_per_sec) || cfg.release_rate_per_sec <= 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "compressor: invalid rate=%d ch=%d block=%d back=%d ahead=%d",
cfg.sample_rate, cfg.channels, cfg.block_duration_ms, cfg.lookback_ms, cfg.lookahead_ms);
return -1;
}
int block = cfg.sample_rate * cfg.block_duration_ms / 1000 * cfg.channels;
int back = cfg.lookback_ms / cfg.block_duration_ms, ahead = cfg.lookahead_ms / cfg.block_duration_ms;
size_t levels_cap = (size_t)back + ahead + 2, pending_cap = (size_t)ahead + 2;
float* levels = u_calloc(levels_cap, sizeof(float));
int16_t* pending = u_calloc(pending_cap * (size_t)block, sizeof(int16_t));
if (!levels || !pending) {
u_free(levels); u_free(pending);
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "compressor: buffers allocation failed");
return -1;
}
u_free(ac->block_levels); u_free(ac->pending);
ac->cfg = cfg; ac->block_samples = block;
ac->lookback_blocks = back; ac->lookahead_blocks = ahead;
ac->block_levels = levels; ac->levels_cap = levels_cap;
ac->pending = pending; ac->pending_cap = pending_cap;
ac->rise_factor_per_block = powf(cfg.rise_rate_per_sec, (float)cfg.block_duration_ms / 1000.0f);
ac->release_factor_per_block = powf(cfg.release_rate_per_sec, (float)cfg.block_duration_ms / 1000.0f);
ac->max_gain = powf(10.0f, cfg.max_gain_db / 20.0f);
ac->configured = 1;
audio_compressor_reset(ac);
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "compressor: configured rate=%d ch=%d ahead=%dms history=%zu pending=%zu",
cfg.sample_rate, cfg.channels, cfg.lookahead_ms, levels_cap, pending_cap);
return 0;
}
int audio_compressor_is_enabled(const struct audio_compressor* ac) {
return ac ? ac->enabled : 0;
struct audio_compressor* audio_compressor_clone_config(const struct audio_compressor* source) {
if (!source || !source->configured) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "compressor: cannot clone configuration"); return NULL; }
struct audio_compressor* copy = audio_compressor_create();
if (copy && audio_compressor_configure(copy, &source->cfg) != 0) { audio_compressor_destroy(copy); return NULL; }
if (copy) copy->enabled = source->enabled;
return copy;
}
void audio_compressor_set_enabled(struct audio_compressor* ac, int enabled) { if (ac) ac->enabled = !!enabled; }
int audio_compressor_is_enabled(const struct audio_compressor* ac) { return ac && ac->enabled; }
/* Одна оценка для всех каналов сохраняет баланс стерео. */
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;
if (fabsf(v) > peak) peak = fabsf(v);
}
float rms = (float)sqrt(sum_sq / (double)count);
return (rms * 2.0f + peak) / 2.0f;
return (2.0f * sqrtf((float)(sum_sq / count)) + 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;
/* Выход записи растёт только вне аппаратного callback; OOM переводит запись в failed. */
static int reserve_output(struct audio_compressor* ac, size_t count) {
if (count > SIZE_MAX / sizeof(int16_t) - ac->output_size) goto failed;
size_t want = ac->output_size + count;
if (want <= ac->output_cap) return 0;
size_t capacity = ac->output_cap ? ac->output_cap : 4096;
while (capacity < want) {
if (capacity > SIZE_MAX / sizeof(int16_t) / 2) { capacity = want; break; }
capacity *= 2;
}
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);
int16_t* samples = u_realloc(ac->output, capacity * sizeof(int16_t));
if (!samples) goto failed;
ac->output = samples; ac->output_cap = capacity;
return 0;
failed:
ac->failed = 1;
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "compressor: recording output allocation failed samples=%zu", count);
return -1;
}
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];
/* Общий алгоритм усиления для записи с lookahead и потокового кадра без lookahead. */
static void apply_gain(struct audio_compressor* ac, int16_t* samples, size_t count, size_t idx) {
size_t begin = idx >= (size_t)ac->lookback_blocks ? idx - ac->lookback_blocks : 0;
size_t end = idx + ac->lookahead_blocks;
if (end >= ac->blocks_seen) end = ac->blocks_seen - 1;
float envelope = 0;
for (size_t i = begin; i <= end; i++) {
float level = ac->block_levels[i % ac->levels_cap];
if (level > envelope) envelope = level;
}
if (envelope < 0.0001f) envelope = 0.0001f;
@@ -227,66 +148,106 @@ static void process_pending_block(struct audio_compressor* ac) {
float G_prev = ac->gain_smoothed;
if (G_raw > ac->gain_smoothed) {
/* сигнал стал тише — плавно поднимаем gain (атака) */
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;
/* сигнал стал громче — плавно опускаем gain (release), без мгновенной ступеньки
* (мгновенный дроп давал клик на границе 20мс-блока). */
float min_fall = ac->gain_smoothed / ac->release_factor_per_block;
ac->gain_smoothed = G_raw > min_fall ? G_raw : min_fall;
}
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);
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "audio_compressor: block %zu level=%.4f envelope=%.4f gainRaw=%.1fdB gain=%.1fdB clip=%u",
idx, ac->block_levels[idx % ac->levels_cap], envelope, 20.0f * log10f(G_raw), gain_db, (unsigned)ac->clip_count);
}
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;
for (size_t i = 0; i < count; i++) {
float v = (float)samples[i] * ac->gain_smoothed;
if (v > 32767.0f) { v = 32767.0f; ac->clip_count++; }
if (v < -32768.0f) { v = -32768.0f; ac->clip_count++; }
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;
/* Слить один готовый блок записи. История хранится по абсолютному номеру в кольце. */
static int drain_block(struct audio_compressor* ac, size_t count) {
if (reserve_output(ac, count) != 0) return -1;
int16_t* samples = ac->pending + ac->pending_head * ac->block_samples;
apply_gain(ac, samples, count, ac->blocks_seen - ac->pending_count);
memcpy(ac->output + ac->output_size, samples, count * sizeof(int16_t));
ac->output_size += count;
ac->pending_head = (ac->pending_head + 1) % ac->pending_cap;
--ac->pending_count;
return 0;
}
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;
/* Ровно один кадр, без output-накопителя и выделений памяти. in/out могут совпадать. */
int audio_compressor_process_frame(struct audio_compressor* ac, const int16_t* in, int16_t* out, size_t count) {
if (!ac || !ac->configured || ac->failed || !in || !out || count != (size_t)ac->block_samples ||
ac->lookahead_blocks || ac->pending_count || ac->accum_count) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "compressor: invalid streaming frame count=%zu", count);
return -1;
}
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);
memmove(out, in, count * sizeof(int16_t));
if (ac->enabled) {
size_t idx = ac->blocks_seen++;
ac->block_levels[idx % ac->levels_cap] = compute_block_level(in, count);
apply_gain(ac, out, count, idx);
}
return 0;
}
const int16_t* audio_compressor_output(const struct audio_compressor* ac) {
return ac ? ac->output : NULL;
int audio_compressor_push(struct audio_compressor* ac, const int16_t* samples, size_t count) {
if (!ac || !ac->configured || ac->failed || (!samples && count)) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "compressor: invalid recording input"); return -1;
}
if (!ac->enabled) {
if (reserve_output(ac, count) != 0) return -1;
if (count) memcpy(ac->output + ac->output_size, samples, count * sizeof(int16_t));
ac->output_size += count;
return 0;
}
while (count) {
size_t take = (size_t)ac->block_samples - ac->accum_count;
if (take > count) take = count;
int16_t* block = ac->pending + ((ac->pending_head + ac->pending_count) % ac->pending_cap) * ac->block_samples;
memcpy(block + ac->accum_count, samples, take * sizeof(int16_t));
samples += take; count -= take; ac->accum_count += take;
if (ac->accum_count == (size_t)ac->block_samples) {
ac->block_levels[ac->blocks_seen++ % ac->levels_cap] = compute_block_level(block, ac->accum_count);
ac->accum_count = 0; ++ac->pending_count;
if (ac->pending_count > (size_t)ac->lookahead_blocks && drain_block(ac, ac->block_samples) != 0) return -1;
}
}
return 0;
}
size_t audio_compressor_output_size(const struct audio_compressor* ac) {
return ac ? ac->output_size : 0;
int audio_compressor_flush(struct audio_compressor* ac) {
if (!ac || !ac->configured || ac->failed) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "compressor: invalid flush"); return -1; }
if (!ac->enabled) return 0;
size_t tail = ac->accum_count;
if (tail) {
int16_t* block = ac->pending + ((ac->pending_head + ac->pending_count) % ac->pending_cap) * ac->block_samples;
ac->block_levels[ac->blocks_seen++ % ac->levels_cap] = compute_block_level(block, tail);
ac->accum_count = 0; ++ac->pending_count;
}
while (ac->pending_count) {
size_t count = tail && ac->pending_count == 1 ? tail : (size_t)ac->block_samples;
if (drain_block(ac, count) != 0) return -1;
}
return 0;
}
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; }
void audio_compressor_clear_output(struct audio_compressor* ac) { if (ac) ac->output_size = 0; }
uint32_t audio_compressor_clip_count(const struct audio_compressor* ac) { return ac ? ac->clip_count : 0; }
float audio_compressor_gain_db(const struct audio_compressor* ac) {
return ac && ac->configured && ac->enabled ? 20.0f * log10f(ac->gain_smoothed) : 0.0f;
}
+26 -4
View File
@@ -1,3 +1,9 @@
/* audio_compressor — адаптивное усиление int16 PCM (моно/стерео) с общей огибающей каналов.
* create → configure → обработка → destroy; объект использует один владелец, блокировок нет.
* Запись: push принимает произвольные порции, flush завершает хвост и lookahead; output принадлежит объекту.
* Поток: process_frame обрабатывает ровно sample_rate*block_duration_ms/1000*channels отсчётов,
* требует нулевого lookahead и пустого входного накопителя; in/out могут совпадать.
* count/output_size — interleaved отсчёты, не байты и не кадры на канал. Выключение даёт исходный PCM. */
#ifndef AUDIO_COMPRESSOR_H
#define AUDIO_COMPRESSOR_H
@@ -18,24 +24,40 @@ typedef struct {
int lookahead_ms;
float max_gain_db;
float rise_rate_per_sec;
float target_level;
float release_rate_per_sec; /* скорость плавного снижения gain (0 = авто: rise*5) */
float target_level; /* Линейная амплитуда огибающей, не дБ; 0 задаёт default 0.25. */
} audio_compressor_config_t;
struct audio_compressor* audio_compressor_create(void);
/* Новый пустой compressor с теми же настройками; позволяет передать запись worker без копии PCM. */
struct audio_compressor* audio_compressor_clone_config(const struct audio_compressor* source);
void audio_compressor_destroy(struct audio_compressor* ac);
void audio_compressor_configure(struct audio_compressor* ac, const audio_compressor_config_t* cfg);
int audio_compressor_configure(struct audio_compressor* ac, const audio_compressor_config_t* cfg);
/* Потоковый кадр без lookahead: результат немедленный, память не выделяется; 0 / -1. */
int audio_compressor_process_frame(struct audio_compressor* ac, const int16_t* in, int16_t* out, size_t count);
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);
/* push копирует вход; push/flush возвращают 0 / -1. Ошибка накопления требует reset перед повтором. */
int audio_compressor_push(struct audio_compressor* ac, const int16_t* samples, size_t count);
int 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);
/* Сбросить накопленный выход (output_size=0) после слива — для потокового режима.
* НЕ трогает gain_smoothed (адаптивная громкость сохраняется). */
void audio_compressor_clear_output(struct audio_compressor* ac);
/* Число сэмплов, обрезанных по потолку int16 (клиппинг) с момента create/reset. */
uint32_t audio_compressor_clip_count(const struct audio_compressor* ac);
/* Текущее фактическое усиление в дБ; выключенный compressor возвращает 0. */
float audio_compressor_gain_db(const struct audio_compressor* ac);
#ifdef __cplusplus
}
#endif
+45 -28
View File
@@ -129,6 +129,15 @@ static const struct {
{"chat_sync", DEBUG_CATEGORY_CHAT_SYNC},
{"member_sync", DEBUG_CATEGORY_MEMBER_SYNC},
{"proxy", DEBUG_CATEGORY_PROXY},
{"video", DEBUG_CATEGORY_VIDEO},
{"reality", DEBUG_CATEGORY_REALITY},
{"dm", DEBUG_CATEGORY_DM},
{"call", DEBUG_CATEGORY_CALL},
{"radio", DEBUG_CATEGORY_RADIO},
{"vad", DEBUG_CATEGORY_VAD},
{"aec", DEBUG_CATEGORY_AEC},
{"stream", DEBUG_CATEGORY_STREAM},
{"discovery", DEBUG_CATEGORY_DISCOVERY},
{"all", DEBUG_CATEGORY_ALL},
{NULL, DEBUG_CATEGORY_NONE}
};
@@ -263,6 +272,10 @@ void debug_set_masks(debug_category_t category_idx, debug_level_t level) {
debug_set_category_level(category_idx, level);
}
void debug_enable_timestamp(int enable) {
g_debug_config.timestamp_enabled = !!enable;
}
void debug_enable_function_name(int enable) {
g_debug_config.function_name_enabled = enable;
}
@@ -314,15 +327,24 @@ void debug_disable_file_output(void) {
}
}
/* Reopen log file (append mode) for SIGHUP. Complete operation with full error logging. */
/* Переоткрыть лог в append-режиме; при ошибке открытия сохранить прежний поток. */
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
FILE* new_file = fopen(path, "a");
if (!new_file) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Cannot reopen log '%s': %s", path, strerror(errno));
return -1;
}
FILE* old_file = g_debug_config.file_output;
g_debug_config.file_output = new_file;
if (old_file && fclose(old_file) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Cannot close previous log '%s': %s", path, strerror(errno));
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Log reopened: %s", path);
return 0;
}
@@ -382,13 +404,15 @@ void debug_output(debug_level_t level, debug_category_t category_idx,
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);
BUF_ADVANCE(snprintf(buffer + offset, remaining, "[%s-%03ld.%03ld] ", time_str, tv.tv_usec / 1000, tv.tv_usec % 1000));
if (g_debug_config.timestamp_enabled) {
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);
BUF_ADVANCE(snprintf(buffer + offset, remaining, "[%s-%03ld.%03ld] ", time_str, tv.tv_usec / 1000, tv.tv_usec % 1000));
}
/* Add thread marker if set */
if (g_debug_config.thread_marker > 0) {
@@ -451,7 +475,7 @@ 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") */
/* Настройка отдельных категорий: "sys=debug,etcp=error"; разделитель ':' также допустим. */
int debug_parse_config(const char* config_string) {
if (!config_string || !*config_string) {
return 0;
@@ -459,12 +483,10 @@ int debug_parse_config(const char* config_string) {
char* str = u_strdup(config_string);
if (!str) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to allocate debug configuration copy");
return -1;
}
debug_category_t categories = 0;
debug_level_t level = DEBUG_LEVEL_NONE;
char* token = strtok(str, ",");
while (token) {
/* Trim leading spaces */
@@ -472,15 +494,16 @@ int debug_parse_config(const char* config_string) {
token++;
}
char* colon = strchr(token, ':');
if (!colon) {
char* separator = strpbrk(token, "=:");
if (!separator) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Missing '=' or ':' in debug setting '%s'", token);
u_free(str);
return -1;
}
*colon = '\0';
*separator = '\0';
char* category_name = token;
char* level_name = colon + 1;
char* level_name = separator + 1;
/* Trim leading spaces in level_name */
while (isspace(*level_name)) {
@@ -488,22 +511,16 @@ int debug_parse_config(const char* config_string) {
}
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);
if (cat <= DEBUG_CATEGORY_NONE || cat >= DEBUG_CATEGORY_COUNT) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Unknown or unsupported debug category '%s' (expected an individual category)", 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;
}
debug_set_category_level(cat, lev);
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Applied debug setting: %s=%s (category=%d, level=%d)", category_name, level_name, cat, lev);
token = strtok(NULL, ",");
}
+53 -25
View File
@@ -1,6 +1,12 @@
/**
* Runtime debug configuration system
* Provides flexible control over debug output without recompilation
* Управление диагностикой: debug_config_init → уровни категорий и настройки вывода → DEBUG_*.
* NONE наследует общий уровень, DISABLED отключает категорию. Имена настроек — category=level.
* Открытый файл имеет приоритет над консолью; hook получает сообщения независимо от выбора выхода.
* Уровни: disabled, none, error, warn, info, debug, trace; отдельный уровень категории заменяет общий, не max().
* Пример: debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); debug_parse_config("etcp=trace,config=info").
* DEBUG_* фильтруют до форматирования; прямой debug_output фильтр не проверяет.
* Строка с префиксами ограничена буфером 4096 байт и обрезается; каждый вывод fflush.
* Модуль и смена глобальных настроек/файлов не синхронизированы; параллельный доступ согласует владелец.
*/
#ifndef DEBUG_CONFIG_H
@@ -41,7 +47,7 @@ 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_CONNECTION 3 // Connection: connect/reconnect/disconnect events only
#define DEBUG_CATEGORY_ETCP 4 // ETCP: protocol, BBR, normalizer
#define DEBUG_CATEGORY_CRYPTO 5 // crypto operations
// #define DEBUG_CATEGORY_MEMORY 6 (merged into SYS)
@@ -67,19 +73,28 @@ typedef int debug_category_t;
#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_PROXY 28 // Proxy modules (SOCKS5, TCP, UDP, ICMP)
#define DEBUG_CATEGORY_COUNT 29 // Total number of categories
#define DEBUG_CATEGORY_VIDEO 29 // Video module (probe/transcode)
#define DEBUG_CATEGORY_REALITY 30 // REALITY TLS camouflage
#define DEBUG_CATEGORY_DM 31 // DM — direct p2p chat between two users
#define DEBUG_CATEGORY_CALL 32 // Call — P2P audio call (signaling + media)
#define DEBUG_CATEGORY_RADIO 33 // Radio — PTT walkie-talkie multicast (voice)
#define DEBUG_CATEGORY_VAD 34 // VAD — Silero voice activity detector (ONNX Runtime)
#define DEBUG_CATEGORY_AEC 35 // AEC — acoustic echo cancellation (SpeexDSP)
#define DEBUG_CATEGORY_STREAM 36 // Подписанные потоки, FEC, буферы и выбор поставщиков
#define DEBUG_CATEGORY_DISCOVERY 37 // LAN UDP: discovery and delivery
#define DEBUG_CATEGORY_COUNT 38 // 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)
debug_level_t category_levels[DEBUG_CATEGORY_COUNT]; // Уровень категории: NONE наследует общий, 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
const char* output_file; // Заимствованный путь для reopen; строка должна жить, пока поле её хранит
// Dual output support
// Выход: файл, иначе консоль при console_enabled != 0
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)
@@ -88,38 +103,47 @@ typedef struct {
/* Global debug configuration */
extern debug_config_t g_debug_config;
/* Initialize debug system with default settings */
/* До использования: ERROR, категории NONE, консоль stdout, timestamp/function/file:line включены.
* Повторный init сбрасывает поля, но не закрывает уже открытый файл; перед ним закрыть файл отдельно. */
void debug_config_init(void);
/* Set debug level */
// итоговый level = max (global level - здесь задается, category level)
// Общий уровень применяется к категориям с уровнем NONE.
void debug_set_level(debug_level_t level);
/* Set debug level for specific category (0 = disabled, otherwise uses that level) */
/* Уровень категории заменяет общий; NONE наследует его, DISABLED подавляет вывод. */
void debug_set_category_level(debug_category_t category, debug_level_t level);
/* Enable/disable specific categories */
/* enable фиксирует текущий общий уровень; disable возвращает к наследованию (NONE).
* Для полного выключения использовать debug_set_category_level(cat, DEBUG_LEVEL_DISABLED). */
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 */
/* Установка уровня одной категории (аргумент categories — индекс, не битовая маска). */
void debug_set_masks(debug_category_t categories, debug_level_t level);
/* Configure output options */
/* Configure output options */
/* Ненулевое enable включает временную метку, 0 отключает. */
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);
/* Открыть ненулевой file_path в append-режиме, только если файл ещё не открыт; путь заимствован. */
void debug_set_output_file(const char* file_path);
/* Dual output configuration */
/* Заменить файл: truncate != 0 → обрезать, иначе append. NULL ничего не меняет.
* Путь заимствован; успешное открытие также выключает console_enabled. */
void debug_enable_file_output(const char* file_path, int truncate);
/* Закрыть файл; прежнее значение console_enabled не восстанавливается. */
void debug_disable_file_output(void);
/* Переоткрыть текущий путь в append-режиме: 0 — успех, -1 — ошибка с диагностикой.
* При ошибке открытия сохраняет прежний поток; console_enabled не меняет. */
int debug_reopen_log(void);
/* Вывод в консоль разрешён только при отсутствии открытого файла. */
void debug_enable_console(int enable);
// IP address to string (static buffer, single-threaded)
// IP/sockaddr в строку: результат возвращается по значению, освобождать не требуется.
typedef struct {
char str[54]; // INET6_ADDRSTRLEN(45) + 6chars (:port) + \0
} ip_str_t;
@@ -127,7 +151,7 @@ typedef struct {
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 в лог
// Hex dump: первые min(len, 128) байт; при len > 128 добавляет "...". Применяется фильтр уровня/категории.
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 */
@@ -136,13 +160,13 @@ 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 */
/* Заимствованное имя категории; для неизвестного индекса возвращает "UNKNOWN". */
const char* debug_get_category_name(debug_category_t category_idx);
/* Get category by name string */
/* Поиск без учёта регистра: индекс, ALL для "all", NONE при NULL/неизвестном имени. */
debug_category_t get_category_by_name(const char* name);
/* Get level by name string with error reporting */
/* Поиск без учёта регистра: NULL → NONE; неизвестное имя логируется и даёт INFO (fallback). */
debug_level_t debug_level_from_name(const char* name);
/* Set category level using text names (main API) */
@@ -154,16 +178,20 @@ int debug_apply_category_config(const char* category_name, const char* level_nam
/* 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) */
/* Форматирует сообщение printf-формата с необязательными метками; фильтрацию выполняет вызывающий.
* Время календарное, локальное, hh:mm:ss-миллисекунды.микросекунды; положительный thread_marker выводится отдельно. */
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) */
/* Hook вызывается синхронно для форматированного сообщения до записи в выход; NULL снимает hook.
* category/message заимствованы на время вызова, message указывает на стековый буфер; для хранения скопировать. */
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") */
/* Настройка отдельных категорий: "etcp=trace,config=info" (также ':' вместо '=').
* NULL/пустая строка ничего не меняет. Возвращает 0 при успехе, -1 при ошибке;
* настройки до ошибочного элемента остаются применёнными. */
int debug_parse_config(const char* config_string);
/* Convenience macros for debug output */
@@ -192,8 +220,8 @@ int debug_parse_config(const char* config_string);
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__)
/* Сообщение без явной категории: ERROR в категории GENERAL, с обычной фильтрацией. */
#define DEBUG_OUTPUT(fmt, ...) DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, fmt, ##__VA_ARGS__)
#ifdef __cplusplus
}
-112
View File
@@ -1,112 +0,0 @@
# 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 = нет)
+23 -17
View File
@@ -3403,27 +3403,33 @@ size_t dns_txt_print(void *dst_, size_t lim, struct dns_txt *txt) {
} /* dns_txt_print() */
typedef int (*dns_parse_fn)(union dns_any *, struct dns_rr *, struct dns_packet *);
typedef int (*dns_push_fn)(struct dns_packet *, union dns_any *);
typedef int (*dns_cmp_fn)(const union dns_any *, const union dns_any *);
typedef size_t (*dns_print_fn)(void *, size_t, union dns_any *);
typedef size_t (*dns_cname_fn)(void *, size_t, union dns_any *);
static const struct {
enum dns_type type;
const char *name;
int (*parse)();
int (*push)();
int (*cmp)();
size_t (*print)();
size_t (*cname)();
dns_parse_fn parse;
dns_push_fn push;
dns_cmp_fn cmp;
dns_print_fn print;
dns_cname_fn cname;
} dns_rrtypes[] = {
{ DNS_T_A, "A", &dns_a_parse, &dns_a_push, &dns_a_cmp, &dns_a_print, 0 },
{ DNS_T_AAAA, "AAAA", &dns_aaaa_parse, &dns_aaaa_push, &dns_aaaa_cmp, &dns_aaaa_print, 0 },
{ DNS_T_MX, "MX", &dns_mx_parse, &dns_mx_push, &dns_mx_cmp, &dns_mx_print, &dns_mx_cname },
{ DNS_T_NS, "NS", &dns_ns_parse, &dns_ns_push, &dns_ns_cmp, &dns_ns_print, &dns_ns_cname },
{ DNS_T_CNAME, "CNAME", &dns_cname_parse, &dns_cname_push, &dns_cname_cmp, &dns_cname_print, &dns_cname_cname },
{ DNS_T_SOA, "SOA", &dns_soa_parse, &dns_soa_push, &dns_soa_cmp, &dns_soa_print, 0 },
{ DNS_T_SRV, "SRV", &dns_srv_parse, &dns_srv_push, &dns_srv_cmp, &dns_srv_print, &dns_srv_cname },
{ DNS_T_OPT, "OPT", &dns_opt_parse, &dns_opt_push, &dns_opt_cmp, &dns_opt_print, 0 },
{ DNS_T_PTR, "PTR", &dns_ptr_parse, &dns_ptr_push, &dns_ptr_cmp, &dns_ptr_print, &dns_ptr_cname },
{ DNS_T_TXT, "TXT", &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0 },
{ DNS_T_SPF, "SPF", &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0 },
{ DNS_T_SSHFP, "SSHFP", &dns_sshfp_parse, &dns_sshfp_push, &dns_sshfp_cmp, &dns_sshfp_print, 0 },
{ DNS_T_A, "A", (dns_parse_fn)&dns_a_parse, (dns_push_fn)&dns_a_push, (dns_cmp_fn)&dns_a_cmp, (dns_print_fn)&dns_a_print, 0 },
{ DNS_T_AAAA, "AAAA", (dns_parse_fn)&dns_aaaa_parse, (dns_push_fn)&dns_aaaa_push, (dns_cmp_fn)&dns_aaaa_cmp, (dns_print_fn)&dns_aaaa_print, 0 },
{ DNS_T_MX, "MX", (dns_parse_fn)&dns_mx_parse, (dns_push_fn)&dns_mx_push, (dns_cmp_fn)&dns_mx_cmp, (dns_print_fn)&dns_mx_print, (dns_cname_fn)&dns_mx_cname },
{ DNS_T_NS, "NS", (dns_parse_fn)&dns_ns_parse, (dns_push_fn)&dns_ns_push, (dns_cmp_fn)&dns_ns_cmp, (dns_print_fn)&dns_ns_print, (dns_cname_fn)&dns_ns_cname },
{ DNS_T_CNAME, "CNAME", (dns_parse_fn)&dns_cname_parse, (dns_push_fn)&dns_cname_push, (dns_cmp_fn)&dns_cname_cmp, (dns_print_fn)&dns_cname_print, (dns_cname_fn)&dns_cname_cname },
{ DNS_T_SOA, "SOA", (dns_parse_fn)&dns_soa_parse, (dns_push_fn)&dns_soa_push, (dns_cmp_fn)&dns_soa_cmp, (dns_print_fn)&dns_soa_print, 0 },
{ DNS_T_SRV, "SRV", (dns_parse_fn)&dns_srv_parse, (dns_push_fn)&dns_srv_push, (dns_cmp_fn)&dns_srv_cmp, (dns_print_fn)&dns_srv_print, (dns_cname_fn)&dns_srv_cname },
{ DNS_T_OPT, "OPT", (dns_parse_fn)&dns_opt_parse, (dns_push_fn)&dns_opt_push, (dns_cmp_fn)&dns_opt_cmp, (dns_print_fn)&dns_opt_print, 0 },
{ DNS_T_PTR, "PTR", (dns_parse_fn)&dns_ptr_parse, (dns_push_fn)&dns_ptr_push, (dns_cmp_fn)&dns_ptr_cmp, (dns_print_fn)&dns_ptr_print, (dns_cname_fn)&dns_ptr_cname },
{ DNS_T_TXT, "TXT", (dns_parse_fn)&dns_txt_parse, (dns_push_fn)&dns_txt_push, (dns_cmp_fn)&dns_txt_cmp, (dns_print_fn)&dns_txt_print, 0 },
{ DNS_T_SPF, "SPF", (dns_parse_fn)&dns_txt_parse, (dns_push_fn)&dns_txt_push, (dns_cmp_fn)&dns_txt_cmp, (dns_print_fn)&dns_txt_print, 0 },
{ DNS_T_SSHFP, "SSHFP", (dns_parse_fn)&dns_sshfp_parse, (dns_push_fn)&dns_sshfp_push, (dns_cmp_fn)&dns_sshfp_cmp, (dns_print_fn)&dns_sshfp_print, 0 },
}; /* dns_rrtypes[] */
+1 -1
View File
@@ -508,7 +508,7 @@ struct dns_rr_i {
int follow;
int (*sort)();
int (*sort)(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *);
unsigned args[2];
struct {
+331
View File
@@ -0,0 +1,331 @@
#include "file_io.h"
#include "platform_compat.h"
#include "debug_config.h"
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <limits.h>
#ifdef _WIN32
static SRWLOCK temp_mutex = SRWLOCK_INIT;
#define TEMP_LOCK() AcquireSRWLockExclusive(&temp_mutex)
#define TEMP_UNLOCK() ReleaseSRWLockExclusive(&temp_mutex)
#else
#include <pthread.h>
static pthread_mutex_t temp_mutex = PTHREAD_MUTEX_INITIALIZER;
#define TEMP_LOCK() pthread_mutex_lock(&temp_mutex)
#define TEMP_UNLOCK() pthread_mutex_unlock(&temp_mutex)
#endif
/* Реестр заимствует стабильный адрес владельца, не выделяет память и общий для workers/instances. */
static struct file_io_temp *owned_temps;
static void file_io_temp_untrack(struct file_io_temp *output) {
struct file_io_temp **p = &owned_temps;
while (*p && *p != output) p = &(*p)->next_owned;
if (*p) *p = output->next_owned;
else DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file temporary owner missing from registry path=%s", output->temporary);
output->next_owned = NULL;
}
#ifdef _WIN32
/* Общие пути API — UTF-8; ANSI Win32 API не применяется. */
int file_io_wide(const char *path, wchar_t wide[FILE_IO_PATH_MAX]) {
if (path && *path && strlen(path) < FILE_IO_PATH_MAX
&& MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, wide, FILE_IO_PATH_MAX)) return 0;
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file invalid UTF-8/oversized path winerr=%lu", GetLastError());
return -1;
}
#endif
int file_io_inspect(const char *path, int follow, struct file_io_info *info) {
if (!path || !*path || strlen(path) >= FILE_IO_PATH_MAX || !info) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file inspect invalid path/output"); return -1;
}
struct file_io_info result = {0};
#ifdef _WIN32
wchar_t wide[FILE_IO_PATH_MAX];
if (file_io_wide(path, wide)) return -1;
DWORD attributes = GetFileAttributesW(wide);
if (attributes == INVALID_FILE_ATTRIBUTES) {
DWORD error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) return 1;
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file inspect failed path=%s winerr=%lu", path, error); return -1;
}
if (!follow && (attributes & FILE_ATTRIBUTE_REPARSE_POINT)) result.type = FILE_IO_LINK;
else {
HANDLE handle = CreateFileW(wide, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
BY_HANDLE_FILE_INFORMATION data;
int valid = handle != INVALID_HANDLE_VALUE && GetFileInformationByHandle(handle, &data);
if (valid) {
result.type = GetFileType(handle) != FILE_TYPE_DISK ? FILE_IO_OTHER
: (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FILE_IO_DIRECTORY : FILE_IO_REGULAR;
uint64_t size = ((uint64_t)data.nFileSizeHigh << 32) | data.nFileSizeLow;
valid = size <= INT64_MAX;
result.size = (int64_t)size;
result.identity[0] = data.dwVolumeSerialNumber;
result.identity[1] = ((uint64_t)data.nFileIndexHigh << 32) | data.nFileIndexLow;
}
DWORD error = GetLastError();
if (handle != INVALID_HANDLE_VALUE) CloseHandle(handle);
if (!valid) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file handle inspect failed path=%s winerr=%lu", path, error); return -1; }
}
#else
struct stat data;
if ((follow ? stat(path, &data) : lstat(path, &data))) {
if (errno == ENOENT) return 1;
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file inspect failed path=%s errno=%d", path, errno); return -1;
}
result.type = S_ISREG(data.st_mode) ? FILE_IO_REGULAR : S_ISDIR(data.st_mode) ? FILE_IO_DIRECTORY
: S_ISLNK(data.st_mode) ? FILE_IO_LINK : FILE_IO_OTHER;
if (data.st_size < 0 || (uint64_t)data.st_size > INT64_MAX) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file size out of range path=%s", path); return -1;
}
result.size = (int64_t)data.st_size;
result.identity[0] = data.st_dev; result.identity[1] = data.st_ino;
#endif
*info = result;
return 0;
}
FILE *file_io_open_read(const char *path) {
struct file_io_info info;
if (file_io_inspect(path, 1, &info) || info.type != FILE_IO_REGULAR) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file source is not regular/readable path=%s", path ? path : "(null)"); return NULL;
}
#ifdef _WIN32
wchar_t wide[FILE_IO_PATH_MAX];
if (file_io_wide(path, wide)) return NULL;
FILE *file = _wfopen(wide, L"rb");
#else
FILE *file = fopen(path, "rb");
#endif
if (!file) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file open read failed path=%s errno=%d", path, errno);
return file;
}
FILE *file_io_open_update(const char *path) {
struct file_io_info info;
if (file_io_inspect(path, 0, &info) || info.type != FILE_IO_REGULAR) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file update requires regular file path=%s", path ? path : "(null)"); return NULL;
}
#ifdef _WIN32
wchar_t wide[FILE_IO_PATH_MAX];
if (file_io_wide(path, wide)) return NULL;
FILE *file = _wfopen(wide, L"r+b");
#else
FILE *file = fopen(path, "r+b");
#endif
if (!file) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file update open failed path=%s errno=%d", path, errno);
return file;
}
int file_io_paths(const char *source, const char *destination) {
struct file_io_info input, output;
if (file_io_inspect(source, 1, &input) || input.type != FILE_IO_REGULAR) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file invalid source path=%s", source ? source : "(null)"); return -1;
}
int status = file_io_inspect(destination, 0, &output);
if (status == 1) return 0;
if (status < 0) return -1;
if (output.type != FILE_IO_REGULAR) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file destination is not regular path=%s type=%d", destination, output.type); return -1;
}
return !memcmp(input.identity, output.identity, sizeof(input.identity));
}
/* Синхронизировать именно открытый файл; повторного открытия имени нет. */
static int file_io_sync_descriptor(FILE *file, const char *path) {
#ifdef _WIN32
int result = _commit(_fileno(file));
#else
int result = fsync(fileno(file));
#endif
if (result) {
int error = errno ? errno : EIO;
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file sync failed path=%s errno=%d", path, error);
errno = error;
}
return result ? -1 : 0;
}
int file_io_sync_file(const char *path) {
FILE *file = file_io_open_update(path);
if (!file) return -1;
int result = file_io_sync_descriptor(file, path);
if (fclose(file)) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file sync close failed path=%s errno=%d", path, errno); result = -1;
}
return result;
}
int file_io_sync_directory(const char *path) {
if (!path || !*path || strlen(path) >= FILE_IO_PATH_MAX) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file directory sync invalid path"); return -1;
}
#ifndef _WIN32
char parent[FILE_IO_PATH_MAX];
memcpy(parent, path, strlen(path) + 1);
char *slash = strrchr(parent, '/');
if (!slash) memcpy(parent, ".", 2);
else if (slash == parent) slash[1] = 0;
else *slash = 0;
int fd = open(parent, O_RDONLY | O_DIRECTORY);
if (fd < 0) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file directory open failed path=%s errno=%d", parent, errno); return -1; }
int error = fsync(fd) ? (errno ? errno : EIO) : 0;
if (close(fd) && !error) error = errno ? errno : EIO;
if (error) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file directory sync/close failed path=%s errno=%d", parent, error); return -1; }
#endif
return 0;
}
/* Только rename; владелец temp снимает регистрацию до длительного directory sync. */
static int file_io_rename(const char *source, const char *destination, unsigned flags) {
#ifdef _WIN32
wchar_t from[FILE_IO_PATH_MAX], to[FILE_IO_PATH_MAX];
if (file_io_wide(source, from) || file_io_wide(destination, to)) return -1;
DWORD options = MOVEFILE_REPLACE_EXISTING | ((flags & FILE_IO_DURABLE) ? MOVEFILE_WRITE_THROUGH : 0);
if (!MoveFileExW(from, to, options)) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file rename failed source=%s destination=%s winerr=%lu", source, destination, GetLastError());
return -1;
}
#else
(void)flags;
if (rename(source, destination)) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file rename failed source=%s destination=%s errno=%d", source, destination, errno); return -1;
}
#endif
return 0;
}
int file_io_publish(const char *source, const char *destination) {
if (file_io_paths(source, destination)) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file publish invalid source/destination"); return -1;
}
if (file_io_rename(source, destination, FILE_IO_DURABLE)) return -1;
if (file_io_sync_directory(destination)) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file installed but directory durability unconfirmed destination=%s", destination); return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "file durably installed destination=%s", destination);
return 0;
}
int file_io_temp_open(struct file_io_temp *output, const char *destination, unsigned flags) {
if (!output || output->file || output->temporary[0] || !destination || !*destination
|| (flags & ~FILE_IO_DURABLE) || strlen(destination) + 38 >= FILE_IO_PATH_MAX) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file temporary invalid owner/path"); return -1;
}
output->flags = flags;
for (unsigned attempt = 0; attempt < 8; attempt++) {
uint8_t random[16]; char suffix[33];
if (random_bytes(random, sizeof(random))) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file temporary RNG failed"); return -1; }
for (unsigned i = 0; i < sizeof(random); i++) snprintf(suffix + i * 2, 3, "%02x", random[i]);
char temporary[FILE_IO_PATH_MAX];
snprintf(temporary, sizeof(temporary), "%s.tmp.%s", destination, suffix);
#ifdef _WIN32
wchar_t wide[FILE_IO_PATH_MAX];
if (file_io_wide(temporary, wide)) return -1;
TEMP_LOCK();
int fd = _wopen(wide, _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE);
#else
TEMP_LOCK();
int fd = open(temporary, O_CREAT | O_EXCL | O_RDWR, 0600);
#endif
if (fd < 0) {
int error = errno; TEMP_UNLOCK();
if (error == EEXIST) continue;
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file temporary open failed destination=%s errno=%d", destination, error); return -1;
}
memcpy(output->temporary, temporary, strlen(temporary) + 1);
output->next_owned = owned_temps; owned_temps = output;
TEMP_UNLOCK(); /* Файл виден очистке только вместе с зарегистрированным владельцем. */
#ifdef _WIN32
output->file = _fdopen(fd, "wb+");
if (!output->file) _close(fd);
#else
output->file = fdopen(fd, "wb+");
if (!output->file) close(fd);
#endif
if (!output->file) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file fdopen failed path=%s errno=%d", output->temporary, errno); return -1; }
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "file temporary opened path=%s durable=%u", output->temporary, flags & FILE_IO_DURABLE);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file temporary collision limit destination=%s", destination); return -1;
}
int file_io_temp_finish(struct file_io_temp *output) {
if (!output || !output->file) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file finish without open file"); return -1; }
int error = fflush(output->file) ? (errno ? errno : EIO) : 0;
if (!error && (output->flags & FILE_IO_DURABLE) && file_io_sync_descriptor(output->file, output->temporary))
error = errno ? errno : EIO;
if (fclose(output->file) && !error) error = errno ? errno : EIO;
output->file = NULL;
if (error) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file flush/sync/close failed path=%s errno=%d", output->temporary, error); return -1; }
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "file temporary finished path=%s durable=%u", output->temporary, output->flags & FILE_IO_DURABLE);
return 0;
}
void file_io_temp_discard(struct file_io_temp *output) {
if (!output) return;
if (output->file) {
if (fclose(output->file)) DEBUG_WARN(DEBUG_CATEGORY_SYS, "file cleanup close failed errno=%d", errno);
output->file = NULL;
}
if (!output->temporary[0]) return;
#ifdef _WIN32
wchar_t wide[FILE_IO_PATH_MAX];
int valid = file_io_wide(output->temporary, wide) == 0;
TEMP_LOCK();
int error = valid ? _wremove(wide) : -1;
#else
TEMP_LOCK();
int error = remove(output->temporary);
#endif
if (error && errno != ENOENT) DEBUG_WARN(DEBUG_CATEGORY_SYS, "file cleanup remove failed path=%s errno=%d", output->temporary, errno);
file_io_temp_untrack(output);
output->temporary[0] = 0;
TEMP_UNLOCK();
}
int file_io_temp_publish(struct file_io_temp *output, const char *source, const char *destination) {
if (!output || output->file || !output->temporary[0] || file_io_paths(source, destination)) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file publish invalid state/paths"); return -1;
}
TEMP_LOCK();
if (file_io_rename(output->temporary, destination, output->flags)) { TEMP_UNLOCK(); return -1; }
file_io_temp_untrack(output);
output->temporary[0] = 0;
TEMP_UNLOCK();
if ((output->flags & FILE_IO_DURABLE) && file_io_sync_directory(destination)) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file temporary installed but directory durability unconfirmed destination=%s", destination); return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "file published destination=%s durable=%u", destination, output->flags & FILE_IO_DURABLE);
return 0;
}
int file_io_remove_orphan(const char *path) {
if (!path || !*path) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file orphan removal without path"); return -1; }
#ifdef _WIN32
wchar_t wide[FILE_IO_PATH_MAX];
if (file_io_wide(path, wide)) return -1;
#endif
TEMP_LOCK();
for (struct file_io_temp *owner = owned_temps; owner; owner = owner->next_owned) {
if (!strcmp(owner->temporary, path)) {
TEMP_UNLOCK();
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "file orphan cleanup keeps active temporary path=%s", path);
return 0;
}
}
#ifdef _WIN32
int result = _wremove(wide);
#else
int result = remove(path);
#endif
int error = errno; TEMP_UNLOCK();
if (!result) return 1;
if (error == ENOENT) return 0;
DEBUG_WARN(DEBUG_CATEGORY_SYS, "file orphan removal failed path=%s errno=%d", path, error);
return -1;
}
+63
View File
@@ -0,0 +1,63 @@
/* file_io — синхронные файловые операции: UTF-8 пути, идентичность и публикация через соседний temp.
* inspect различает найденный/отсутствующий файл и ошибку; paths сравнивает идентичность файлов.
* Temp обнулить → open → писать в file → finish → publish; discard обязателен также при ошибке.
* Активный temp автоматически защищён от remove_orphan во всех потоках до publish/discard, включая время после finish.
* Пока temp активен, структуру нельзя копировать/перемещать; с одним temp работает один владелец.
* Исходник и каталоги должны оставаться стабильными на время операции. Это не защита от подмены родительских каталогов.
* FILE_IO_DURABLE сохраняет данные при finish и имя при publish. POSIX temp имеет права 0600.
* После успешного rename temp отсоединён даже при ошибке синхронизации каталога: отката публикации нет. */
#ifndef FILE_IO_H
#define FILE_IO_H
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#define FILE_IO_PATH_MAX 4096
#define FILE_IO_DURABLE 1u
enum file_io_type { FILE_IO_REGULAR = 1, FILE_IO_DIRECTORY, FILE_IO_LINK, FILE_IO_OTHER };
struct file_io_info {
int type; // обычный файл, каталог, ссылка/reparse point или другой объект
int64_t size; // размер в байтах, без сужения до int
uint64_t identity[3]; // идентификатор файла внутри файловой системы, сравнивать весь массив
};
struct file_io_temp {
char temporary[FILE_IO_PATH_MAX]; // только собственный temp; пусто после discard/publish
FILE *file; // открытый wb+; NULL после finish
struct file_io_temp *next_owned; // внутренний реестр живых temp; изменяет только file_io под блокировкой
unsigned flags; // режим, заданный open: 0 либо FILE_IO_DURABLE на весь срок операции
};
// path — UTF-8; follow!=0 разрешает конечную ссылку. info обязателен, заполняется только при успехе.
int file_io_inspect(const char *path, int follow, struct file_io_info *info);
#ifdef _WIN32
// Преобразовать непустой UTF-8 путь в wide[FILE_IO_PATH_MAX]; 0/-1.
int file_io_wide(const char *path, wchar_t wide[FILE_IO_PATH_MAX]);
#endif
// Открыть обычный файл для чтения; путь UTF-8. NULL при ошибке, fclose принадлежит вызывающему.
FILE *file_io_open_read(const char *path);
// Открыть существующий regular-файл для чтения/записи без усечения; конечные ссылки запрещены. UTF-8, NULL при ошибке.
FILE *file_io_open_update(const char *path);
// Источник должен быть regular; назначение отсутствует или regular без конечной ссылки. 1 — один файл, 0 — разные, -1 — ошибка.
int file_io_paths(const char *source, const char *destination);
// Сохранить существующий закрытый regular-файл без конечной ссылки; UTF-8, 0/-1.
int file_io_sync_file(const char *path);
// Сохранить каталог, содержащий path; без разделителя используется '.', для '/file' — '/'. UTF-8, 0/-1.
int file_io_sync_directory(const char *path);
// Переместить отдельный готовый файл с durable заменой destination; caller владеет source. 0/-1.
// Ошибка после rename не откатывает замену; сохраняется каталог назначения. Файлы/каталоги не подменяются извне.
int file_io_publish(const char *source, const char *destination);
// Создать эксклюзивный temp рядом с destination; структура заранее обнулена, даже после ошибки вызвать discard.
// flags=0 — flush/close и atomic rename; FILE_IO_DURABLE — также sync файла и каталога назначения.
int file_io_temp_open(struct file_io_temp *output, const char *destination, unsigned flags);
// Проверить flush/[sync]/close, обнулить file при любом результате; temp остаётся собственностью output.
int file_io_temp_finish(struct file_io_temp *output);
// Закрыть/удалить собственный temp; повторный вызов допустим. Ошибки очистки логируются.
void file_io_temp_discard(struct file_io_temp *output);
// Повторно проверить paths и заменить destination закрытым соседним temp; одинаковые файлы отклоняются. 0/-1.
// При ошибке до rename temporary сохраняется; после rename он пуст, в том числе при отказе directory sync.
int file_io_temp_publish(struct file_io_temp *output, const char *source, const char *destination);
/* Удалить уже признанный сиротой файл, атомарно проверив владение file_io_temp во всех потоках.
* 1 — удалён, 0 — активный temp или уже отсутствует, -1 — ошибка (логируется).
* path должен иметь то же нормализованное написание, что и путь при temp_open; рабочий каталог не меняется. */
int file_io_remove_orphan(const char *path);
#endif
+7 -7
View File
@@ -23,24 +23,24 @@ int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
struct sockaddr_storage *result)
{
if (!local_addr || !remote_addr || !result) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: NULL argument");
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "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);
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "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");
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "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");
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "get_outgoing_local_ip: socket_create_udp failed");
return -1;
}
@@ -56,7 +56,7 @@ int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
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));
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "get_outgoing_local_ip: bind failed: %s", socket_strerror(err));
socket_close_wrapper(sock);
return -1;
}
@@ -65,7 +65,7 @@ int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
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));
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "get_outgoing_local_ip: connect failed: %s", socket_strerror(err));
socket_close_wrapper(sock);
return -1;
}
@@ -75,7 +75,7 @@ int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
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));
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "get_outgoing_local_ip: getsockname failed: %s", socket_strerror(err));
socket_close_wrapper(sock);
return -1;
}
+6 -4
View File
@@ -3,6 +3,8 @@
*
* Определяет локальный IP, который ОС выберет для отправки
* на заданный удаленный адрес через заданный локальный адрес.
* Передать local/remote одного семейства AF_INET или AF_INET6 и буфер result.
* Создаёт и закрывает временный UDP-сокет; предназначен для выбора маршрута, не для обработки каждого пакета.
*/
#ifndef GETMYIP_H
@@ -26,13 +28,13 @@ extern "C" {
* Определяет локальный IP-адрес, который ОС выберет для отправки
* на заданный удаленный адрес через заданный локальный адрес.
*
* Создает временный UDP-сокет, выполняет bind на local_addr,
* Создает временный UDP-сокет, выполняет bind на копию local_addr с портом 0,
* connect к remote_addr и получает выбранный локальный IP через getsockname.
*
* @param local_addr Локальный адрес (0.0.0.0 / :: для автоопределения)
* @param remote_addr Удаленный адрес (для определения маршрута)
* @param local_addr Локальный адрес (0.0.0.0 / :: для автоопределения); входной порт игнорируется
* @param remote_addr Удаленный адрес того же семейства (для определения маршрута)
* @param result Заполняемый локальный IP (порт = 0)
* @return 0 при успехе, -1 при ошибке
* @return 0 при успехе, -1 при NULL, неподдерживаемом/разном семействе или ошибке socket/bind/connect/getsockname
*/
int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
const struct sockaddr_storage *remote_addr,
-34
View File
@@ -1,34 +0,0 @@
# 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`
+27 -10
View File
@@ -1,4 +1,6 @@
#include "json_flat.h"
#include "mem.h"
#include "debug_config.h"
#include <string.h>
#define SKIP_WS(p, end) while ((p) < (end) && (*(p) == ' ' || *(p) == '\t' || *(p) == '\n' || *(p) == '\r')) (p)++
@@ -19,6 +21,8 @@ static int json_read_string(const char** pp, const char* end, char* out, size_t
case 'n': ch = '\n'; break;
case 'r': ch = '\r'; break;
case 't': ch = '\t'; break;
case 'b': ch = '\b'; break;
case 'f': ch = '\f'; break;
default: return -1;
}
}
@@ -33,20 +37,22 @@ static int json_read_string(const char** pp, const char* end, char* out, size_t
return 0;
}
static int json_parse_pair(const char** pp, const char* end,
static int json_parse_pair(const char** pp, const char* end, char* scratch, size_t capacity,
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;
char* key = scratch;
if (json_read_string(&p, end, key, capacity) != 0) return -1;
size_t key_size = strlen(key) + 1;
char* val = scratch + key_size;
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 (json_read_string(&p, end, val, capacity - key_size) != 0) return -1;
if (cb && cb(key, val, arg) != 0) { *pp = p; return 1; }
if (cb) { int rc = cb(key, val, arg); if (rc != 0) { *pp = p; return rc < 0 ? -1 : 1; } }
SKIP_WS(p, end); CHECK(p, end);
if (*p == ',') p++;
@@ -61,13 +67,23 @@ int json_flat_parse(const char* json, json_flat_cb cb, void* arg) {
SKIP_WS(p, end); CHECK(p, end);
if (*p != '{') return -1; p++;
/* Decoded key + value + two NULs fit within the encoded object size. */
size_t capacity = strlen(json) + 1;
if (capacity > UINT32_MAX) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "json_flat: input too large"); return -1; }
char local[384];
char* scratch = capacity <= sizeof(local) ? local : u_malloc(capacity);
if (!scratch) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "json_flat: allocation failed bytes=%zu", capacity); return -1; }
int result = 0;
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;
int rc = json_parse_pair(&p, end, scratch, capacity, cb, arg);
if (rc < 0) { result = -1; break; }
if (rc > 0) break;
SKIP_WS(p, end);
if (p >= end) { result = -1; break; }
if (*p == '}') break;
}
if (scratch != local) u_free(scratch);
return result;
}
struct get_ctx { const char* key; char* out; size_t sz; int found; };
@@ -81,6 +97,7 @@ static int get_cb(const char* k, const char* v, void* arg) {
}
int json_flat_get(const char* json, const char* key, char* value_out, size_t value_sz) {
if (!key || (value_out && !value_sz)) return -1;
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;
+7 -1
View File
@@ -2,7 +2,12 @@
* @file json_flat.h
* @brief Парсер плоского JSON без вложенности: {"key":"value",...}
*
* Только строковые значения. Без malloc, без зависимостей.
* Только строковые значения и обычные escapes; \uXXXX не поддерживается.
* Callback получает полные key/value, действительные только до его возврата.
* parse возвращает 0 при успехе/остановке callback, -1 при ошибке; остановка не проверяет остаток ввода.
* Синтаксис проверяется нестрого: допускаются пропущенные и завершающие запятые;
* хвост после закрывающей } игнорируется даже без остановки callback.
* get ищет первый ключ и копирует значение с NUL, при малом буфере обрезает; 0 найден, -1 ошибка/нет ключа.
*/
#ifndef JSON_FLAT_H
@@ -15,6 +20,7 @@ extern "C" {
#endif
typedef int (*json_flat_cb)(const char* key, const char* value, void* arg);
/* Callback: 0 = continue, >0 = stop successfully, <0 = error. */
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);
+44
View File
@@ -0,0 +1,44 @@
Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic,
Jean-Marc Valin, Timothy B. Terriberry,
CSIRO, Gregory Maxwell, Mark Borgerding,
Erik de Castro Lopo, Mozilla, Amazon
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.
- Neither the name of Internet Society, IETF or IETF Trust, nor the
names of specific contributors, may be used to endorse or promote
products derived from this software without specific prior written
permission.
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.
Opus is subject to the royalty-free patent licenses which are
specified at:
Xiph.Org Foundation:
https://datatracker.ietf.org/ipr/1524/
Microsoft Corporation:
https://datatracker.ietf.org/ipr/1914/
Broadcom Corporation:
https://datatracker.ietf.org/ipr/1526/
+2
View File
@@ -1,5 +1,7 @@
noinst_LIBRARIES = libopus_internal.a
EXTRA_DIST = COPYING
# Override global CFLAGS to disable ASAN (makes .a incompatible with PIE linking)
CFLAGS = -g -Og -fno-omit-frame-pointer -fno-pie -fPIC
+154 -57
View File
@@ -52,15 +52,23 @@ static void on_get_trampoline(void* arg);
static void empty_trampoline(void* arg) {
struct ll_queue* q = (struct ll_queue*)arg;
q->empty_call_soon_id = NULL;
if (q->count != 0) return;
queue_callback_fn cb = q->empty_callback;
void* cb_arg = q->empty_callback_arg;
q->empty_callback = NULL;
q->empty_callback_arg = NULL;
if (q->count == 0 && cb) cb(q, cb_arg);
if (cb) cb(q, cb_arg);
}
static void on_get_trampoline(void* arg) {
struct ll_queue* q = (struct ll_queue*)arg;
q->on_get_call_soon_id = NULL;
if (!q->on_get_pending || !q->on_get) return;
q->on_get_pending--;
if (q->on_get_pending) {
q->on_get_call_soon_id = uasync_call_soon(q->ua, q, on_get_trampoline);
if (!q->on_get_call_soon_id) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] on_get scheduling failed: pending=%zu", q->name, q->on_get_pending);
}
if (q->on_get) q->on_get(q, q->on_get_arg);
}
@@ -121,15 +129,8 @@ struct ll_entry* ll_alloc_lldgram(uint16_t len) {
return entry;
}
void queue_free(struct ll_queue* q) {
if (!q) return;
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "queue_free: freeing queue %p, head=%p, tail=%p, count=%d",
q, q->head, q->tail, q->count);
// ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно
// Это упрощает архитектуру и предотвращает double-u_free
static void queue_cancel_notifications(struct ll_queue* q) {
while (q->deferred_waiters) queue_waiter_cancel(q, q->deferred_waiters);
// Освободить список waiters (сбросить handle->internal, отменить отложенные вызовы)
struct queue_waiter* w = q->waiter_head;
while (w) {
@@ -141,11 +142,6 @@ void queue_free(struct ll_queue* q) {
q->waiter_head = NULL;
q->waiter_tail = NULL;
// Освободить хеш-таблицу
if (q->hash_table) {
u_free(q->hash_table);
}
// Отменить отложенное возобновление
if (q->resume_timeout_id) {
uasync_call_soon_cancel(q->ua, q->resume_timeout_id);
@@ -159,6 +155,35 @@ void queue_free(struct ll_queue* q) {
q->empty_callback_arg = NULL;
q->on_get = NULL;
q->on_get_arg = NULL;
if (q->on_get_call_soon_id) uasync_call_soon_cancel(q->ua, q->on_get_call_soon_id);
q->on_get_call_soon_id = NULL; q->on_get_pending = 0;
q->callback_suspended = 1;
}
void queue_clear(struct ll_queue* q) {
if (!q) return;
#ifdef QUEUE_THREAD_CHECK
queue_check_thread(q);
#endif
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "[%s] discard queue: packets=%d bytes=%zu", q->name, q->count, q->total_bytes);
queue_cancel_notifications(q);
struct ll_entry* entry = q->head;
q->head = q->tail = NULL; q->count = 0; q->total_bytes = 0;
if (q->hash_table) memset(q->hash_table, 0, q->hash_size * sizeof(*q->hash_table));
while (entry) {
struct ll_entry* next = entry->next;
entry->owner = NULL; entry->next = entry->prev = entry->hash_next = NULL;
queue_dgram_free(entry); queue_entry_free(entry); entry = next;
}
}
void queue_free(struct ll_queue* q) {
if (!q) return;
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "queue_free: freeing queue %p, head=%p, count=%d", (void*)q, (void*)q->head, q->count);
queue_cancel_notifications(q);
if (q->hash_table) u_free(q->hash_table);
if (q->head) DEBUG_WARN(DEBUG_CATEGORY_SYS, "[%s] freeing nonempty queue: count=%d; entries remain caller-owned", q->name, q->count);
for (struct ll_entry* e = q->head; e; e = e->next) e->owner = NULL;
u_free(q);
}
@@ -194,6 +219,19 @@ void queue_resume_callback(struct ll_queue* q) {
}
}
// Общий запуск коллбэка автозабора: immediate (default) или deferred через uasync_call_soon.
// Deferred-режим очищает стек производителя перед обработкой первого элемента.
static void queue_trigger_callback(struct ll_queue* q) {
if (!q->head || q->callback_suspended || !q->callback) return;
if (q->callback_defer) {
if (!q->resume_timeout_id)
q->resume_timeout_id = uasync_call_soon(q->ua, q, queue_resume_timeout_cb);
} else {
q->callback(q, q->callback_arg);
}
}
void queue_set_size_limit(struct ll_queue* q, int lim) {
if (!q) return;
q->size_limit = lim;
@@ -303,22 +341,42 @@ static uint32_t make_hash(const void* data, uint16_t len) {// алгоритм F
return hash;
}
static void waiter_defer_cb(void* arg) {
struct queue_waiter_handle* h = (struct queue_waiter_handle*)arg;
h->call_soon_id = NULL;
if (h->defer_cb) h->defer_cb(h->defer_q, h->defer_arg);
else DEBUG_ERROR(DEBUG_CATEGORY_SYS, "waiter_defer_cb: NULL callback (stale?) q=%p", (void*)h->defer_q);
static void unlink_deferred_waiter(struct ll_queue* q, struct queue_waiter_handle* h) {
struct queue_waiter_handle** p = &q->deferred_waiters;
while (*p && *p != h) p = &(*p)->defer_next;
if (*p) *p = h->defer_next;
else DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] deferred waiter not found: handle=%p", q->name, h);
h->defer_next = NULL; h->defer_q = NULL;
}
static inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
static void waiter_defer_cb(void* arg) {
struct queue_waiter_handle* h = (struct queue_waiter_handle*)arg;
struct ll_queue* q = h->defer_q;
queue_threshold_callback_fn cb = h->defer_cb;
void* cb_arg = h->defer_arg;
h->call_soon_id = NULL;
unlink_deferred_waiter(q, h);
h->defer_cb = NULL; h->defer_arg = NULL;
if (cb) cb(q, cb_arg);
else DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] deferred waiter has no callback", q->name);
}
static inline int queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
struct queue_waiter_handle* h) {
if (q->waiter_defer) {
if (h->call_soon_id) { DEBUG_WARN(DEBUG_CATEGORY_SYS, "queue_waiter_call: overwriting existing call_soon_id=%p", h->call_soon_id); uasync_call_soon_cancel(q->ua, h->call_soon_id); }
if (h->call_soon_id) queue_waiter_cancel(h->defer_q, h);
h->defer_q = q; h->defer_cb = cb; h->defer_arg = arg;
h->call_soon_id = uasync_call_soon(q->ua, h, waiter_defer_cb);
if (h->call_soon_id) { h->defer_next = q->deferred_waiters; q->deferred_waiters = h; }
else {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] deferred waiter scheduling failed: handle=%p", q->name, h);
h->defer_q = NULL; h->defer_cb = NULL; h->defer_arg = NULL;
return -1;
}
} else {
cb(q, arg);
}
return 0;
}
// Проверить и запустить первый ожидающий коллбэк (FIFO / round-robin)
@@ -338,13 +396,18 @@ static void check_waiters(struct ll_queue* q) {
queue_threshold_callback_fn cb = waiter->callback;
void* arg = waiter->callback_arg;
u_free(waiter);
if (cb) {
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "check_waiters: waking head waiter, count=%d<=%d, bytes=%zu<=%zu",
q->count, q->threshold_max_packets, q->total_bytes, q->threshold_max_bytes);
queue_waiter_call(q, cb, arg, h);
if (queue_waiter_call(q, cb, arg, h) < 0) {
// Отказ планирования не теряет ожидание: следующий get/remove повторит попытку.
waiter->next = q->waiter_head; q->waiter_head = waiter;
if (!q->waiter_tail) q->waiter_tail = waiter;
h->internal = waiter;
return;
}
}
u_free(waiter);
}
int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
@@ -353,7 +416,7 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
if (entry->owner || entry->prev || entry->next || q->head == entry || q->tail == entry) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] double put detected! entry=%p prev=%p next=%p len=%u",
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next, entry->len);
abort();
@@ -367,6 +430,7 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
}
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_WARN(DEBUG_CATEGORY_SYS, "[%s] queue full: count=%d limit=%d", q->name, q->count, q->size_limit);
queue_dgram_free(entry);
queue_entry_free(entry);
return -1;
@@ -378,19 +442,18 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
if (q->tail) q->tail->next = entry; else q->head = entry;
q->tail = entry;
entry->owner = q;
q->count++;
entry->int_len = entry->len;
q->total_bytes += entry->int_len;
// НЕ вызываем add_to_hash
if (q->count == 1 && !q->callback_suspended && q->callback) {
q->callback(q, q->callback_arg);
}
#ifdef QUEUE_DEBUG
queue_check_consistency(q);
#endif
// Пользовательский callback может освободить очередь.
if (q->count == 1) queue_trigger_callback(q);
return 0;
}
@@ -400,7 +463,7 @@ int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
if (entry->owner || entry->prev || entry->next || q->head == entry || q->tail == entry) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] double put_with_index detected! entry=%p prev=%p next=%p",
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
abort();
@@ -416,6 +479,7 @@ int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
entry->index_hash = make_hash(entry->data + q->index_offset, q->index_size);
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_WARN(DEBUG_CATEGORY_SYS, "[%s] queue full: count=%d limit=%d", q->name, q->count, q->size_limit);
queue_dgram_free(entry);
queue_entry_free(entry);
return -1;
@@ -427,19 +491,18 @@ int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
if (q->tail) q->tail->next = entry; else q->head = entry;
q->tail = entry;
entry->owner = q;
q->count++;
entry->int_len = entry->len;
q->total_bytes += entry->int_len;
add_to_hash(q, entry);
if (q->count == 1 && !q->callback_suspended && q->callback) {
q->callback(q, q->callback_arg);
}
#ifdef QUEUE_DEBUG
queue_check_consistency(q);
#endif
// Пользовательский callback может освободить очередь.
if (q->count == 1) queue_trigger_callback(q);
return 0;
}
@@ -449,7 +512,7 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
queue_check_thread(q);
#endif
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
if (entry->owner || entry->prev || entry->next || q->head == entry || q->tail == entry) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] double put_first detected! entry=%p prev=%p next=%p",
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
abort();
@@ -462,22 +525,25 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
return -1;
}
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_WARN(DEBUG_CATEGORY_SYS, "[%s] queue full: count=%d limit=%d", q->name, q->count, q->size_limit);
queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
entry->next = q->head;
entry->prev = NULL;
if (q->head) q->head->prev = entry; else q->tail = entry;
q->head = entry;
entry->owner = q;
q->count++;
entry->int_len = entry->len;
q->total_bytes += entry->int_len;
if (q->count == 1 && !q->callback_suspended && q->callback) {
q->callback(q, q->callback_arg);
}
#ifdef QUEUE_DEBUG
queue_check_consistency(q);
#endif
// Пользовательский callback может освободить очередь.
if (q->count == 1) queue_trigger_callback(q);
return 0;
}
@@ -488,7 +554,7 @@ int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry)
queue_check_thread(q);
#endif
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
if (entry->owner || entry->prev || entry->next || q->head == entry || q->tail == entry) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] double put_first_with_index detected! entry=%p prev=%p next=%p",
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
abort();
@@ -504,6 +570,7 @@ int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry)
entry->index_hash = make_hash(entry->data + q->index_offset, q->index_size);
if (q->size_limit >= 0 && q->count >= q->size_limit) {
DEBUG_WARN(DEBUG_CATEGORY_SYS, "[%s] queue full: count=%d limit=%d", q->name, q->count, q->size_limit);
queue_dgram_free(entry);
queue_entry_free(entry);
return -1;
@@ -514,19 +581,18 @@ int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry)
if (q->head) q->head->prev = entry; else q->tail = entry;
q->head = entry;
entry->owner = q;
q->count++;
entry->int_len = entry->len;
q->total_bytes += entry->int_len;
add_to_hash(q, entry);
if (q->count == 1 && !q->callback_suspended && q->callback) {
q->callback(q, q->callback_arg);
}
#ifdef QUEUE_DEBUG
queue_check_consistency(q);
#endif
// Пользовательский callback может освободить очередь.
if (q->count == 1) queue_trigger_callback(q);
return 0;
}
@@ -549,6 +615,7 @@ struct ll_entry* queue_data_get(struct ll_queue* q) {
entry->next = NULL;
entry->prev = NULL;
entry->owner = NULL;
remove_from_hash(q, entry);
@@ -557,18 +624,21 @@ struct ll_entry* queue_data_get(struct ll_queue* q) {
// Приостановить коллбэки для предотвращения рекурсии
q->callback_suspended = 1;
// Проверить ожидающие коллбэки
check_waiters(q);
if (q->count == 0 && q->empty_callback && !q->empty_call_soon_id)
q->empty_call_soon_id = uasync_call_soon(q->ua, q, (timeout_callback_t)empty_trampoline);
if (q->on_get)
uasync_call_soon(q->ua, q, (timeout_callback_t)on_get_trampoline);
if (q->on_get) {
q->on_get_pending++;
if (!q->on_get_call_soon_id) {
q->on_get_call_soon_id = uasync_call_soon(q->ua, q, on_get_trampoline);
if (!q->on_get_call_soon_id) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] on_get scheduling failed: pending=%zu", q->name, q->on_get_pending);
}
}
#ifdef QUEUE_DEBUG
queue_check_consistency(q);// !!!! for debug
#endif
check_waiters(q);
return entry;
}
@@ -647,6 +717,11 @@ void queue_set_waiter_defer(struct ll_queue* q, int enable) {
q->waiter_defer = enable;
}
void queue_set_callback_defer(struct ll_queue* q, int enable) {
if (!q) return;
q->callback_defer = enable;
}
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
if (!q) return;
void* old = q->empty_call_soon_id;
@@ -662,18 +737,24 @@ void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
if (!q) return;
q->on_get = cbk_fn;
q->on_get_arg = arg;
if (!cbk_fn) {
if (q->on_get_call_soon_id) uasync_call_soon_cancel(q->ua, q->on_get_call_soon_id);
q->on_get_call_soon_id = NULL; q->on_get_pending = 0;
}
}
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg) {
if (!q || !h) return -1;
if (!q || !h || !callback) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "queue_waiter_wait: invalid arguments q=%p handle=%p callback=%p", q, h, (void*)callback);
return -1;
}
if (h->internal) queue_waiter_cancel(q, h);
if (h->internal || h->call_soon_id) queue_waiter_cancel(h->call_soon_id ? h->defer_q : q, h);
if (q->count <= q->threshold_max_packets
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes)) {
queue_waiter_call(q, callback, arg, h);
return 1;
return queue_waiter_call(q, callback, arg, h) < 0 ? -1 : 1;
}
struct queue_waiter* waiter = u_malloc(sizeof(struct queue_waiter));
@@ -694,9 +775,18 @@ int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
}
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h) {
if (!q || !h || !h->internal) return;
if (h->call_soon_id) { uasync_call_soon_cancel(q->ua, h->call_soon_id); h->call_soon_id = NULL; }
if (!q || !h) return;
/* Отложенный вызов (waiter_defer) может висеть и при internal==NULL:
* queue_waiter_wait по ветке «очередь свободна» планирует call_soon_id,
* не регистрируя waiter. Ранний выход по !internal оставлял бы висячий
* callback с arg=handle, который после teardown разыменовывал бы freed-память. */
if (h->call_soon_id) {
struct ll_queue* deferred_q = h->defer_q;
uasync_call_soon_cancel(deferred_q->ua, h->call_soon_id); h->call_soon_id = NULL;
unlink_deferred_waiter(deferred_q, h); h->defer_cb = NULL; h->defer_arg = NULL;
}
if (!h->internal) return;
struct queue_waiter* waiter = h->internal;
struct queue_waiter* prev = NULL;
struct queue_waiter* curr = q->waiter_head;
@@ -754,6 +844,10 @@ struct ll_entry* queue_find_next_by_index(struct ll_queue* q, const void* index_
int queue_remove_data(struct ll_queue* q, struct ll_entry* entry) {
if (!q || !entry) return -1;
if (entry->owner != q) {
DEBUG_WARN(DEBUG_CATEGORY_SYS, "[%s] cannot remove foreign entry=%p owner=%p", q->name, entry, entry->owner);
return -1;
}
#ifdef QUEUE_THREAD_CHECK
queue_check_thread(q);
@@ -777,6 +871,7 @@ int queue_remove_data(struct ll_queue* q, struct ll_entry* entry) {
entry->next = NULL;
entry->prev = NULL;
entry->owner = NULL;
remove_from_hash(q, entry);
@@ -784,6 +879,8 @@ int queue_remove_data(struct ll_queue* q, struct ll_entry* entry) {
#ifdef QUEUE_DEBUG
queue_check_consistency(q);// !!!! for debug
#endif
if (q->count == 0 && q->empty_callback && !q->empty_call_soon_id)
q->empty_call_soon_id = uasync_call_soon(q->ua, q, empty_trampoline);
check_waiters(q);
return 0;
}
+120 -52
View File
@@ -1,6 +1,10 @@
#ifndef LL_QUEUE_H
#define LL_QUEUE_H
/* Очередь одного uasync-потока; наличие pthread-заголовков не делает её межпоточной.
* put может синхронно вызвать consumer/waiter. Держать владельцев живыми до завершения callbacks,
* waiter handle обнулять целиком и отменять до освобождения; entry и dgram освобождаются отдельно. */
#ifdef __cplusplus
extern "C" {
#endif
@@ -37,8 +41,8 @@ extern "C" {
* @note Важные правила использования автозабора (queue_set_callback):
* 1. Коллбэк получает управление только если в очереди есть элементы И !callback_suspended
* 2. Внутри коллбэка ОБЯЗАТЕЛЬНО: queue_data_get() → обработать элемент → queue_resume_callback()
* 3. Без вызова queue_resume_callback очередь навсегда застрянет
* 4. Не извлекайте элементы вручную вне коллбэка — это нарушит логику
* 3. Без queue_resume_callback автозабор остаётся приостановленным
* 4. Ручное извлечение допустимо; для продолжения автозабора после него тоже нужен resume
* 5. queue_data_get() автоматически suspend'ит коллбэки для предотвращения рекурсии
*
* @note Память:
@@ -57,10 +61,11 @@ struct queue_waiter_handle;
* @brief Элемент очереди (переменного размера).
*
* Память выделяется одним блоком: [struct ll_entry + data[size]].
* Поле dgram — отдельный блок (malloc / пул / внешняя память).
* Поле dgram — отдельный блок (u_malloc / пул / внешняя память со своим dgram_free_fn).
*/
struct ll_entry {
char* name;
struct ll_queue* owner; // Владелец элемента; NULL после извлечения
struct ll_entry* next; // Следующий элемент в очереди
struct ll_entry* prev; // Предыдущий элемент в очереди
uint16_t size; // Размер пользовательского буфера data[] (байт)
@@ -70,9 +75,9 @@ struct ll_entry {
uint8_t* dgram; // Указатель на данные пакета
void (*dgram_free_fn)(uint8_t* data); // Кастомная функция освобождения dgram
struct memory_pool* dgram_pool; // Пул для dgram (если используется)
uint32_t index_hash; // Хеш по первым 4 байтам индекса
uint32_t index_hash; // FNV-1a (32 бита) по всем index_size байтам data[] от index_offset
struct ll_entry* hash_next; // Следующий в хеш-цепочке
struct memory_pool* pool; // Пул, из которого выделен сам entry (NULL = malloc)
struct memory_pool* pool; // Пул, из которого выделен сам entry (NULL = u_malloc)
uint8_t data[0]; // Гибкий массив пользовательских данных (размер = size)
};
@@ -123,6 +128,7 @@ struct queue_waiter_handle {
queue_threshold_callback_fn defer_cb;
void* defer_arg;
void* call_soon_id; ///< хендл отложенного вызова (для отмены)
struct queue_waiter_handle* defer_next; ///< список отложенных уведомлений очереди
};
/**
@@ -146,9 +152,11 @@ struct ll_queue {
struct queue_waiter* waiter_head; // Голова списка ожидающих
struct queue_waiter* waiter_tail; // Хвост списка ожидающих
struct queue_waiter_handle* deferred_waiters; // Уже запланированные уведомления
int threshold_max_packets; // Общий порог: макс. кол-во элементов (по умолчанию 0)
size_t threshold_max_bytes; // Общий порог: макс. объём данных (0 = не проверять)
int waiter_defer; // 0=immediate callback, 1=defer via uasync_call_soon
int callback_defer; // 0=immediate callback из put (default), 1=defer via uasync_call_soon (очистка стека)
queue_callback_fn empty_callback; // одноразовый коллбэк при опустошении очереди
void* empty_callback_arg;
@@ -156,6 +164,8 @@ struct ll_queue {
queue_callback_fn on_get; // коллбэк после каждого queue_data_get (deferred)
void* on_get_arg;
void* on_get_call_soon_id;
size_t on_get_pending;
struct ll_entry** hash_table; // Хеш-таблица для поиска по id (если hash_size > 0)
size_t hash_size; // Размер хеш-таблицы
@@ -171,7 +181,7 @@ struct ll_queue {
#endif
};
/* ==================== Создание / уничтожение ==================== */
// ==================== Создание / уничтожение ====================
/**
* @brief Создаёт новую пустую очередь.
@@ -179,8 +189,8 @@ struct ll_queue {
* @param hash_size размер хеш-таблицы (0 = поиск по id отключён)
* @param index_offset смещение ключа индекса в data[] каждого entry (0 если hash_size=0)
* @param index_size размер ключа индекса (0 если hash_size=0)
* @param name имя очереди для отладки
* @return указатель на очередь или NULL при ошибке выделения памяти
* @param name заимствованное имя для отладки, должно жить до queue_free
* @return указатель на очередь или NULL при NULL ua / ошибке выделения памяти
*/
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_offset, uint16_t index_size, char* name);
@@ -190,42 +200,55 @@ struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_o
*
* @warning НЕ ОСВОБОЖДАЕТ элементы в очереди!
* Их нужно извлечь через queue_data_get() и освободить через queue_entry_free().
* Перед дренажем снять callback и отменить ожидания владельцев, затем для каждого entry освободить dgram и entry.
* queue_free отменяет свои отложенные уведомления и waiters; user_arg не освобождает. NULL допустим.
*/
void queue_free(struct ll_queue* q);
/* ==================== Конфигурация ==================== */
/* Аварийный сброс: освободить все entry/dgram, отменить waiters и отложенные уведомления без их вызова.
* Не выделяет память (кроме действий пользовательского dgram_free_fn). Очередь/индекс/обычный callback/пороги сохраняются.
* empty/on_get обработчики снимаются, callback приостановлен; владельцы заново ставят ожидания после восстановления.
* Вызывать в потоке ua; NULL допустим. Для продолжения автозабора нужен queue_resume_callback(). */
void queue_clear(struct ll_queue* q);
// ==================== Конфигурация ====================
/**
* @brief Устанавливает максимальное количество элементов в очереди.
* @param q очередь
* @param lim лимит (-1 = без ограничения)
* @param lim лимит (любое отрицательное значение = без ограничения)
*
* При превышении лимита новые элементы автоматически освобождаются.
*/
void queue_set_size_limit(struct ll_queue* q, int lim);
/* ==================== Автозабор элементов ==================== */
// ==================== Автозабор элементов ====================
/**
* @brief Устанавливает коллбэк для автоматического извлечения элементов.
* @param q очередь
* @param cbk_fn функция-коллбэк
* @param cbk_fn функция-коллбэк (NULL отключает автозабор)
* @param arg пользовательский аргумент
*
* Коллбэк вызывается автоматически при наличии элементов и разрешённом состоянии.
* Только заменяет функцию и аргумент: не запускает обработку, не снимает callback_suspended
* и не отменяет уже запланированный resume. Отложенный resume использует новые функцию и аргумент.
* Для запуска автозабора из уже заполненной очереди вызовите queue_resume_callback().
*/
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
/**
* @brief Возобновляет работу коллбэка после обработки элемента в следующем event loop.
* @brief Разрешает автозабор и планирует отложенный вызов коллбэка.
* @param q очередь
*
* @warning ОБЯЗАТЕЛЬНО вызывать после завершения обработки элемента в коллбэке.
* Иначе автозабор элементов навсегда остановится.
* Сразу снимает callback_suspended. Если есть элементы и callback, планирует uasync_call_soon,
* если resume ещё не запланирован. При исполнении повторно проверяет наличие элементов,
* callback и отсутствие приостановки. Последующий put в пустую очередь может вызвать callback
* синхронно, если callback_defer == 0.
* @note Вызывать после завершения обработки извлечённого элемента для продолжения автозабора.
*/
void queue_resume_callback(struct ll_queue* q);
/* ==================== Пороговое ожидание (backpressure) ==================== */
// ==================== Пороговое ожидание (backpressure) ====================
/**
* @brief Устанавливает общий порог освобождения очереди.
@@ -234,87 +257,128 @@ void queue_resume_callback(struct ll_queue* q);
* @param max_bytes максимальный объём данных (0 = не проверять)
*
* Порог — общий на всю очередь. Все waiters в связном списке используют этот порог.
* При достижении порога вызывается следующий waiter из списка (FIFO / round-robin).
* После успешного get/remove при выполнении порога вызывается один следующий waiter из FIFO-списка.
* Сам setter старые ожидания не проверяет. Байт-порог использует int_len, зафиксированный при put.
* По умолчанию: max_packets=0, max_bytes=0 (пустая очередь).
*/
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes);
// enable=0 — синхронный waiter (по умолчанию), !=0 — через uasync_call_soon.
// Deferred-уведомление не резервирует место и повторно порог не проверяет: очередь к исполнению может заполниться.
// Повторную регистрацию producer из его callback при свободной очереди откладывать, чтобы избежать рекурсии.
void queue_set_waiter_defer(struct ll_queue* q, int enable);
/**
* @brief Управляет способом вызова коллбэка автозабора при добавлении элемента.
* @param q очередь
* @param enable 0 — первый запуск коллбэка из queue_data_put* вызывается синхронно (по умолчанию);
* 1 — первый запуск откладывается через uasync_call_soon (очищает стек вызывающего).
*
* @note Влияет только на первый запуск из put. Повторные запуски (queue_resume_callback)
* всегда идут через uasync_call_soon.
*/
void queue_set_callback_defer(struct ll_queue* q, int enable);
/**
* @brief Устанавливает одноразовый коллбэк при опустошении очереди (count==0).
* @param q очередь
* @param cbk_fn коллбэк (NULL для отмены)
* @param arg пользовательский аргумент
*
* Коллбэк вызывается асинхронно (через uasync_call_soon) когда count становится 0.
* После вызова сбрасывается — для повторного использования нужно вызвать снова.
* Заменяет регистрацию и отменяет её прежний отложенный вызов. При count == 0 сразу планирует
* uasync_call_soon; иначе вызов планируется при опустошении через queue_data_get/queue_remove_data.
* При исполнении снова проверяет count: если очередь заполнилась, регистрация сохраняется
* до следующего опустошения. Функция и аргумент сбрасываются ДО вызова callback,
* поэтому внутри него можно зарегистрировать новое уведомление.
*/
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
/**
* @brief Устанавливает коллбэк вызываемый после каждого queue_data_get (deferred через call_soon).
* @brief Устанавливает коллбэк после каждого успешного queue_data_get (deferred через call_soon).
* @param q очередь
* @param cbk_fn коллбэк (NULL для отмены)
* @param arg пользовательский аргумент
*
* Уведомления подсчитываются, не сливаются в одно; remove их не создаёт.
* NULL отменяет отложенный вызов и обнуляет накопленные уведомления. Замена использует новые функцию/arg.
*/
void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
/**
* @brief Регистрирует ожидание освобождения очереди до общего порога.
* @param q очередь
* @param h указатель на handle (создаётся как `struct queue_waiter_handle h = {0}` или `h.internal = NULL`)
* @param h handle, полностью обнулённый перед первым использованием: `struct queue_waiter_handle h = {0}`
* @param callback функция, вызываемая при достижении порога
* @param arg аргумент коллбэка
* @return 1 — условие уже выполнено (callback вызван немедленно), 0 — зарегистрирован в списке, -1 — ошибка
* @return 1 — условие выполнено (callback вызван или отложен), 0 — ожидает порог, -1 — ошибка
*
* Если handle уже ожидает (h->internal != NULL) — старый waiter отменяется и создаётся новый.
* При корректных аргументах повторная регистрация в той же очереди отменяет прежнее ожидание.
* Если h->internal != NULL, перед переносом в другую очередь обязательно вызовите
* queue_waiter_cancel(old_q, h): очередь старого ожидания в handle не хранится.
* Уже отложенный callback (h->call_soon_id != NULL) отменяется по сохранённому h->defer_q,
* поэтому в этом состоянии handle можно передать другой очереди без отдельной отмены.
* При queue_free отменяются также уже запланированные уведомления.
*/
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg);
/**
* @brief Отменяет ожидание (удаляет из списка, если зарегистрирован).
* @param q очередь
* @param q очередь исходного ожидания (для h->internal); не NULL
* @param h указатель на handle
*
* Безопасно вызывать в любом состоянии: если waiter не зарегистрирован — ничего не делает.
* Отложенный callback отменяется через сохранённую h->defer_q. Если ожидания и отложенного
* callback нет — ничего не делает. При h->internal != NULL передавать другую очередь нельзя.
*/
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h);
/* ==================== Работа с данными ==================== */
// ==================== Работа с данными ====================
/**
* Общие правила владения для всех четырёх queue_data_put*:
* - При q == NULL или entry == NULL возвращается -1 без освобождения памяти.
* - Элемент должен быть извлечён из прежней очереди: owner, prev и next должны быть NULL.
* Повторная вставка связанного элемента приводит к abort().
* - При остальных отказах (лимит, неверный вид put или границы индекса) освобождаются
* сначала dgram через queue_dgram_free(), затем entry через queue_entry_free().
* - При успехе владение передаётся очереди/потребителю. Put может синхронно вызвать callback,
* который обработает и освободит entry и даже очередь до возврата из put.
* Использовать эти указатели после put можно только при гарантированном времени жизни.
*/
/**
* @brief Добавляет элемент в конец очереди (FIFO).
* @param q очередь
* @param entry элемент
* @param id идентификатор для поиска
* @return 0 — успех, -1 — превышен лимит (элемент освобождён)
* @param q очередь без хеш-таблицы (hash_size == 0)
* @param entry элемент; правила передачи владения см. выше
* @return 0 — добавлен, -1 — NULL-аргумент, переполнение или hash_size > 0
*/
int queue_data_put(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Добавляет элемент в начало очереди (LIFO, высокий приоритет).
* @param q очередь
* @param entry элемент
* @param id идентификатор
* @return 0 — успех, -1 — превышен лимит (элемент освобождён)
* @param q очередь без хеш-таблицы (hash_size == 0)
* @param entry элемент; правила передачи владения см. выше
* @return 0 — добавлен, -1 — NULL-аргумент, переполнение или hash_size > 0
*/
int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Добавляет элемент в конец очереди с хеш-индексом (использует index_offset/index_size из очереди).
* @param q очередь
* @param entry элемент
* @return 0 — успех, -1 — ошибка
* @param q очередь с настроенной хеш-таблицей (hash_size > 0, index_size > 0)
* @param entry элемент; ключ занимает index_size байт в data[] от index_offset и должен помещаться в entry->size
* @return 0 — добавлен, -1 — NULL-аргумент, переполнение или неверные границы/нулевой размер индекса
* @note Владение см. выше. При hash_size == 0 реализация может вернуть 0, добавив элемент без индексации;
* наличие хеш-таблицы — предусловие, а не гарантированная проверка с возвратом -1.
*/
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Добавляет элемент в начало очереди с хеш-индексом (использует index_offset/index_size из очереди).
* @param q очередь
* @param entry элемент
* @return 0 — успех, -1 — ошибка
* @param q очередь с настроенной хеш-таблицей (hash_size > 0, index_size > 0)
* @param entry элемент; ключ занимает index_size байт в data[] от index_offset и должен помещаться в entry->size
* @return 0 — добавлен, -1 — NULL-аргумент, переполнение или неверные границы/нулевой размер индекса
* @note Владение см. выше. При hash_size == 0 реализация может вернуть 0, добавив элемент без индексации;
* наличие хеш-таблицы — предусловие, а не гарантированная проверка с возвратом -1.
*/
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry);
@@ -336,35 +400,35 @@ struct ll_entry* queue_data_get(struct ll_queue* q);
*/
int queue_entry_count(struct ll_queue* q);
/* ==================== Управление памятью элементов ==================== */
// ==================== Управление памятью элементов ====================
// проверить правильность очереди (корректность цепочки, счетчики элементов и байтов)
int queue_check_consistency(struct ll_queue* q);
/**
* @brief Выделяет элемент с отдельным буфером dgram (malloc).
* @brief Выделяет элемент с отдельным буфером dgram через u_malloc (entry->len=0, memlen=len).
* @param len требуемый размер dgram
* @return элемент или NULL при ошибке
*/
struct ll_entry* ll_alloc_lldgram(uint16_t len);
/**
* @brief Создаёт элемент с пользовательским буфером data[] (malloc одним блоком).
* @param data_size размер data[]
* @brief Создаёт обнулённый элемент с пользовательским буфером data[] (u_malloc одним блоком).
* @param data_size размер data[], должен помещаться в uint16_t
* @return элемент или NULL при ошибке
*/
struct ll_entry* queue_entry_new(size_t data_size);
/**
* @brief Создаёт элемент из пула памяти.
* @param pool пул
* @return элемент или NULL, если пул пуст
* @param pool пул с object_size >= sizeof(struct ll_entry); разность размеров должна помещаться в uint16_t
* @return элемент или NULL при ошибке выделения; пустой кеш пула пополняется автоматически
*/
struct ll_entry* queue_entry_new_from_pool(struct memory_pool* pool);
/**
* @brief Освобождает структуру элемента (возвращает в пул или free).
* @param entry элемент
* @brief Освобождает структуру элемента (возвращает в пул или u_free).
* @param entry извлечённый из очереди элемент; NULL допустим
*
* @note НЕ освобождает dgram! Используйте queue_dgram_free() отдельно.
*/
@@ -373,10 +437,14 @@ void queue_entry_free(struct ll_entry* entry);
/**
* @brief Освобождает буфер dgram элемента.
* @param entry элемент
*
* При dgram != NULL вызывает dgram_free_fn, если она задана; иначе возвращает буфер в dgram_pool,
* если задан пул; иначе вызывает u_free. Затем сбрасывает dgram и len. Сам entry не освобождает.
* Внешнему буферу нужен подходящий dgram_free_fn.
*/
void queue_dgram_free(struct ll_entry* entry);
/* ==================== Поиск и удаление ==================== */
// ==================== Поиск и удаление ====================
/**
* @brief Находит элемент по ключу индекса (требуется hash_size > 0 при создании).
@@ -406,12 +474,12 @@ struct ll_entry* queue_find_next_by_index(struct ll_queue* q,
*/
int queue_remove_data(struct ll_queue* q, struct ll_entry* entry);
/* ==================== Утилиты ==================== */
// ==================== Утилиты ====================
/**
* @brief Возвращает суммарный объём данных в очереди.
* @param q очередь
* @return количество байт
* @return сумма int_len (снимки len при put); изменение len связанного entry счётчик не пересчитывает
*/
static inline size_t queue_total_bytes(struct ll_queue* q) {
return q->total_bytes;
-234
View File
@@ -1,234 +0,0 @@
# ll_queue — Двусвязная FIFO-очередь с автозабором и backpressure
## 1. Назначение
Двусвязная очередь элементов (FIFO) с автоматическим вызовом callback при появлении данных, пороговым ожиданием освобождения (backpressure), хеш-индексом для поиска и поддержкой пулов памяти. Используется как основной механизм передачи данных между компонентами внутри одного потока uasyc: TUN ↔ ETCP ↔ routing ↔ normalizer и т.д.
Работает **строго в одном потоке** (один uasyc). Для многопоточного доступа не предназначена.
## 2. Как пользоваться
### 2.1. Создание очереди
```c
// Без хеша (простая очередь)
struct ll_queue* q = queue_new(ua, 0, 0, 0, "my_queue");
// С хешем — для быстрого поиска по ключу внутри data[]
// hash_size=256, ключ лежит в data[] по смещению 0, размер ключа 4 байта
struct ll_queue* q = queue_new(ua, 256, 0, 4, "my_hash_queue");
```
### 2.2. Создание элемента
Три способа, по убыванию производительности:
```c
// 1. Из memory pool (самый быстрый)
struct ll_entry* e = queue_entry_new_from_pool(instance->pkt_pool);
// 2. Через malloc (data[] фиксированного размера одним блоком)
struct ll_entry* e = queue_entry_new(sizeof(struct my_data));
// 3. Элемент + отдельный буфер dgram (для переменного размера данных)
struct ll_entry* e = ll_alloc_lldgram(dgram_len);
memcpy(e->dgram, src_data, dgram_len);
e->len = dgram_len;
```
### 2.3. Запись в очередь (backpressure)
**Главное правило: не забиваем очередь!** Добавляем следующий элемент только когда очередь опустела до заданного порога. Для этого используется Пороговое ожидание:
```c
// 1. Настроить порог (например: ждать пока count<=0, т.е. очередь совсем пуста)
queue_set_threshold(q, 0, 0);
// 2. В структуре-производителе завести handle:
struct queue_waiter_handle my_waiter = {0};
// 3. При готовности отправить — зарегистрировать ожидание:
static void my_send_cb(struct ll_queue* q, void* arg) {
struct my_producer* p = (struct my_producer*)arg;
struct ll_entry* e = ... // создать элемент
e->len = data_len;
queue_data_put_with_index(q, e);
// Снова зарегистрироваться на следующую порцию
queue_waiter_wait(q, &p->waiter, my_send_cb, p);
}
// Первичная регистрация:
queue_waiter_wait(q, &my_waiter, my_send_cb, producer);
// 4. При деинициализации — отменить ожидание:
queue_waiter_cancel(q, &my_waiter);
```
### 2.4. Чтение из очереди (callback)
```c
// Установить callback — будет вызываться при появлении элементов
queue_set_callback(q, my_callback, my_data);
static void my_callback(struct ll_queue* q, void* arg) {
// 1. ИЗВЛЕЧЬ элемент
struct ll_entry* e = queue_data_get(q);
if (!e) { queue_resume_callback(q); return; }
// 2. ОБРАБОТАТЬ
if (e->dgram && e->len > 0) {
do_something(e->dgram, e->len);
queue_dgram_free(e); // сначала освободить dgram
}
queue_entry_free(e); // потом освободить сам entry
// 3. ОБЯЗАТЕЛЬНО возобновить callback
queue_resume_callback(q);
}
```
**Критически важно:** `queue_data_get()` автоматически приостанавливает callback-и (`callback_suspended = 1`). Без вызова `queue_resume_callback()` очередь **навсегда застрянет** — callback больше никогда не вызовется.
Во **всех ветках** (включая ранние return по ошибке) должен быть `queue_resume_callback(q)`:
```c
// ПРАВИЛЬНО:
if (!e) { queue_resume_callback(q); return; }
if (!e->dgram) { queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(q); return; }
// НЕПРАВИЛЬНО — очередь зависнет:
if (!e) return;
```
### 2.5. Поиск по индексу
```c
// Создать очередь с хешем (ключ 4 байта по смещению 0):
struct ll_queue* q = queue_new(ua, 256, 0, 4, "indexed_q");
// Добавлять элементы через _with_index (хеш вычисляется автоматически):
queue_data_put_with_index(q, entry);
// Искать:
uint32_t key = 42;
struct ll_entry* found = queue_find_data_by_index(q, &key);
// Если несколько элементов с одним ключом — продолжать поиск:
struct ll_entry* next = queue_find_next_by_index(q, &key, found);
```
### 2.6. Освобождение ресурсов
```c
// 1. Снять callback
queue_set_callback(q, NULL, NULL);
// 2. Отменить всех waiter-ов
queue_waiter_cancel(q, &my_waiter);
// 3. Извлечь и освободить ВСЕ оставшиеся элементы
struct ll_entry* e;
while ((e = queue_data_get(q)) != NULL) {
queue_dgram_free(e);
queue_entry_free(e);
}
// После извлечения всех элементов resume не нужен — callback уже снят.
// 4. Освободить очередь
queue_free(q);
```
**Важно:** `queue_free()` **не освобождает элементы**. Их нужно извлечь через `queue_data_get()` и освободить вручную.
### 2.7. Приоритетная вставка (LIFO)
```c
// Вставить в начало очереди (высокий приоритет):
queue_data_put_first(q, entry);
queue_data_put_first_with_index(q, entry);
```
### 2.8. Удаление из середины
```c
queue_remove_data(q, entry); // удаляет из очереди, НЕ освобождает память
queue_dgram_free(entry); // освободить dgram отдельно
queue_entry_free(entry); // освободить entry отдельно
```
После `queue_remove_data()` элемент больше не принадлежит очереди — вызывающий сам отвечает за его освобождение.
### 2.9. Callback при опустошении
```c
static void on_empty(struct ll_queue* q, void* arg) {
// Очередь стала пустой (count==0)
// Вызывается однократно, после вызова сбрасывается
}
queue_set_empty_callback(q, on_empty, my_data);
```
## 3. API
### Создание / уничтожение
| Функция | Назначение |
|---------|-----------|
| `queue_new(ua, hash_size, index_offset, index_size, name)` | Создать очередь. `hash_size=0` — без хеша. `ua` обязателен (для uasync_call_soon). |
| `queue_free(q)` | Освободить очередь (НЕ элементы — их надо извлечь и освободить отдельно). |
### Создание / освобождение элементов
| Функция | Назначение |
|---------|-----------|
| `queue_entry_new(data_size)` | Выделить entry с data[] через malloc. |
| `queue_entry_new_from_pool(pool)` | Выделить entry из memory pool. |
| `ll_alloc_lldgram(len)` | Выделить entry + отдельный буфер dgram (malloc). |
| `queue_entry_free(entry)` | Освободить entry (возврат в pool или free). НЕ освобождает dgram! |
| `queue_dgram_free(entry)` | Освободить dgram (через dgram_free_fn, pool или free). |
### Чтение (callback)
| Функция | Назначение |
|---------|-----------|
| `queue_set_callback(q, cbk_fn, arg)` | Установить callback автозабора. Вызывается при наличии элементов. |
| `queue_data_get(q)` | Извлечь элемент из головы. Приостанавливает callback до `queue_resume_callback()`. |
| `queue_resume_callback(q)` | **Обязательно** вызвать после обработки элемента. Планирует отложенный вызов callback через uasync_call_soon. |
### Запись
| Функция | Назначение |
|---------|-----------|
| `queue_data_put(q, entry)` | Добавить в конец (FIFO). Для очередей БЕЗ хеша. |
| `queue_data_put_with_index(q, entry)` | Добавить в конец с хеш-индексом. |
| `queue_data_put_first(q, entry)` | Добавить в начало (LIFO, высокий приоритет). |
| `queue_data_put_first_with_index(q, entry)` | Добавить в начало с хеш-индексом. |
### Backpressure
| Функция | Назначение |
|---------|-----------|
| `queue_set_threshold(q, max_packets, max_bytes)` | Задать общий порог. Когда `count <= max_packets` И `total_bytes <= max_bytes`, waiter-ы пробуждаются (FIFO). |
| `queue_waiter_wait(q, h, callback, arg)` | Зарегистрировать ожидание. Возвращает 1 (уже выполнено), 0 (в очереди), -1 (ошибка). |
| `queue_waiter_cancel(q, h)` | Отменить ожидание. Безопасно вызывать в любом состоянии. |
| `queue_set_waiter_defer(q, enable)` | Отложенный вызов waiter-ов (через uasync_call_soon). |
| `queue_set_empty_callback(q, cbk_fn, arg)` | Одноразовый callback при count==0. |
| `queue_set_on_get(q, cbk_fn, arg)` | Callback после каждого queue_data_get (deferred). |
### Поиск / удаление
| Функция | Назначение |
|---------|-----------|
| `queue_find_data_by_index(q, index_key)` | Найти по ключу (требуется hash_size>0). Размер ключа = q->index_size. |
| `queue_find_next_by_index(q, index_key, prev_entry)` | Следующий элемент с тем же ключом. |
| `queue_remove_data(q, entry)` | Удалить из очереди (память НЕ освобождает). |
### Утилиты
| Функция | Назначение |
|---------|-----------|
| `queue_entry_count(q)` | Текущее количество элементов. |
| `queue_total_bytes(q)` | Суммарный объём данных (сумма len). |
| `queue_set_size_limit(q, lim)` | Ограничение на количество элементов (при превышении новые удаляются). |
| `queue_check_consistency(q)` | Проверка целостности (счётчики, циклы, prev/next). Только для отладки. |
+32 -10
View File
@@ -13,11 +13,15 @@
#define CANARY 0xDEADBEEF
#define PADDING_FILL 0xAA
#define METADATA_SIZE 128
#define ALLOCATION_OVERHEAD (12 + (size_t)2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE)
#define POINTER_SIZE sizeof(void*)
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE * 2)
#define PREV_OFFSET LOCATION_MAX
#define NEXT_OFFSET (LOCATION_MAX + POINTER_SIZE)
// Порог детекта аномально больших аллокаций (гигантские mmap-резервации, напр. VSZ ~4G)
#define LARGE_ALLOC_LOG_THRESHOLD (8u * 1024u * 1024u) // 8 МБ
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
#include <execinfo.h>
#include <pthread.h>
@@ -86,7 +90,7 @@ void u_check(void* ptr, const char* text, const char* location) {
uint8_t* prefix_start = base + METADATA_SIZE;
uint32_t* pre_canary = (uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE);
uint32_t size = *(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 4);
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE;
size_t total = (size_t)size + ALLOCATION_OVERHEAD;
uint8_t* user = (uint8_t*)ptr;
bool error = false;
for (size_t i = 0; i < BOUNDARY_CHECK_SIZE; i++) {
@@ -106,14 +110,15 @@ void u_check(void* ptr, const char* text, const char* location) {
error = true;
}
}
uint32_t* post_canary = (uint32_t*)(user + size);
if (*post_canary != CANARY) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "%s: %s: overflow canary corrupted at %p (expected 0x%X, got 0x%X)", location, text, ptr, CANARY, *post_canary);
uint32_t post_canary;
memcpy(&post_canary, user + size, sizeof(post_canary));
if (post_canary != CANARY) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "%s: %s: overflow canary corrupted at %p (expected 0x%X, got 0x%X)", location, text, ptr, CANARY, post_canary);
error = true;
}
if (error) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "%s: %s: Allocated at: %s", location, text, alloc_loc);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "%s: %s: Memory block size (user): %u bytes, total allocated: %u bytes", location, text, size, total);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "%s: %s: Memory block size (user): %u bytes, total allocated: %zu bytes", location, text, size, total);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "%s: %s: Full memory dump at base %p:", location, text, base);
hex_dump(base, total);
exit(EXIT_FAILURE);
@@ -121,9 +126,21 @@ void u_check(void* ptr, const char* text, const char* location) {
}
void* u_malloc_impl(uint32_t size, const char* location) {
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE;
size_t total = size;
if (total > SIZE_MAX - ALLOCATION_OVERHEAD) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "MALLOC OVERFLOW: %u user bytes + %zu overhead bytes at %s", size, ALLOCATION_OVERHEAD, location);
return NULL;
}
if (size >= LARGE_ALLOC_LOG_THRESHOLD) {
DEBUG_WARN(DEBUG_CATEGORY_SYS, "LARGE ALLOC: %u bytes (%.1f MB) at %s",
size, (double)size / (1024.0 * 1024.0), location);
}
total += ALLOCATION_OVERHEAD;
uint8_t* base = (uint8_t*)malloc(total);
if (!base && size > 0) return NULL;
if (!base) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "MALLOC FAILED: %u user bytes, %zu total bytes at %s", size, total, location);
return NULL;
}
memset(base, PADDING_FILL, total);
// Set metadata
size_t loc_len = strlen(location);
@@ -134,7 +151,8 @@ void* u_malloc_impl(uint32_t size, const char* location) {
uint8_t* prefix_start = base + METADATA_SIZE;
*(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE) = CANARY;
*(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 4) = size;
*(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 8 + size) = CANARY;
const uint32_t post_canary = CANARY; /* размер пользователя не обязан быть кратен выравниванию uint32_t */
memcpy(prefix_start + BOUNDARY_CHECK_SIZE + 8 + size, &post_canary, sizeof(post_canary));
// Add to list (thread-safe)
LOCK();
void** prev_ptr = (void**)(base + PREV_OFFSET);
@@ -151,6 +169,10 @@ void* u_malloc_impl(uint32_t size, const char* location) {
}
void* u_calloc_impl(uint32_t nmemb, uint32_t size, const char* location) {
if (nmemb != 0 && size > UINT32_MAX / nmemb) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "CALLOC OVERFLOW: %u * %u (wraps uint32) at %s", nmemb, size, location);
return NULL;
}
uint32_t total_size = nmemb * size;
void* ptr = u_malloc_impl(total_size, location);
if (ptr) memset(ptr, 0, total_size);
@@ -196,7 +218,7 @@ void u_free_impl(void* ptr, const char* location) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Base: %p", (void*)base);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Allocated at: %s", alloc_loc);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "User size: %u bytes", size);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Total allocated: %u bytes", size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Total allocated: %zu bytes", (size_t)size + ALLOCATION_OVERHEAD);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Active allocs: %zu", active_count);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "--- Metadata dump at %p (%u bytes) ---", (void*)base, (unsigned)(METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8));
hex_dump(base, METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8);
@@ -218,7 +240,7 @@ void u_free_impl(void* ptr, const char* location) {
allocated_count--;
frees_count++;
UNLOCK();
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE;
size_t total = (size_t)size + ALLOCATION_OVERHEAD;
memset(base, 0, total);
free(base);
}
+20 -2
View File
@@ -1,6 +1,12 @@
/**
* Memory management layer
* Provides wrappers for malloc/realloc/calloc/free with error handling
* mem — выделение памяти с защитными полями, местом аллокации и учётом живых блоков.
* Использовать u_malloc/u_calloc/u_realloc/u_strdup и парный u_free; обычный free к этим блокам неприменим.
* Макросы передают LOCATION автоматически. OOM возвращает NULL; обнаруженное повреждение завершает процесс.
* realloc живого блока при ошибке сохраняет его, при size=0 освобождает; NULL обрабатывается как malloc.
* Размеры аргументов _impl — uint32_t; до вызова проверить, что исходные size_t помещаются в этот диапазон.
* Список аллокаций защищён мьютексом; доступ к одному блоку и чтение счётчика согласует вызывающий.
* Защитные поля проверяются для живых блоков; обращение к уже освобождённому/чужому указателю недопустимо.
* Отчёт u_report_unfreed_blocks вызывать после остановки потоков, меняющих список аллокаций.
*/
#ifndef MEM_H
#define MEM_H
@@ -12,18 +18,30 @@ extern "C" {
#define XSTR(s) STR(s)
#define STR(s) #s
#define LOCATION __FILE__ ":" XSTR(__LINE__)
// Проверяет canary/padding живого блока; text/location — строки диагностики. NULL ptr допустим.
// Обнаруженное повреждение логируется в SYS и завершает процесс через exit(EXIT_FAILURE).
void u_check(void* ptr, const char* text, const char* location);
#define u_malloc(size) u_malloc_impl(size, LOCATION)
#define u_calloc(nmemb, size) u_calloc_impl(nmemb, size, LOCATION)
#define u_realloc(ptr, size) u_realloc_impl(ptr, size, LOCATION)
#define u_free(ptr) u_free_impl(ptr, LOCATION)
#define u_strdup(s) u_strdup_impl(s, LOCATION)
// Выделяет size байт с метаданными; NULL при OOM/переполнении полного размера. size=0 тоже создаёт учтённый блок.
// location — обязательная строка места вызова, копируется в метаданные; payload не обнуляется.
void* u_malloc_impl(uint32_t size, const char* location);
// Выделяет и обнуляет nmemb*size байт; NULL при произведении > UINT32_MAX или ошибке malloc.
void* u_calloc_impl(uint32_t nmemb, uint32_t size, const char* location);
// NULL ptr → malloc(size), включая size=0. Для живого ptr: size=0 → free и NULL;
// иначе новый блок с min(старый размер, size) байтами данных. При OOM возвращает NULL, старый блок сохранён.
// Результат сохранять временно: tmp = u_realloc(ptr, size); if (tmp) ptr = tmp; (при size > 0).
void* u_realloc_impl(void* ptr, uint32_t size, const char* location);
// Проверяет и освобождает живой блок из этих обёрток; NULL допустим. Обычный free к нему неприменим.
void u_free_impl(void* ptr, const char* location);
// Копирует ASCIIZ-строку (strlen(s)+1 должно помещаться в uint32_t); NULL при NULL s/OOM. Освобождать u_free.
char* u_strdup_impl(const char* s, const char* location);
// После остановки изменяющих список потоков: статистика и дампы живых блоков; сами блоки не освобождает.
void u_report_unfreed_blocks(void);
// Число живых блоков, чтение без блокировки; параллельные аллокации/освобождения нужно исключить извне.
size_t u_get_allocated_count(void);
#ifdef __cplusplus
}
-48
View File
@@ -1,48 +0,0 @@
# mem (Memory Wrappers с отслеживанием утечек)
## 1. Назначение
Обёртки над стандартными `malloc`/`calloc`/`realloc`/`free`/`strdup` с детекцией:
- **Buffer overflow/underflow** — каждый блок окружён канарейками (`0xDEADBEEF`) и padding-областями, которые проверяются при `u_free` и `u_realloc`. При повреждении — подробный дамп блока и аварийное завершение.
- **Double free** — двусвязный список всех живых аллокаций; повторное освобождение детектируется по отсутствию блока в списке.
- **Утечки памяти** — `u_report_unfreed_blocks()` при завершении выводит все незакрытые блоки с местом аллокации (`файл:строка`), размером и hex-дампом данных.
Функции потокобезопасны (мьютекс/критическая секция).
## 2. Как пользоваться
```c
#include "mem.h"
// Вместо malloc/calloc/realloc/free/strdup используй соответствующие макросы:
void* buf = u_malloc(data_size);
char* str = u_strdup("hello");
buf = u_realloc(buf, new_size);
u_free(buf);
u_free(str);
// В конце main (перед return) — отчёт о незакрытых блоках:
u_report_unfreed_blocks();
// Текущее число живых аллокаций (для диагностики):
size_t count = u_get_allocated_count();
```
**Важно:**
- Макросы (`u_malloc`, `u_calloc`, `u_free`, и т.д.) автоматически подставляют `__FILE__:__LINE__` через `LOCATION` — это нужно для диагностики утечек и повреждений. Напрямую вызывай `_impl`-функции только если нужно явно передать location.
- `u_realloc(NULL, size)` эквивалентен `u_malloc(size)`, `u_realloc(ptr, 0)` эквивалентен `u_free(ptr)`.
- Потокобезопасность гарантирована, но аллокации с метаданными заметно дороже обычного `malloc` — используй `memory_pool` для hot-path объектов.
- При обнаружении повреждения (overflow/underflow/double-free) программа завершается через `exit(EXIT_FAILURE)` после вывода отладочной информации.
## 3. API
| Функция/макрос | Описание |
|---|---|
| `u_malloc(size)` | Аналог `malloc` с boundary check и трекингом |
| `u_calloc(nmemb, size)` | Аналог `calloc` (выделяет + обнуляет) |
| `u_realloc(ptr, size)` | Аналог `realloc`, с проверкой целостности старого блока |
| `u_free(ptr)` | Освобождение с проверкой canary, double-free и удалением из списка |
| `u_strdup(s)` | Аналог `strdup` через u_malloc |
| `u_report_unfreed_blocks()` | Выводит все незакрытые блоки с дампом; вызывается при shutdown |
| `u_get_allocated_count()` | Возвращает количество живых аллокаций |
| `u_check(ptr, text, location)` | Ручная проверка целостности блока (canary + padding) |
+26 -14
View File
@@ -5,47 +5,59 @@
#include "memory_pool.h"
#include "mem.h"
// Layout: [user_data: object_size] [canary: 4 bytes] [alloc_loc: pointer] [counter: 1 byte]
// Layout: [user_data] [canary: 4] [alloc_loc: pointer] [free_loc: pointer] [counter: 1]
#define POOL_CANARY_OFF (pool->object_size)
#define POOL_LOC_OFF (pool->object_size + 4)
#define POOL_COUNTER_OFF (pool->object_size + 4 + sizeof(const char*))
#define POOL_ALLOC_SIZE (pool->object_size + 4 + sizeof(const char*) + 1)
#define POOL_FREE_LOC_OFF (pool->object_size + 4 + sizeof(const char*))
#define POOL_COUNTER_OFF (pool->object_size + 4 + 2 * sizeof(const char*))
#define POOL_ALLOC_SIZE (pool->object_size + 4 + 2 * sizeof(const char*) + 1)
#define POOL_CANARY_VAL 0xDEADBEAF
static void pool_init_tags(struct memory_pool* pool, void* obj, const char* location)
{
uint32_t* canary = (uint32_t*)((uint8_t*)obj + POOL_CANARY_OFF);
const char** loc = (const char**)((uint8_t*)obj + POOL_LOC_OFF);
const uint32_t canary = POOL_CANARY_VAL;
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
*canary = POOL_CANARY_VAL;
*loc = location;
/* Метаданные следуют за payload произвольного размера и могут быть невыровненными. */
memcpy((uint8_t*)obj + POOL_CANARY_OFF, &canary, sizeof(canary));
memcpy((uint8_t*)obj + POOL_LOC_OFF, &location, sizeof(location));
const char* free_loc = NULL;
memcpy((uint8_t*)obj + POOL_FREE_LOC_OFF, &free_loc, sizeof(free_loc));
*counter = 1;
}
static void pool_check_and_clear_tags(struct memory_pool* pool, void* obj, const char* location)
{
uint32_t* canary = (uint32_t*)((uint8_t*)obj + POOL_CANARY_OFF);
const char** loc = (const char**)((uint8_t*)obj + POOL_LOC_OFF);
uint32_t canary;
const char *loc, *free_loc;
memcpy(&canary, (uint8_t*)obj + POOL_CANARY_OFF, sizeof(canary));
memcpy(&loc, (uint8_t*)obj + POOL_LOC_OFF, sizeof(loc));
memcpy(&free_loc, (uint8_t*)obj + POOL_FREE_LOC_OFF, sizeof(free_loc));
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
if (*canary != POOL_CANARY_VAL) {
if (canary != POOL_CANARY_VAL) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "pool_free BUFFER OVERFLOW pool=%p name=%s sz=%zu allocs=%zu reuse=%zu free=%d",
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count, pool->free_count);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, " obj=%p alloc=%s free=%s canary=0x%08x expected=0x%08x",
obj, *loc ? *loc : "(null)", location, *canary, POOL_CANARY_VAL);
/* Повреждённый хвост может содержать битые указатели; их нельзя читать как строки. */
DEBUG_ERROR(DEBUG_CATEGORY_SYS, " obj=%p alloc_loc_ptr=%p free_loc_ptr=%p free_attempt=%s canary=0x%08x expected=0x%08x",
obj, (void*)loc, (void*)free_loc, location, canary, POOL_CANARY_VAL);
if (pool->object_size)
log_dump(DEBUG_LEVEL_ERROR, DEBUG_CATEGORY_SYS, " user_data", obj, pool->object_size);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "pool_free: corruption logged, stopping thread permanently obj=%p", obj);
volatile int _halt = 1;
while (_halt) {}
}
if (*counter == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "pool_free DOUBLE FREE pool=%p name=%s sz=%zu allocs=%zu reuse=%zu alloc=%s free=%s",
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "pool_free DOUBLE FREE pool=%p name=%s sz=%zu allocs=%zu reuse=%zu obj=%p",
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count,
*loc ? *loc : "(null)", location);
obj);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, " alloc=%s first_free=%s repeated_free=%s",
loc ? loc : "(null)", free_loc ? free_loc : "(null)", location);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "pool_free: corruption logged, stopping thread permanently obj=%p", obj);
volatile int _halt = 1;
while (_halt) {}
}
*counter = 0;
memcpy((uint8_t*)obj + POOL_FREE_LOC_OFF, &location, sizeof(location));
}
static size_t g_total_free = 0;
+20 -6
View File
@@ -1,4 +1,13 @@
// memory_pool.h
/* memory_pool — повторное использование блоков фиксированного размера в одном потоке.
* init создаёт пустой пул; alloc выделяет блок по требованию или берёт свободный и обнуляет payload.
* free проверяет защитный хвост и возвращает не более MEMORY_POOL_MAX_FREE блоков в кеш.
* object_size должен вмещать void*: свободный блок хранит next в начале payload.
* Перед destroy вернуть все выданные блоки: destroy освобождает только кеш и сам пул.
* name заимствован до destroy; блок возвращать только своему пулу, внутренней синхронизации нет. */
/* Хвост блока хранит canary, места alloc/free и метку занятости. Повреждение или повторный возврат
* кешированного блока логируется в SYS и навсегда останавливает текущий поток циклом диагностики.
* Пример для очередей: init(sizeof(struct ll_entry) + inline_size, name), затем queue_entry_new_from_pool.
* Объекты, уже переданные в u_free при переполнении кеша, больше нельзя проверять или возвращать. */
#ifndef MEMORY_POOL_H
#define MEMORY_POOL_H
@@ -20,26 +29,31 @@ struct memory_pool {
void* free_head; // Голова linked list свободных блоков (next хранится в начале каждого блока)
int free_count; // Количество свободных блоков
size_t object_size; // Размер объектов в пуле (пользовательские данные)
size_t allocations; // Общее количество аллокаций (включая новые malloc)
size_t allocations; // Попытки выделить новый блок, включая неудачные; выдачи из кеша не учитываются
size_t reuse_count; // Количество повторных использований из пула
uint8_t alloc_tag_counter; // монотонный счётчик для детекции double-free (1 байт)
uint8_t alloc_tag_counter; // Не используется; init устанавливает 1. Double-free проверяется по метке в хвосте блока
const char* name; // Имя пула для диагностики (напр. "data_pool", "pkt_pool")
};
// сам пул:
// если используем с элементами ll_queue то не забываем что к object_size надо прибавить sizeof(struct ll_entry)
// Создаёт пустой пул: object_size >= sizeof(void*), name живёт до destroy; NULL при OOM.
// Для ll_queue размер включает sizeof(struct ll_entry) и пользовательский data[].
struct memory_pool* memory_pool_init(size_t object_size, const char* name);
// Освобождает кеш и пул, но не выданные объекты; NULL допустим. Все владельцы должны предварительно вернуть блоки.
void memory_pool_destroy(struct memory_pool* pool);
// элементы пула:
// Выдаёт обнулённый payload из кеша или нового блока; NULL при NULL pool/OOM. location — место вызова в хвосте.
void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location);
// Возвращает свой живой obj в кеш или u_free, если кеш заполнен; NULL pool/obj ничего не делает.
void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* location);
// Копирует счётчики попыток новых аллокаций и выдач из кеша; любой выход может быть NULL, NULL pool ничего не меняет.
void memory_pool_get_stats(struct memory_pool* pool, size_t* allocations, size_t* reuse_count);
// Общее число кешированных блоков всех пулов; глобальный счётчик не синхронизирован.
size_t memory_pool_get_total_free_blocks(void);
#define memory_pool_alloc(pool) memory_pool_alloc_impl(pool, PLOCATION)
#define memory_pool_free(pool, obj) memory_pool_free_impl(pool, obj, PLOCATION)
/* Проверяет присутствие в кеше свободных блоков; не определяет состояние уже освобождённого через u_free блока. */
int memory_pool_is_freed(struct memory_pool* pool, void* obj);
-57
View File
@@ -1,57 +0,0 @@
# memory_pool (Object Pool Allocator)
## 1. Назначение
Быстрый аллокатор объектов фиксированного размера. Вместо постоянных `malloc`/`free` хранит до 64 освобождённых объектов в linked list для повторного использования. Применяется для hot-path объектов, которые часто создаются и уничтожаются: пакеты, фрагменты, ACK-пакеты, структуры маршрутизации, TCP-сегменты lwIP и т.д.
Встроенная защита:
- **Buffer overflow** — канарейка `0xDEADBEAF` после пользовательских данных проверяется при освобождении. При повреждении — бесконечный цикл (hang) для отладки.
- **Double free** — однобайтный счётчик в метаданных; повторное освобождение детектируется и вызывает hang.
- Вся диагностика пишется через `DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, ...)`.
Не потокобезопасен — каждый пул используется из одного потока (обычно в рамках одного u_async event loop).
## 2. Как пользоваться
```c
#include "memory_pool.h"
// 1. Создать пул (обычно в init-функции инстанса):
struct memory_pool* pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM), "pkt_pool");
// 2. Выделить объект (если есть свободный — вернёт из пула, иначе u_calloc):
struct ETCP_DGRAM* dgram = memory_pool_alloc(pkt_pool);
// 3. Освободить объект (возвращается в пул, если там меньше 64 блоков):
memory_pool_free(pkt_pool, dgram);
// 4. Получить статистику:
size_t allocs, reuse;
memory_pool_get_stats(pkt_pool, &allocs, &reuse);
// reuse много → эффективно; reuse мало → пул слишком мал для нагрузки
// 5. Проверить, в пуле ли объект (0 — не в пуле, 1 — в пуле):
if (memory_pool_is_freed(pkt_pool, obj)) { /* уже освобождён */ }
// 6. Уничтожить пул (при shutdown):
memory_pool_destroy(pkt_pool);
// реально освобождает все кэшированные блоки через u_free() и сам пул.
```
**Важно:**
- Если объекты пула используются как элементы `ll_queue`, в `object_size` нужно закладывать `sizeof(struct ll_entry)`. Например: `memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt), "pkt_pool")`.
- `memory_pool_alloc` обнуляет объект перед возвратом (защита от утечки старых данных).
- При заполнении пула (64 свободных блока) лишние `memory_pool_free` реально вызывают `u_free` — блок не кэшируется.
- `memory_pool_destroy` проходит по всем свободным блокам и вызывает `u_free` для каждого, затем освобождает сам `struct memory_pool`.
## 3. API
| Функция/макрос | Описание |
|---|---|
| `memory_pool_init(object_size, name)` | Создаёт пул для объектов заданного размера. `name` — для диагностики. |
| `memory_pool_alloc(pool)` | Выделяет объект: из кэша (если есть) или через `u_calloc`. Обнуляет перед возвратом. |
| `memory_pool_free(pool, obj)` | Возвращает объект в пул (до 64) или вызывает `u_free`. Проверяет canary и double-free. |
| `memory_pool_destroy(pool)` | Уничтожает пул: освобождает все кэшированные блоки и сам пул. |
| `memory_pool_get_stats(pool, &allocs, &reuse)` | Статистика: общее число аллокаций и число повторных использований из кэша. |
| `memory_pool_get_total_free_blocks()` | Глобальный счётчик свободных блоков во всех пулах (для диагностики). |
| `memory_pool_is_freed(pool, obj)` | Проверяет, находится ли объект в свободном списке пула (уже освобождён). |
+206 -48
View File
@@ -6780,7 +6780,9 @@ typedef enum
ma_device_notification_type_rerouted,
ma_device_notification_type_interruption_began,
ma_device_notification_type_interruption_ended,
ma_device_notification_type_unlocked
ma_device_notification_type_unlocked,
ma_device_notification_type_capture_overrun, /* uTun: подтверждённый backend разрыв входного PCM. */
ma_device_notification_type_playback_underrun /* uTun: backend исчерпал буфер воспроизведения. */
} ma_device_notification_type;
typedef struct
@@ -7543,11 +7545,13 @@ struct ma_context
ma_proc pa_context_disconnect;
ma_proc pa_context_set_state_callback;
ma_proc pa_context_get_state;
ma_proc pa_context_errno;
ma_proc pa_context_get_sink_info_list;
ma_proc pa_context_get_source_info_list;
ma_proc pa_context_get_sink_info_by_name;
ma_proc pa_context_get_source_info_by_name;
ma_proc pa_operation_unref;
ma_proc pa_operation_cancel;
ma_proc pa_operation_get_state;
ma_proc pa_channel_map_init_extend;
ma_proc pa_channel_map_valid;
@@ -7563,6 +7567,7 @@ struct ma_context
ma_proc pa_stream_get_buffer_attr;
ma_proc pa_stream_set_buffer_attr;
ma_proc pa_stream_get_device_name;
ma_proc pa_stream_get_latency;
ma_proc pa_stream_set_write_callback;
ma_proc pa_stream_set_read_callback;
ma_proc pa_stream_set_suspended_callback;
@@ -7574,6 +7579,7 @@ struct ma_context
ma_proc pa_stream_cork;
ma_proc pa_stream_trigger;
ma_proc pa_stream_begin_write;
ma_proc pa_stream_cancel_write;
ma_proc pa_stream_write;
ma_proc pa_stream_peek;
ma_proc pa_stream_drop;
@@ -11553,6 +11559,17 @@ IMPLEMENTATION
#ifndef miniaudio_c
#define miniaudio_c
/* uTun: необязательные наблюдатели, без изменения ABI и поведения аудиотракта. */
#ifndef MA_DIAGNOSTIC_EVENT
#define MA_DIAGNOSTIC_EVENT(device, kind, stage, value, detail) ((void)0)
#endif
#ifndef MA_DIAGNOSTIC_DEVICE_INIT
#define MA_DIAGNOSTIC_DEVICE_INIT(device) ((void)0)
#endif
#ifndef MA_DIAGNOSTIC_LOG
#define MA_DIAGNOSTIC_LOG(log, level, message) ((void)0)
#endif
#include <assert.h>
#include <limits.h> /* For INT_MAX */
#include <math.h> /* sin(), etc. */
@@ -13752,6 +13769,7 @@ MA_API ma_result ma_log_post(ma_log* pLog, ma_uint32 level, const char* pMessage
return MA_INVALID_ARGS;
}
MA_DIAGNOSTIC_LOG(pLog, level, pMessage);
ma_log_lock(pLog);
{
ma_uint32 iLog;
@@ -20200,6 +20218,14 @@ static void ma_device__on_notification(ma_device_notification notification)
{
MA_ASSERT(notification.pDevice != NULL);
if (notification.type == ma_device_notification_type_capture_overrun) {
MA_DIAGNOSTIC_EVENT(notification.pDevice, AUDIO_DIAG_BACKEND_OVERFLOW, AUDIO_DIAG_READ, 1, 0);
} else if (notification.type == ma_device_notification_type_playback_underrun) {
MA_DIAGNOSTIC_EVENT(notification.pDevice, AUDIO_DIAG_BACKEND_UNDERRUN, AUDIO_DIAG_WRITE, 1, 0);
} else {
MA_DIAGNOSTIC_EVENT(notification.pDevice, AUDIO_DIAG_NOTIFY, -1, notification.type, ma_device_get_state(notification.pDevice));
}
if (notification.pDevice->onNotification != NULL) {
notification.pDevice->onNotification(&notification);
}
@@ -20360,6 +20386,7 @@ static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut
{
float masterVolumeFactor;
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_CALLBACK, frameCount, (pFramesIn != NULL) | ((pFramesOut != NULL) << 1));
ma_device_get_master_volume(pDevice, &masterVolumeFactor); /* Use ma_device_get_master_volume() to ensure the volume is loaded atomically. */
if (pDevice->onData) {
@@ -20407,6 +20434,7 @@ static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut
ma_silence_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels);
}
}
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_CALLBACK, frameCount, 0);
}
@@ -20590,6 +20618,7 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m
if (framesToProcessInClientFormat == 0) {
if (ma_pcm_rb_pointer_distance(pRB) == (ma_int32)ma_pcm_rb_get_subbuffer_size(pRB)) {
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_CAPTURE_DROP, AUDIO_DIAG_READ, framesToProcessInDeviceFormat, ma_pcm_rb_available_read(pRB));
break; /* Overrun. Not enough room in the ring buffer for input frame. Excess frames are dropped. */
}
}
@@ -20625,6 +20654,7 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice,
ma_result result;
ma_uint8 silentInputFrames[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
ma_uint32 totalFramesReadOut = 0;
ma_silence_pcm_frames(pFramesInInternalFormat, frameCount, pDevice->playback.internalFormat, pDevice->playback.internalChannels);
MA_ASSERT(pDevice != NULL);
MA_ASSERT(frameCount > 0);
@@ -20636,6 +20666,7 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice,
Sitting in the ring buffer should be captured data from the capture callback in external format. If there's not enough data in there for
the whole frameCount frames we just use silence instead for the input data.
*/
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_DUPLEX, frameCount, ma_pcm_rb_available_read(pRB));
MA_ZERO_MEMORY(silentInputFrames, sizeof(silentInputFrames));
while (totalFramesReadOut < frameCount && ma_device_is_started(pDevice)) {
@@ -20646,8 +20677,12 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice,
if (pDevice->playback.inputCacheRemaining > 0) {
ma_uint64 framesConvertedIn = pDevice->playback.inputCacheRemaining;
ma_uint64 framesConvertedOut = (frameCount - totalFramesReadOut);
ma_data_converter_process_pcm_frames(&pDevice->playback.converter, ma_offset_pcm_frames_ptr(pDevice->playback.pInputCache, pDevice->playback.inputCacheConsumed, pDevice->playback.format, pDevice->playback.channels), &framesConvertedIn, pFramesInInternalFormat, &framesConvertedOut);
result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, ma_offset_pcm_frames_ptr(pDevice->playback.pInputCache, pDevice->playback.inputCacheConsumed, pDevice->playback.format, pDevice->playback.channels), &framesConvertedIn, pFramesInInternalFormat, &framesConvertedOut);
if (result != MA_SUCCESS || (framesConvertedIn == 0 && framesConvertedOut == 0)) {
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_DUPLEX, 1, result);
break;
}
pDevice->playback.inputCacheConsumed += framesConvertedIn;
pDevice->playback.inputCacheRemaining -= framesConvertedIn;
@@ -20662,30 +20697,34 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice,
inputFrameCount = (ma_uint32)pDevice->playback.inputCacheCap;
result = ma_pcm_rb_acquire_read(pRB, &inputFrameCount, &pInputFrames);
if (result == MA_SUCCESS) {
if (inputFrameCount > 0) {
ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, pInputFrames, inputFrameCount);
} else {
if (ma_pcm_rb_pointer_distance(pRB) == 0) {
break; /* Underrun. */
}
if (result != MA_SUCCESS) {
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_DUPLEX, 1, result);
break;
}
if (inputFrameCount > 0) {
ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, pInputFrames, inputFrameCount);
result = ma_pcm_rb_commit_read(pRB, inputFrameCount);
if (result != MA_SUCCESS) {
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_DUPLEX, 1, result);
break;
}
} else {
/* No capture data available. Feed in silence. */
inputFrameCount = (ma_uint32)ma_min(pDevice->playback.inputCacheCap, sizeof(silentInputFrames) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels));
/* Отсутствие микрофонных кадров не останавливает рендер; синтетический ввод не сдвигает capture-ring. */
inputFrameCount = (ma_uint32)ma_min(pDevice->playback.inputCacheCap,
sizeof(silentInputFrames) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels));
ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, silentInputFrames, inputFrameCount);
}
pDevice->playback.inputCacheConsumed = 0;
pDevice->playback.inputCacheRemaining = inputFrameCount;
result = ma_pcm_rb_commit_read(pRB, inputFrameCount);
if (result != MA_SUCCESS) {
return result; /* Should never happen. */
}
}
}
if (totalFramesReadOut < frameCount) {
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_UNFILLED, AUDIO_DIAG_DUPLEX, frameCount - totalFramesReadOut, ma_pcm_rb_available_read(pRB));
}
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_DUPLEX, totalFramesReadOut, ma_pcm_rb_available_read(pRB));
return MA_SUCCESS;
}
@@ -24793,6 +24832,7 @@ static ma_result ma_device_read__wasapi(ma_device* pDevice, void* pFrames, ma_ui
/* Overrun detection. */
if ((flags & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) {
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_capture_overrun));
/* Glitched. Probably due to an overrun. */
/*
@@ -24923,6 +24963,7 @@ static ma_result ma_device_write__wasapi(ma_device* pDevice, const void* pFrames
{
ma_result result = MA_SUCCESS;
ma_uint32 totalFramesProcessed = 0;
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_WRITE, frameCount, pDevice->wasapi.mappedBufferPlaybackLen);
/* Keep writing to the device until it's stopped or we've consumed all of our input. */
while (ma_device_get_state(pDevice) == ma_device_state_started && totalFramesProcessed < frameCount) {
@@ -25009,6 +25050,7 @@ static ma_result ma_device_write__wasapi(ma_device* pDevice, const void* pFrames
*pFramesWritten = totalFramesProcessed;
}
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_WRITE, totalFramesProcessed, result);
return result;
}
@@ -29869,6 +29911,7 @@ static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_u
continue; /* Try again. */
} else if (resultALSA == -EPIPE) {
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (read)\n");
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_capture_overrun));
/* Overrun. Recover and try again. If this fails we need to return an error. */
resultALSA = ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture, resultALSA, MA_TRUE);
@@ -29924,6 +29967,7 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames,
continue; /* Try again. */
} else if (resultALSA == -EPIPE) {
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (write)\n");
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_playback_underrun));
/* Underrun. Recover and try again. If this fails we need to return an error. */
resultALSA = ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback, resultALSA, MA_TRUE); /* MA_TRUE=silent (don't print anything on error). */
@@ -30908,11 +30952,13 @@ typedef int (* ma_pa_context_connect_proc) (
typedef void (* ma_pa_context_disconnect_proc) (ma_pa_context* c);
typedef void (* ma_pa_context_set_state_callback_proc) (ma_pa_context* c, ma_pa_context_notify_cb_t cb, void* userdata);
typedef ma_pa_context_state_t (* ma_pa_context_get_state_proc) (const ma_pa_context* c);
typedef int (* ma_pa_context_errno_proc) (const ma_pa_context* c);
typedef ma_pa_operation* (* ma_pa_context_get_sink_info_list_proc) (ma_pa_context* c, ma_pa_sink_info_cb_t cb, void* userdata);
typedef ma_pa_operation* (* ma_pa_context_get_source_info_list_proc) (ma_pa_context* c, ma_pa_source_info_cb_t cb, void* userdata);
typedef ma_pa_operation* (* ma_pa_context_get_sink_info_by_name_proc) (ma_pa_context* c, const char* name, ma_pa_sink_info_cb_t cb, void* userdata);
typedef ma_pa_operation* (* ma_pa_context_get_source_info_by_name_proc)(ma_pa_context* c, const char* name, ma_pa_source_info_cb_t cb, void* userdata);
typedef void (* ma_pa_operation_unref_proc) (ma_pa_operation* o);
typedef void (* ma_pa_operation_cancel_proc) (ma_pa_operation* o);
typedef ma_pa_operation_state_t (* ma_pa_operation_get_state_proc) (const ma_pa_operation* o);
typedef ma_pa_channel_map* (* ma_pa_channel_map_init_extend_proc) (ma_pa_channel_map* m, unsigned channels, ma_pa_channel_map_def_t def);
typedef int (* ma_pa_channel_map_valid_proc) (const ma_pa_channel_map* m);
@@ -30927,6 +30973,7 @@ typedef const ma_pa_sample_spec* (* ma_pa_stream_get_sample_spec_proc) (
typedef const ma_pa_channel_map* (* ma_pa_stream_get_channel_map_proc) (ma_pa_stream* s);
typedef const ma_pa_buffer_attr* (* ma_pa_stream_get_buffer_attr_proc) (ma_pa_stream* s);
typedef ma_pa_operation* (* ma_pa_stream_set_buffer_attr_proc) (ma_pa_stream* s, const ma_pa_buffer_attr* attr, ma_pa_stream_success_cb_t cb, void* userdata);
typedef int (* ma_pa_stream_get_latency_proc)(const ma_pa_stream* s, ma_uint64* usec, int* negative);
typedef const char* (* ma_pa_stream_get_device_name_proc) (const ma_pa_stream* s);
typedef void (* ma_pa_stream_set_write_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata);
typedef void (* ma_pa_stream_set_read_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata);
@@ -30939,6 +30986,7 @@ typedef int (* ma_pa_stream_is_corked_proc) (
typedef ma_pa_operation* (* ma_pa_stream_cork_proc) (ma_pa_stream* s, int b, ma_pa_stream_success_cb_t cb, void* userdata);
typedef ma_pa_operation* (* ma_pa_stream_trigger_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata);
typedef int (* ma_pa_stream_begin_write_proc) (ma_pa_stream* s, void** data, size_t* nbytes);
typedef int (* ma_pa_stream_cancel_write_proc) (ma_pa_stream* s);
typedef int (* ma_pa_stream_write_proc) (ma_pa_stream* s, const void* data, size_t nbytes, ma_pa_free_cb_t free_cb, int64_t offset, ma_pa_seek_mode_t seek);
typedef int (* ma_pa_stream_peek_proc) (ma_pa_stream* s, const void** data, size_t* nbytes);
typedef int (* ma_pa_stream_drop_proc) (ma_pa_stream* s);
@@ -31126,23 +31174,49 @@ static ma_pa_channel_position_t ma_channel_position_to_pulse(ma_channel position
}
#endif
static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_pa_operation* pOP)
#ifndef MA_PULSE_WAIT_TIMEOUT_MS
#define MA_PULSE_WAIT_TIMEOUT_MS 1000
#endif
static ma_result ma_pulse_wait_step(ma_context* pContext, ma_ptr pMainLoop, ma_timer* pTimer, const char* operation)
{
int resultPA;
if (ma_timer_get_time_in_seconds(pTimer) * 1000 >= MA_PULSE_WAIT_TIMEOUT_MS) {
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR,
"[PulseAudio] %s timed out after %d ms.\n", operation, MA_PULSE_WAIT_TIMEOUT_MS);
return MA_TIMEOUT;
}
resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 0, NULL);
if (resultPA < 0) {
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] %s iterate failed: %d.\n", operation, resultPA);
return ma_result_from_pulse(resultPA);
}
if (resultPA == 0) ma_sleep(1);
return MA_SUCCESS;
}
static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_pa_operation* pOP)
{
ma_result result;
ma_timer timer;
ma_pa_operation_state_t state;
MA_ASSERT(pContext != NULL);
MA_ASSERT(pOP != NULL);
ma_timer_init(&timer);
for (;;) {
state = ((ma_pa_operation_get_state_proc)pContext->pulse.pa_operation_get_state)(pOP);
if (state == MA_PA_OPERATION_DONE) return MA_SUCCESS;
if (state != MA_PA_OPERATION_RUNNING) {
break; /* Done. */
ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Operation cancelled.\n");
return MA_ERROR;
}
resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL);
if (resultPA < 0) {
return ma_result_from_pulse(resultPA);
result = ma_pulse_wait_step(pContext, pMainLoop, &timer, "wait");
if (result != MA_SUCCESS) {
((ma_pa_operation_cancel_proc)pContext->pulse.pa_operation_cancel)(pOP);
return result;
}
}
@@ -31165,9 +31239,11 @@ static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma
static ma_result ma_wait_for_pa_context_to_connect__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_ptr pPulseContext)
{
int resultPA;
ma_result result;
ma_timer timer;
ma_pa_context_state_t state;
ma_timer_init(&timer);
for (;;) {
state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pPulseContext);
if (state == MA_PA_CONTEXT_READY) {
@@ -31179,10 +31255,8 @@ static ma_result ma_wait_for_pa_context_to_connect__pulse(ma_context* pContext,
return MA_ERROR;
}
resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL);
if (resultPA < 0) {
return ma_result_from_pulse(resultPA);
}
result = ma_pulse_wait_step(pContext, pMainLoop, &timer, "context connect");
if (result != MA_SUCCESS) return result;
}
/* Should never get here. */
@@ -31191,9 +31265,11 @@ static ma_result ma_wait_for_pa_context_to_connect__pulse(ma_context* pContext,
static ma_result ma_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_ptr pStream)
{
int resultPA;
ma_result result;
ma_timer timer;
ma_pa_stream_state_t state;
ma_timer_init(&timer);
for (;;) {
state = ((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pStream);
if (state == MA_PA_STREAM_READY) {
@@ -31205,10 +31281,8 @@ static ma_result ma_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, m
return MA_ERROR;
}
resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL);
if (resultPA < 0) {
return ma_result_from_pulse(resultPA);
}
result = ma_pulse_wait_step(pContext, pMainLoop, &timer, "stream connect");
if (result != MA_SUCCESS) return result;
}
return MA_SUCCESS;
@@ -31382,10 +31456,13 @@ static ma_result ma_context_get_default_device_index__pulse(ma_context* pContext
if (deviceType == ma_device_type_playback) {
ma_pa_sink_info sinkInfo;
MA_ZERO_OBJECT(&sinkInfo);
sinkInfo.index = (ma_uint32)-1;
result = ma_context_get_sink_info__pulse(pContext, NULL, &sinkInfo);
if (result != MA_SUCCESS) {
return result;
}
if (sinkInfo.index == (ma_uint32)-1) return MA_NO_DEVICE;
if (pIndex != NULL) {
*pIndex = sinkInfo.index;
@@ -31394,10 +31471,13 @@ static ma_result ma_context_get_default_device_index__pulse(ma_context* pContext
if (deviceType == ma_device_type_capture) {
ma_pa_source_info sourceInfo;
MA_ZERO_OBJECT(&sourceInfo);
sourceInfo.index = (ma_uint32)-1;
result = ma_context_get_source_info__pulse(pContext, NULL, &sourceInfo);
if (result != MA_SUCCESS) {
return result;
}
if (sourceInfo.index == (ma_uint32)-1) return MA_NO_DEVICE;
if (pIndex != NULL) {
*pIndex = sourceInfo.index;
@@ -31551,7 +31631,7 @@ static void ma_context_get_device_info_sink_callback__pulse(ma_pa_context* pPuls
{
ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData;
if (endOfList > 0) {
if (endOfList != 0 || pInfo == NULL) {
return;
}
@@ -31588,7 +31668,7 @@ static void ma_context_get_device_info_source_callback__pulse(ma_pa_context* pPu
{
ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData;
if (endOfList > 0) {
if (endOfList != 0 || pInfo == NULL) {
return;
}
@@ -31640,6 +31720,7 @@ static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_devi
}
result = ma_context_get_default_device_index__pulse(pContext, deviceType, &callbackData.defaultDeviceIndex);
if (result != MA_SUCCESS) return result;
if (deviceType == ma_device_type_playback) {
pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)(pContext->pulse.pPulseContext), pDeviceName, ma_context_get_device_info_sink_callback__pulse, &callbackData);
@@ -31648,7 +31729,8 @@ static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_devi
}
if (pOP != NULL) {
ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP);
result = ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP);
if (result != MA_SUCCESS) goto done;
} else {
result = MA_ERROR;
goto done;
@@ -31747,6 +31829,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo
MA_ASSERT(bpf > 0);
frameCount = byteCount / bpf;
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_READ, frameCount, deviceState);
framesProcessed = 0;
while (ma_device_get_state(pDevice) == ma_device_state_started && framesProcessed < frameCount) {
@@ -31756,6 +31839,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo
int pulseResult = ((ma_pa_stream_peek_proc)pDevice->pContext->pulse.pa_stream_peek)(pStream, &pMappedPCMFrames, &bytesMapped);
if (pulseResult < 0) {
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_READ, 1, pulseResult);
break; /* Failed to map. Abort. */
}
@@ -31764,12 +31848,25 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo
if (pMappedPCMFrames != NULL) {
ma_device_handle_backend_data_callback(pDevice, NULL, pMappedPCMFrames, framesMapped);
} else {
/* It's a hole. */
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] ma_device_on_read__pulse: Hole.\n");
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_HOLE, AUDIO_DIAG_READ, framesMapped, 0);
ma_uint8 silence[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
ma_uint64 remaining = framesMapped;
ma_uint32 capacity = sizeof(silence) / bpf;
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_capture_overrun));
ma_silence_pcm_frames(silence, capacity, pDevice->capture.internalFormat, pDevice->capture.internalChannels);
while (remaining > 0 && ma_device_get_state(pDevice) == ma_device_state_started) {
ma_uint32 n = (ma_uint32)ma_min(remaining, capacity);
#ifdef MA_AUDIO_CAPTURE_HOLE
MA_AUDIO_CAPTURE_HOLE(framesMapped - remaining);
#endif
ma_device_handle_backend_data_callback(pDevice, NULL, silence, n);
remaining -= n;
}
}
pulseResult = ((ma_pa_stream_drop_proc)pDevice->pContext->pulse.pa_stream_drop)(pStream);
if (pulseResult < 0) {
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_READ, 1, pulseResult);
break; /* Failed to drop the buffer. */
}
@@ -31780,6 +31877,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo
break;
}
}
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_READ, framesProcessed, 0);
}
static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stream* pStream, ma_uint64* pFramesProcessed)
@@ -31798,6 +31896,7 @@ static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stre
deviceState = ma_device_get_state(pDevice);
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_MAP, 0, deviceState);
bytesMapped = ((ma_pa_stream_writable_size_proc)pDevice->pContext->pulse.pa_stream_writable_size)(pStream);
if (bytesMapped != (size_t)-1) {
if (bytesMapped > 0) {
@@ -31810,15 +31909,23 @@ static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stre
}
framesMapped = bytesMapped / bpf;
if (framesMapped == 0 || pMappedPCMFrames == NULL) {
((ma_pa_stream_cancel_write_proc)pDevice->pContext->pulse.pa_stream_cancel_write)(pStream);
result = MA_ERROR;
goto done;
}
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_MAP, framesMapped, 0);
if (deviceState == ma_device_state_started || deviceState == ma_device_state_starting) { /* Check for starting state just in case this is being used to do the initial fill. */
ma_device_handle_backend_data_callback(pDevice, pMappedPCMFrames, NULL, framesMapped);
} else {
/* Device is not started. Write silence. */
ma_silence_pcm_frames(pMappedPCMFrames, framesMapped, pDevice->playback.format, pDevice->playback.channels);
ma_silence_pcm_frames(pMappedPCMFrames, framesMapped, pDevice->playback.internalFormat, pDevice->playback.internalChannels);
}
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_SUBMIT, framesMapped, 0);
pulseResult = ((ma_pa_stream_write_proc)pDevice->pContext->pulse.pa_stream_write)(pStream, pMappedPCMFrames, bytesMapped, NULL, 0, MA_PA_SEEK_RELATIVE);
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_SUBMIT, framesMapped, pulseResult);
if (pulseResult < 0) {
result = ma_result_from_pulse(pulseResult);
goto done; /* Failed to write data to stream. */
@@ -31835,6 +31942,10 @@ static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stre
}
done:
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_MAP, framesProcessed, result);
if (result != MA_SUCCESS) {
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_WRITE, 1, result);
}
if (pFramesProcessed != NULL) {
*pFramesProcessed = framesProcessed;
}
@@ -31850,6 +31961,7 @@ static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, v
ma_uint64 framesProcessed;
ma_uint32 deviceState;
ma_result result;
ma_bool32 diagnosticNoProgress = MA_FALSE;
MA_ASSERT(pDevice != NULL);
@@ -31866,6 +31978,7 @@ static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, v
MA_ASSERT(bpf > 0);
frameCount = byteCount / bpf;
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_WRITE, frameCount, deviceState);
framesProcessed = 0;
while (framesProcessed < frameCount) {
@@ -31882,8 +31995,14 @@ static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, v
break;
}
if (framesProcessedThisIteration == 0 && !diagnosticNoProgress) {
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_NO_PROGRESS, AUDIO_DIAG_WRITE, 1, frameCount - framesProcessed);
diagnosticNoProgress = MA_TRUE;
}
if (framesProcessedThisIteration == 0) break;
framesProcessed += framesProcessedThisIteration;
}
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_WRITE, framesProcessed, 0);
}
static void ma_device_on_suspended__pulse(ma_pa_stream* pStream, void* pUserData)
@@ -31902,10 +32021,10 @@ static void ma_device_on_suspended__pulse(ma_pa_stream* pStream, void* pUserData
if (suspended == 1) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Suspended.\n");
ma_device__on_notification_stopped(pDevice);
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_began));
} else {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Resumed.\n");
ma_device__on_notification_started(pDevice);
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_ended));
}
}
@@ -32044,7 +32163,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi
if (pDescriptorCapture->sampleRate != 0) {
ss.rate = pDescriptorCapture->sampleRate;
}
streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY;
streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_AUTO_TIMING_UPDATE | MA_PA_STREAM_INTERPOLATE_TIMING;
if (ma_format_from_pulse(ss.format) == ma_format_unknown) {
if (ma_is_little_endian()) {
@@ -32203,7 +32322,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi
ss.rate = pDescriptorPlayback->sampleRate;
}
streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY;
streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_AUTO_TIMING_UPDATE | MA_PA_STREAM_INTERPOLATE_TIMING;
if (ma_format_from_pulse(ss.format) == ma_format_unknown) {
if (ma_is_little_endian()) {
ss.format = MA_PA_SAMPLE_FLOAT32LE;
@@ -32375,6 +32494,10 @@ on_error1:
((ma_pa_stream_unref_proc)pDevice->pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamCapture);
}
on_error0:
((ma_pa_context_disconnect_proc)pDevice->pContext->pulse.pa_context_disconnect)((ma_pa_context*)pDevice->pulse.pPulseContext);
((ma_pa_context_unref_proc)pDevice->pContext->pulse.pa_context_unref)((ma_pa_context*)pDevice->pulse.pPulseContext);
((ma_pa_mainloop_free_proc)pDevice->pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pDevice->pulse.pMainLoop);
MA_ZERO_OBJECT(&pDevice->pulse);
return result;
}
@@ -32504,8 +32627,24 @@ static ma_result ma_device_data_loop__pulse(ma_device* pDevice)
the callbacks deal with it.
*/
while (ma_device_get_state(pDevice) == ma_device_state_started) {
ma_pa_context_state_t contextState = ((ma_pa_context_get_state_proc)pDevice->pContext->pulse.pa_context_get_state)((ma_pa_context*)pDevice->pulse.pPulseContext);
ma_pa_stream_state_t captureState = pDevice->pulse.pStreamCapture ?
((ma_pa_stream_get_state_proc)pDevice->pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pDevice->pulse.pStreamCapture) : MA_PA_STREAM_READY;
ma_pa_stream_state_t playbackState = pDevice->pulse.pStreamPlayback ?
((ma_pa_stream_get_state_proc)pDevice->pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pDevice->pulse.pStreamPlayback) : MA_PA_STREAM_READY;
if (contextState == MA_PA_CONTEXT_FAILED || contextState == MA_PA_CONTEXT_TERMINATED ||
captureState == MA_PA_STREAM_FAILED || captureState == MA_PA_STREAM_TERMINATED ||
playbackState == MA_PA_STREAM_FAILED || playbackState == MA_PA_STREAM_TERMINATED) {
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR,
"[PulseAudio] Device failed context=%d capture=%d playback=%d errno=%d.\n", contextState, captureState,
playbackState, ((ma_pa_context_errno_proc)pDevice->pContext->pulse.pa_context_errno)((ma_pa_context*)pDevice->pulse.pPulseContext));
return MA_ERROR;
}
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_LOOP, 0, 0);
resultPA = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL);
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_LOOP, 0, resultPA);
if (resultPA < 0) {
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_LOOP, 1, resultPA);
break;
}
}
@@ -32588,11 +32727,13 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
pContext->pulse.pa_context_disconnect = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_disconnect");
pContext->pulse.pa_context_set_state_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_set_state_callback");
pContext->pulse.pa_context_get_state = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_state");
pContext->pulse.pa_context_errno = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_errno");
pContext->pulse.pa_context_get_sink_info_list = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_sink_info_list");
pContext->pulse.pa_context_get_source_info_list = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_source_info_list");
pContext->pulse.pa_context_get_sink_info_by_name = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_sink_info_by_name");
pContext->pulse.pa_context_get_source_info_by_name = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_source_info_by_name");
pContext->pulse.pa_operation_unref = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_operation_unref");
pContext->pulse.pa_operation_cancel = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_operation_cancel");
pContext->pulse.pa_operation_get_state = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_operation_get_state");
pContext->pulse.pa_channel_map_init_extend = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_channel_map_init_extend");
pContext->pulse.pa_channel_map_valid = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_channel_map_valid");
@@ -32607,6 +32748,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
pContext->pulse.pa_stream_get_channel_map = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_channel_map");
pContext->pulse.pa_stream_get_buffer_attr = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_buffer_attr");
pContext->pulse.pa_stream_set_buffer_attr = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_set_buffer_attr");
pContext->pulse.pa_stream_get_latency = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_latency");
pContext->pulse.pa_stream_get_device_name = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_device_name");
pContext->pulse.pa_stream_set_write_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_set_write_callback");
pContext->pulse.pa_stream_set_read_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_set_read_callback");
@@ -32619,6 +32761,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
pContext->pulse.pa_stream_cork = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_cork");
pContext->pulse.pa_stream_trigger = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_trigger");
pContext->pulse.pa_stream_begin_write = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_begin_write");
pContext->pulse.pa_stream_cancel_write = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_cancel_write");
pContext->pulse.pa_stream_write = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_write");
pContext->pulse.pa_stream_peek = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_peek");
pContext->pulse.pa_stream_drop = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_drop");
@@ -32651,11 +32794,13 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
ma_pa_context_disconnect_proc _pa_context_disconnect = pa_context_disconnect;
ma_pa_context_set_state_callback_proc _pa_context_set_state_callback = pa_context_set_state_callback;
ma_pa_context_get_state_proc _pa_context_get_state = pa_context_get_state;
ma_pa_context_errno_proc _pa_context_errno = pa_context_errno;
ma_pa_context_get_sink_info_list_proc _pa_context_get_sink_info_list = pa_context_get_sink_info_list;
ma_pa_context_get_source_info_list_proc _pa_context_get_source_info_list = pa_context_get_source_info_list;
ma_pa_context_get_sink_info_by_name_proc _pa_context_get_sink_info_by_name = pa_context_get_sink_info_by_name;
ma_pa_context_get_source_info_by_name_proc _pa_context_get_source_info_by_name= pa_context_get_source_info_by_name;
ma_pa_operation_unref_proc _pa_operation_unref = pa_operation_unref;
ma_pa_operation_cancel_proc _pa_operation_cancel = pa_operation_cancel;
ma_pa_operation_get_state_proc _pa_operation_get_state = pa_operation_get_state;
ma_pa_channel_map_init_extend_proc _pa_channel_map_init_extend = pa_channel_map_init_extend;
ma_pa_channel_map_valid_proc _pa_channel_map_valid = pa_channel_map_valid;
@@ -32670,6 +32815,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
ma_pa_stream_get_channel_map_proc _pa_stream_get_channel_map = pa_stream_get_channel_map;
ma_pa_stream_get_buffer_attr_proc _pa_stream_get_buffer_attr = pa_stream_get_buffer_attr;
ma_pa_stream_set_buffer_attr_proc _pa_stream_set_buffer_attr = pa_stream_set_buffer_attr;
ma_pa_stream_get_latency_proc _pa_stream_get_latency = pa_stream_get_latency;
ma_pa_stream_get_device_name_proc _pa_stream_get_device_name = pa_stream_get_device_name;
ma_pa_stream_set_write_callback_proc _pa_stream_set_write_callback = pa_stream_set_write_callback;
ma_pa_stream_set_read_callback_proc _pa_stream_set_read_callback = pa_stream_set_read_callback;
@@ -32682,6 +32828,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
ma_pa_stream_cork_proc _pa_stream_cork = pa_stream_cork;
ma_pa_stream_trigger_proc _pa_stream_trigger = pa_stream_trigger;
ma_pa_stream_begin_write_proc _pa_stream_begin_write = pa_stream_begin_write;
ma_pa_stream_cancel_write_proc _pa_stream_cancel_write = pa_stream_cancel_write;
ma_pa_stream_write_proc _pa_stream_write = pa_stream_write;
ma_pa_stream_peek_proc _pa_stream_peek = pa_stream_peek;
ma_pa_stream_drop_proc _pa_stream_drop = pa_stream_drop;
@@ -32713,11 +32860,13 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
pContext->pulse.pa_context_disconnect = (ma_proc)_pa_context_disconnect;
pContext->pulse.pa_context_set_state_callback = (ma_proc)_pa_context_set_state_callback;
pContext->pulse.pa_context_get_state = (ma_proc)_pa_context_get_state;
pContext->pulse.pa_context_errno = (ma_proc)_pa_context_errno;
pContext->pulse.pa_context_get_sink_info_list = (ma_proc)_pa_context_get_sink_info_list;
pContext->pulse.pa_context_get_source_info_list = (ma_proc)_pa_context_get_source_info_list;
pContext->pulse.pa_context_get_sink_info_by_name = (ma_proc)_pa_context_get_sink_info_by_name;
pContext->pulse.pa_context_get_source_info_by_name = (ma_proc)_pa_context_get_source_info_by_name;
pContext->pulse.pa_operation_unref = (ma_proc)_pa_operation_unref;
pContext->pulse.pa_operation_cancel = (ma_proc)_pa_operation_cancel;
pContext->pulse.pa_operation_get_state = (ma_proc)_pa_operation_get_state;
pContext->pulse.pa_channel_map_init_extend = (ma_proc)_pa_channel_map_init_extend;
pContext->pulse.pa_channel_map_valid = (ma_proc)_pa_channel_map_valid;
@@ -32732,6 +32881,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
pContext->pulse.pa_stream_get_channel_map = (ma_proc)_pa_stream_get_channel_map;
pContext->pulse.pa_stream_get_buffer_attr = (ma_proc)_pa_stream_get_buffer_attr;
pContext->pulse.pa_stream_set_buffer_attr = (ma_proc)_pa_stream_set_buffer_attr;
pContext->pulse.pa_stream_get_latency = (ma_proc)_pa_stream_get_latency;
pContext->pulse.pa_stream_get_device_name = (ma_proc)_pa_stream_get_device_name;
pContext->pulse.pa_stream_set_write_callback = (ma_proc)_pa_stream_set_write_callback;
pContext->pulse.pa_stream_set_read_callback = (ma_proc)_pa_stream_set_read_callback;
@@ -32744,6 +32894,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
pContext->pulse.pa_stream_cork = (ma_proc)_pa_stream_cork;
pContext->pulse.pa_stream_trigger = (ma_proc)_pa_stream_trigger;
pContext->pulse.pa_stream_begin_write = (ma_proc)_pa_stream_begin_write;
pContext->pulse.pa_stream_cancel_write = (ma_proc)_pa_stream_cancel_write;
pContext->pulse.pa_stream_write = (ma_proc)_pa_stream_write;
pContext->pulse.pa_stream_peek = (ma_proc)_pa_stream_peek;
pContext->pulse.pa_stream_drop = (ma_proc)_pa_stream_drop;
@@ -43767,6 +43918,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC
result = pContext->callbacks.onDeviceInit(pDevice, pConfig, &descriptorPlayback, &descriptorCapture);
if (result != MA_SUCCESS) {
ma_event_uninit(&pDevice->stopEvent);
ma_event_uninit(&pDevice->startEvent);
ma_event_uninit(&pDevice->wakeupEvent);
ma_mutex_uninit(&pDevice->startStopLock);
@@ -44008,6 +44160,8 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC
}
}
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_OPEN, -1, pDevice->type, pDevice->sampleRate);
MA_DIAGNOSTIC_DEVICE_INIT(pDevice);
MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped);
return MA_SUCCESS;
}
@@ -44175,6 +44329,7 @@ MA_API void ma_device_uninit(ma_device* pDevice)
ma_free(pDevice->pContext, &allocationCallbacks);
}
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_CLOSE, -1, 0, 0);
MA_ZERO_OBJECT(pDevice);
}
@@ -44506,6 +44661,14 @@ MA_API ma_result ma_device_handle_backend_data_callback(ma_device* pDevice, void
if (frameCount == 0) {
return MA_INVALID_ARGS;
}
if (((pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_loopback) && pInput == NULL) ||
(pDevice->type == ma_device_type_playback && pOutput == NULL)) {
return MA_INVALID_ARGS;
}
#ifdef MA_AUDIO_TIMING_BEGIN
MA_AUDIO_TIMING_BEGIN(pDevice, frameCount, pInput != NULL);
#endif
if (pDevice->type == ma_device_type_duplex) {
if (pInput != NULL) {
@@ -44517,22 +44680,17 @@ MA_API ma_result ma_device_handle_backend_data_callback(ma_device* pDevice, void
}
} else {
if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_loopback) {
if (pInput == NULL) {
return MA_INVALID_ARGS;
}
ma_device__send_frames_to_client(pDevice, frameCount, pInput);
}
if (pDevice->type == ma_device_type_playback) {
if (pOutput == NULL) {
return MA_INVALID_ARGS;
}
ma_device__read_frames_from_client(pDevice, frameCount, pOutput);
}
}
#ifdef MA_AUDIO_TIMING_END
MA_AUDIO_TIMING_END();
#endif
return MA_SUCCESS;
}
+17
View File
@@ -146,6 +146,23 @@ int opus_codec_encoder_signal_set(opus_codec_encoder_t* enc, int signal_type) {
return opus_encoder_ctl(enc->opus_enc, OPUS_SET_SIGNAL(signal_type));
}
int opus_codec_encoder_application_set(opus_codec_encoder_t* enc, int application) {
if (!enc || !enc->opus_enc) return -1;
return opus_encoder_ctl(enc->opus_enc, OPUS_SET_APPLICATION(application));
}
int opus_codec_encoder_delay(opus_codec_encoder_t* enc, int* samples) {
if (!enc || !samples) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "Opus delay invalid arguments"); return -1; }
opus_int32 delay = 0;
int result = opus_encoder_ctl(enc->opus_enc, OPUS_GET_LOOKAHEAD(&delay));
if (result != OPUS_OK || delay < 0 || delay > enc->sample_rate) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "Opus delay query failed result=%d samples=%d", result, (int)delay); return -1;
}
*samples = delay;
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "Opus capture delay samples=%d rate=%d", (int)delay, enc->sample_rate);
return 0;
}
const char* opus_codec_strerror(int err) {
switch (err) {
case OPUS_OK: return "OK";
+13
View File
@@ -1,3 +1,9 @@
/* opus_codec — владеющие обёртки encoder/decoder libopus для int16 interleaved PCM.
* create принимает частоту и 1/2 канала; неположительная частота → 48000, неверные каналы → моно.
* Encoder/decoder имеют состояние потока; каждому нужен один последовательный владелец.
* frame_samples — отсчёты НА КАНАЛ: PCM вмещает frame_samples*channels элементов.
* encode возвращает байты Opus, decode — отсчёты на канал; отрицательное значение означает ошибку.
* out_cap — байты; decode с data=NULL/len=0 использует PLC libopus. Освобождение — соответствующим destroy. */
#ifndef OPUS_CODEC_H
#define OPUS_CODEC_H
@@ -10,6 +16,10 @@ extern "C" {
typedef struct opus_codec_encoder opus_codec_encoder_t;
typedef struct opus_codec_decoder opus_codec_decoder_t;
/* Режим кодирования Opus: голос (VOIP, default) или музыка (AUDIO). */
#define OPUS_CODEC_APP_VOIP 2048 /* OPUS_APPLICATION_VOIP */
#define OPUS_CODEC_APP_AUDIO 2049 /* OPUS_APPLICATION_AUDIO */
opus_codec_encoder_t* opus_codec_encoder_create(int sample_rate, int channels);
void opus_codec_encoder_destroy(opus_codec_encoder_t* enc);
int opus_codec_encode(opus_codec_encoder_t* enc, const int16_t* pcm, int frame_samples,
@@ -23,6 +33,9 @@ int opus_codec_decode(opus_codec_decoder_t* dec, const uint8_t* data, int len,
int opus_codec_encoder_bitrate_set(opus_codec_encoder_t* enc, int bitrate_bps);
int opus_codec_encoder_complexity_set(opus_codec_encoder_t* enc, int complexity);
int opus_codec_encoder_signal_set(opus_codec_encoder_t* enc, int signal_type);
int opus_codec_encoder_application_set(opus_codec_encoder_t* enc, int application);
/* Задержка codec в сэмплах на канал; запрашивать после настройки encoder, не задавать константой. */
int opus_codec_encoder_delay(opus_codec_encoder_t* enc, int* samples);
const char* opus_codec_strerror(int err);
+60 -22
View File
@@ -39,6 +39,25 @@
#endif
#endif
/*
* Классифицирует IPv4-адрес (network byte order):
* 1 = глобальный/публичный (маршрутизируемый в интернете),
* 0 = приватный, CGNAT, loopback, link-local, multicast или зарезервированный.
*/
int ip_is_public(uint32_t addr_be) {
uint32_t a = ntohl(addr_be);
if ((a & 0xFF000000u) == 0x0A000000u) return 0; /* 10.0.0.0/8 */
if ((a & 0xFFF00000u) == 0xAC100000u) return 0; /* 172.16.0.0/12 */
if ((a & 0xFFFF0000u) == 0xC0A80000u) return 0; /* 192.168.0.0/16 */
if ((a & 0xFF000000u) == 0x7F000000u) return 0; /* 127.0.0.0/8 */
if ((a & 0xFFFF0000u) == 0xA9FE0000u) return 0; /* 169.254.0.0/16 */
if ((a & 0xFF000000u) == 0x00000000u) return 0; /* 0.0.0.0/8 */
if ((a & 0xF0000000u) == 0xE0000000u) return 0; /* 224.0.0.0/4 */
if ((a & 0xF0000000u) == 0xF0000000u) return 0; /* 240.0.0.0/4 */
if ((a & 0xFFC00000u) == 0x64400000u) return 0; /* 100.64.0.0/10 (CGNAT) */
return 1;
}
/*
* Generate cryptographically secure random bytes
* Returns 0 on success, -1 on error
@@ -60,6 +79,12 @@ int random_bytes(uint8_t *buffer, size_t len) {
#endif
}
/* Глобальный unicast IPv6 (2000::/3). При выборе адреса интерфейса предпочитаем его
* ULA (fc00::/7) и link-local (fe80::/10). */
static int ipv6_is_global_unicast(const uint8_t* a) {
return (a[0] & 0xe0) == 0x20;
}
#ifndef _WIN32
uint32_t get_interface_ip_by_index(uint32_t netif_index) {
struct ifaddrs *ifaddr, *ifa;
@@ -89,7 +114,8 @@ int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* ad
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) return -1;
int found = 0;
uint8_t global_match[16], local_match[16];
int has_global = 0, has_local = 0;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue;
if (ifa->ifa_name == NULL) continue;
@@ -100,20 +126,18 @@ int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* ad
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ifa->ifa_addr;
if ((sin6->sin6_addr.s6_addr[0] & 0xfe) == 0xfe && sin6->sin6_addr.s6_addr[1] == 0x80) continue;
int is_temporary = (ifa->ifa_flags & IFA_F_TEMPORARY) ? 1 : 0;
if (temporary && is_temporary) {
memcpy(addr_out, &sin6->sin6_addr, 16);
found = 1;
break;
}
if (!temporary && !is_temporary) {
memcpy(addr_out, &sin6->sin6_addr, 16);
found = 1;
break;
if ((temporary && !is_temporary) || (!temporary && is_temporary)) continue;
if (ipv6_is_global_unicast(sin6->sin6_addr.s6_addr)) {
if (!has_global) { memcpy(global_match, &sin6->sin6_addr, 16); has_global = 1; }
} else {
if (!has_local) { memcpy(local_match, &sin6->sin6_addr, 16); has_local = 1; }
}
}
freeifaddrs(ifaddr);
return found ? 0 : -1;
if (has_global) { memcpy(addr_out, global_match, 16); return 0; }
if (has_local) { memcpy(addr_out, local_match, 16); return 0; }
return -1;
}
#ifdef __linux__
@@ -151,8 +175,8 @@ int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t*
if (send(nl_sock, &req, req.nlh.nlmsg_len, 0) < 0) { close(nl_sock); return -1; }
uint8_t fallback[16], stable_addr[16], temp_addr[16];
int has_fallback = 0, has_stable = 0, has_temp = 0;
uint8_t fallback[16], g_stable[16], g_temp[16], l_stable[16], l_temp[16];
int has_fallback = 0, has_g_stable = 0, has_g_temp = 0, has_l_stable = 0, has_l_temp = 0;
char buf[8192];
int done = 0;
@@ -181,19 +205,32 @@ int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t*
int is_temp = (ifa_flags & IFA_F_TEMPORARY) != 0;
int is_mngtmp = (ifa_flags & IFA_F_MANAGETEMPADDR) != 0;
int is_global = ipv6_is_global_unicast(addr);
if (!has_fallback) { memcpy(fallback, addr, 16); has_fallback = 1; }
if (!has_stable && (!is_temp || is_mngtmp)) { memcpy(stable_addr, addr, 16); has_stable = 1; }
if (!has_temp && is_temp && !is_mngtmp) { memcpy(temp_addr, addr, 16); has_temp = 1; }
if (is_global) {
if (!has_g_stable && (!is_temp || is_mngtmp)) { memcpy(g_stable, addr, 16); has_g_stable = 1; }
if (!has_g_temp && is_temp && !is_mngtmp) { memcpy(g_temp, addr, 16); has_g_temp = 1; }
} else {
if (!has_l_stable && (!is_temp || is_mngtmp)) { memcpy(l_stable, addr, 16); has_l_stable = 1; }
if (!has_l_temp && is_temp && !is_mngtmp) { memcpy(l_temp, addr, 16); has_l_temp = 1; }
}
}
}
close(nl_sock);
if (prefer_stable && has_stable) { memcpy(addr_out, stable_addr, 16); return 0; }
if (!prefer_stable && has_temp) { memcpy(addr_out, temp_addr, 16); return 0; }
if (has_stable) { memcpy(addr_out, stable_addr, 16); return 0; }
if (has_temp) { memcpy(addr_out, temp_addr, 16); return 0; }
if (prefer_stable) {
if (has_g_stable) { memcpy(addr_out, g_stable, 16); return 0; }
if (has_g_temp) { memcpy(addr_out, g_temp, 16); return 0; }
if (has_l_stable) { memcpy(addr_out, l_stable, 16); return 0; }
if (has_l_temp) { memcpy(addr_out, l_temp, 16); return 0; }
} else {
if (has_g_temp) { memcpy(addr_out, g_temp, 16); return 0; }
if (has_g_stable) { memcpy(addr_out, g_stable, 16); return 0; }
if (has_l_temp) { memcpy(addr_out, l_temp, 16); return 0; }
if (has_l_stable) { memcpy(addr_out, l_stable, 16); return 0; }
}
if (has_fallback) { memcpy(addr_out, fallback, 16); return 0; }
return -1;
}
@@ -335,6 +372,7 @@ int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t*
PIP_ADAPTER_ADDRESSES adapters = plat_win_get_adapters(AF_INET6);
if (!adapters) return -1;
int found = 0;
uint8_t local_match[16]; int has_local = 0;
PIP_ADAPTER_ADDRESSES a = plat_win_find_adapter(adapters, netif_index);
if (a) {
for (PIP_ADAPTER_UNICAST_ADDRESS u = a->FirstUnicastAddress; u; u = u->Next) {
@@ -344,12 +382,12 @@ int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t*
{ static const uint8_t lb[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
if (memcmp(b, lb, 16) == 0) continue; } /* loopback */
{ static const uint8_t z[16]; if (memcmp(b, z, 16) == 0) continue; } /* :: */
memcpy(addr_out, b, 16);
found = 1;
break;
if (ipv6_is_global_unicast(b)) { memcpy(addr_out, b, 16); found = 1; break; }
if (!has_local) { memcpy(local_match, b, 16); has_local = 1; }
}
}
free(adapters);
if (!found && has_local) { memcpy(addr_out, local_match, 16); found = 1; }
return found ? 0 : -1;
}
+25 -10
View File
@@ -1,5 +1,13 @@
/**
* Platform compatibility layer for POSIX functions on Windows
* platform_compat — общие заголовки POSIX/Windows и адаптеры отсутствующих POSIX-функций.
* Также предоставляет криптографический random_bytes, адреса интерфейсов, выбор default route
* и классификацию публичных IPv4. Сокетные типы/ошибки и nonblocking API — в socket_compat.h.
* Подключать этот заголовок вместо прямых POSIX includes для общего кода.
* Windows: htobe16/32/64 и be16/32/64toh через byteswap, strcasecmp/strncasecmp через _stricmp/_strnicmp,
* memmem через inline-поиск (пустой образец возвращает начало), poll через WSAPoll, mkdir через _mkdir.
* utun_gettimeofday — календарное Unix-время (не монотонное); Windows игнорирует tz.
* Windows pipe — _pipe(4096, _O_BINARY). fcntl: F_GETFL возвращает 0, F_SETFL с O_NONBLOCK включает
* неблокирующий режим; остальные флаги игнорируются, обратное переключение этим адаптером не выполняется.
*/
#ifndef PLATFORM_COMPAT_H
@@ -176,31 +184,38 @@ extern "C" {
#include <stdint.h>
// Generate cryptographically secure random bytes
// Returns 0 on success, -1 on error
// Заполняет buffer криптографическими случайными байтами: POSIX /dev/urandom, Windows BCryptGenRandom.
// buffer не NULL, len > 0 (Windows: помещается в ULONG). 0 — всё заполнено, -1 — ошибка/неполное чтение.
int random_bytes(uint8_t *buffer, size_t len);
// Get IPv4 address of interface by its index (ifindex)
// Returns IPv4 address in network byte order, 0 on error
// POSIX: getifaddrs; Windows: IP Helper. Адрес в network byte order, 0 при ошибке/отсутствии адреса.
uint32_t get_interface_ip_by_index(uint32_t netif_index);
// Get IPv6 address of interface by its index (ifindex)
// temporary=1: get temporary (privacy) IPv6, temporary=0: get permanent IPv6
// POSIX фильтрует по IFA_F_TEMPORARY в ifa_flags, но getifaddrs не гарантирует адресные флаги;
// для точного выбора temporary/stable на Linux использовать get_interface_ipv6_addr_nl().
// Windows игнорирует temporary: предпочитает глобальный адрес, затем локальный, исключая link-local/loopback/::.
// Returns 0 on success (fills addr_out with 16 bytes), -1 on error or not found
int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out);
// Get IPv6 address using netlink (Linux-only, accurate IFA_F_* flags)
// prefer_stable=1: prefer permanent/stable (IFA_F_MANAGETEMPADDR) over temporary
// prefer_stable=0: prefer temporary over stable
// Выбрать IPv6: Linux использует netlink с IFA_F_*, прочие POSIX — getifaddrs(), Windows — IP Helper.
// POSIX: prefer_stable=1 предпочитает постоянный адрес, 0 — temporary; допускается fallback на другой тип.
// Windows игнорирует prefer_stable, выбор как у get_interface_ipv6_by_index().
// Returns 0 on success (fills addr_out with 16 bytes), -1 on error
int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t* addr_out);
// Determine the interface index for the default route (outgoing traffic)
// Creates a temp UDP socket, connects to a well-known address to force
// kernel routing, then matches the chosen source IP to getifaddrs()
// POSIX: UDP connect к 8.8.8.8:53 или [2001:4860:4860::8888]:53, сопоставление локального IP с getifaddrs().
// Windows: GetBestInterfaceEx к тому же адресу. Результат — выбранный маршрут к этому адресу,
// который может отличаться от default route при более специфичных маршрутах.
// family: AF_INET or AF_INET6, returns ifindex (0 on failure)
uint32_t get_default_route_netif_index(int family);
// Классифицирует IPv4 (network byte order) по встроенному списку: 0 для 0/8, 10/8, 127/8, 169.254/16,
// 172.16/12, 192.168/16, 100.64/10, 224/4 и 240/4; 1 для остальных. Это не проверка достижимости адреса.
int ip_is_public(uint32_t addr_be);
// Cross-platform mkdir
#ifdef _WIN32
-81
View File
@@ -1,81 +0,0 @@
# platform_compat — Cross-platform compatibility layer
## 1. Назначение
Унифицирует различия между POSIX (Linux/FreeBSD) и Windows (MSYS2 UCRT64) на уровне системных вызовов и типов. Предоставляет единый API для байтового порядка, строковых функций, энтропии, работы с сетевыми интерфейсами и времени. Полностью заголовочный (inline) для макросов и тривиальных обёрток, `.c` только для нетривиальных реализаций.
## 2. Как пользоваться
Подключить `"platform_compat.h"` — всё остальное разрешается автоматически в зависимости от `_WIN32`.
### Байтовый порядок
```c
uint16_t v16 = htobe16(x); // host → big-endian
uint32_t v32 = be32toh(x); // big-endian → host
uint64_t v64 = be64toh(x); // big-endian → host
// На Linux использует <endian.h>, на Windows — _byteswap_*
```
### Криптостойкий random
```c
uint8_t salt[8];
if (random_bytes(salt, sizeof(salt)) != 0) { /* ошибка */ }
// Linux: /dev/urandom, Windows: BCryptGenRandom
```
### Сетевые интерфейсы
```c
// Определить интерфейс маршрута по умолчанию
uint32_t ifidx = get_default_route_netif_index(AF_INET); // или AF_INET6
// Получить IPv4 адрес интерфейса по индексу
uint32_t ipv4 = get_interface_ip_by_index(ifidx); // network byte order
// Получить IPv6 адрес (постоянный или временный)
uint8_t ipv6[16];
if (get_interface_ipv6_by_index(ifidx, 1, ipv6) == 0) { // temporary=1
// ipv6 содержит 16 байт адреса
}
```
**Windows**: `get_interface_ip_by_index`, `get_interface_ipv6_by_index`, `get_default_route_netif_index` — заглушки (возвращают 0/-1).
### Время
```c
struct timeval tv;
utun_gettimeofday(&tv, NULL); // макрос: на Linux → gettimeofday, на Windows → inline-реализация
```
### Прочее
- `strcasecmp`/`strncasecmp` — на Windows `_stricmp`/`_strnicmp`
- `memmem` — на Windows inline-реализация `compat_memmem`
- `pipe` — на Windows `_pipe` с `_O_BINARY`
- `poll` — на Windows `WSAPoll`, флаги `POLLIN`/`POLLOUT` и т.д. определены если отсутствуют
- `ssize_t` — определён если отсутствует
- `fcntl` — на Windows упрощённая реализация только для `F_SETFL`/`O_NONBLOCK`
- `utun_mkdir` — кроссплатформенный `mkdir`
### Ограничения
- Функции сетевых интерфейсов работают только на POSIX, на Windows — заглушки
- `get_default_route_netif_index` создаёт временный UDP-сокет к `8.8.8.8` (IPv4) или `2001:4860:4860::8888` (IPv6) для определения интерфейса
- Только IPv4/IPv6; `family` только `AF_INET` или `AF_INET6`
## 3. API
### Байтовый порядок
| Макрос | Назначение |
|--------|------------|
| `htobe16(x)` / `be16toh(x)` | 16-bit host ↔ big-endian |
| `htobe32(x)` / `be32toh(x)` | 32-bit host ↔ big-endian |
| `htobe64(x)` / `be64toh(x)` | 64-bit host ↔ big-endian |
### Основные функции
| Функция | Назначение |
|---------|------------|
| `random_bytes(buffer, len)` | Криптостойкие случайные байты. 0 — успех, -1 — ошибка |
| `get_interface_ip_by_index(ifindex)` | IPv4 адрес интерфейса в network byte order, 0 при ошибке |
| `get_interface_ipv6_by_index(ifindex, temporary, out)` | IPv6 адрес (temporary=1 — временный privacy-адрес), 0 — успех |
| `get_default_route_netif_index(family)` | ifindex дефолтного маршрута через connect() к внешнему IP |
### Макросы-заменители (условные)
| Макрос | Назначение |
|--------|------------|
| `utun_gettimeofday(tv, tz)` | gettimeofday (inline на Windows) |
| `utun_mkdir(path, mode)` | mkdir |
+249
View File
@@ -0,0 +1,249 @@
#include <string.h>
#include "mem.h"
#include "debug_config.h"
#include "reed_solomon.h"
struct rs_codec {
unsigned n;
unsigned k;
size_t max_block_size;
uint8_t gf_log[256];
uint8_t gf_exp[510];
uint8_t *generator;
uint8_t *work;
uint8_t storage[];
};
/* Умножение в GF(256) с примитивным полиномом x^8+x^4+x^3+x^2+1 (0x11d). */
static uint8_t rs_multiply(const struct rs_codec *ctx, uint8_t a, uint8_t b) {
return a && b ? ctx->gf_exp[ctx->gf_log[a] + ctx->gf_log[b]] : 0;
}
/* Обратить k строк матрицы; результат — правая половина рабочей матрицы [A|I]. */
static int rs_invert(struct rs_codec *ctx, const uint8_t *const rows[]) {
unsigned k = ctx->k;
unsigned stride = 2 * k;
for (unsigned i = 0; i < k; i++) {
uint8_t *row = ctx->work + i * stride;
memcpy(row, rows[i], k);
memset(row + k, 0, k);
row[k + i] = 1;
}
for (unsigned col = 0; col < k; col++) {
unsigned pivot = col;
while (pivot < k && !ctx->work[pivot * stride + col]) pivot++;
if (pivot == k) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: singular matrix n=%u k=%u column=%u", ctx->n, k, col);
return RS_ERR_MATRIX;
}
uint8_t *row = ctx->work + col * stride;
if (pivot != col) {
uint8_t *other = ctx->work + pivot * stride;
for (unsigned j = 0; j < stride; j++) {
uint8_t tmp = row[j];
row[j] = other[j];
other[j] = tmp;
}
}
uint8_t inverse = ctx->gf_exp[255 - ctx->gf_log[row[col]]];
for (unsigned j = col; j < stride; j++) row[j] = rs_multiply(ctx, row[j], inverse);
for (unsigned i = 0; i < k; i++) {
if (i == col) continue;
uint8_t *other = ctx->work + i * stride;
uint8_t factor = other[col];
if (!factor) continue;
for (unsigned j = col; j < stride; j++) other[j] ^= rs_multiply(ctx, factor, row[j]);
}
}
return RS_OK;
}
/* Построить систематическую матрицу G=V*inverse(V[0:k]) в точках 2^i поля GF(256). */
static int rs_build_generator(struct rs_codec *ctx) {
unsigned k = ctx->k;
if (ctx->n != k) {
const uint8_t *rows[RS_MAX_BLOCKS];
for (unsigned i = 0; i < ctx->n; i++) {
uint8_t value = 1;
for (unsigned j = 0; j < k; j++) {
ctx->generator[i * k + j] = value;
value = rs_multiply(ctx, value, ctx->gf_exp[i]);
}
}
for (unsigned i = 0; i < k; i++) rows[i] = ctx->generator + i * k;
int rc = rs_invert(ctx, rows);
if (rc != RS_OK) return rc;
for (unsigned i = k; i < ctx->n; i++) {
uint8_t result[RS_MAX_BLOCKS];
for (unsigned j = 0; j < k; j++) {
uint8_t value = 0;
for (unsigned t = 0; t < k; t++) {
value ^= rs_multiply(ctx, ctx->generator[i * k + t], ctx->work[t * 2 * k + k + j]);
}
result[j] = value;
}
memcpy(ctx->generator + i * k, result, k);
}
}
memset(ctx->generator, 0, k * k);
for (unsigned i = 0; i < k; i++) ctx->generator[i * k + i] = 1;
return RS_OK;
}
/* Инициализация параметров, таблиц поля и матрицы; все рабочие данные в одной аллокации. */
int rs_init(struct rs_codec **out, unsigned n, unsigned r, size_t block_size) {
if (out) *out = NULL;
if (!out || !n || n > RS_MAX_BLOCKS || r >= n || block_size <= RS_HEADER_SIZE || block_size > RS_MAX_BLOCK_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: invalid init ctx=%p n=%u r=%u block_size=%zu", (void *)out, n, r, block_size);
return RS_ERR_INVALID_ARG;
}
unsigned k = n - r;
size_t bytes = sizeof(struct rs_codec) + n * k + 2 * k * k;
struct rs_codec *ctx = u_calloc(1, (uint32_t)bytes);
if (!ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: allocation failed n=%u r=%u block_size=%zu bytes=%zu", n, r, block_size, bytes);
return RS_ERR_MEMORY;
}
ctx->n = n;
ctx->k = k;
ctx->max_block_size = block_size;
ctx->generator = ctx->storage;
ctx->work = ctx->storage + n * k;
unsigned value = 1;
for (unsigned i = 0; i < 255; i++) {
ctx->gf_exp[i] = (uint8_t)value;
ctx->gf_log[value] = (uint8_t)i;
value <<= 1;
if (value & 256) value ^= 0x11d;
}
for (unsigned i = 255; i < 510; i++) ctx->gf_exp[i] = ctx->gf_exp[i - 255];
int rc = rs_build_generator(ctx);
if (rc != RS_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: generator failed n=%u k=%u rc=%d", n, k, rc);
u_free(ctx);
return rc;
}
*out = ctx;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "reed_solomon: initialized n=%u r=%u max_block_size=%zu input_capacity=%zu memory=%zu",
n, r, block_size, k * (block_size - RS_HEADER_SIZE), bytes);
return RS_OK;
}
void rs_free(struct rs_codec *ctx) {
if (!ctx) return;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "reed_solomon: free n=%u r=%u max_block_size=%zu", ctx->n, ctx->n - ctx->k, ctx->max_block_size);
u_free(ctx);
}
size_t rs_input_capacity(const struct rs_codec *ctx) {
if (!ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: input_capacity requires a context");
return 0;
}
return ctx->k * (ctx->max_block_size - RS_HEADER_SIZE);
}
/* Записать заголовки и исходные субблоки, затем вычислить r проверочных субблоков. */
int rs_encode(struct rs_codec *ctx, uint64_t seq, const uint8_t *input, size_t length,
uint8_t *const blocks[], size_t *block_size) {
if (!ctx || !input || !blocks || !block_size || !length || length > rs_input_capacity(ctx)) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: invalid encode ctx=%p input=%p blocks=%p length=%zu size_out=%p",
(void *)ctx, (const void *)input, (const void *)blocks, length, (void *)block_size);
return RS_ERR_INVALID_ARG;
}
size_t payload_size = (length + ctx->k - 1) / ctx->k;
for (unsigned i = 0; i < ctx->n; i++) {
if (!blocks[i]) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: missing encode buffer seq=%llu subblock=%u", (unsigned long long)seq, i);
return RS_ERR_INVALID_ARG;
}
}
for (unsigned i = 0; i < ctx->n; i++) {
for (unsigned j = 0; j < 8; j++) blocks[i][j] = (uint8_t)(seq >> (56 - 8 * j));
blocks[i][8] = (uint8_t)i;
uint8_t *payload = blocks[i] + RS_HEADER_SIZE;
memset(payload, 0, payload_size);
if (i < ctx->k) {
size_t offset = i * payload_size;
size_t take = offset < length ? length - offset : 0;
if (take > payload_size) take = payload_size;
if (take) memcpy(payload, input + offset, take);
continue;
}
for (unsigned j = 0; j < ctx->k; j++) {
uint8_t factor = ctx->generator[i * ctx->k + j];
if (!factor) continue;
const uint8_t *source = blocks[j] + RS_HEADER_SIZE;
for (size_t b = 0; b < payload_size; b++) payload[b] ^= rs_multiply(ctx, factor, source[b]);
}
}
*block_size = RS_HEADER_SIZE + payload_size;
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "reed_solomon: encoded seq=%llu n=%u r=%u length=%zu padding=%zu block_size=%zu max=%zu",
(unsigned long long)seq, ctx->n, ctx->n - ctx->k, length, ctx->k * payload_size - length,
*block_size, ctx->max_block_size);
return RS_OK;
}
/* Проверить всю пачку и восстановить только отсутствующие исходные субблоки. */
int rs_decode(struct rs_codec *ctx, const uint8_t *const blocks[], size_t block_size,
uint8_t *output, size_t output_capacity) {
if (!ctx || !blocks || !output || block_size <= RS_HEADER_SIZE || block_size > ctx->max_block_size
|| output_capacity < ctx->k * (block_size - RS_HEADER_SIZE)) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: invalid decode ctx=%p blocks=%p output=%p block_size=%zu capacity=%zu",
(void *)ctx, (const void *)blocks, (void *)output, block_size, output_capacity);
return RS_ERR_INVALID_ARG;
}
size_t payload_size = block_size - RS_HEADER_SIZE;
const uint8_t *rows[RS_MAX_BLOCKS];
unsigned selected[RS_MAX_BLOCKS];
unsigned count = 0;
unsigned missing = 0;
uint64_t seq = 0;
for (unsigned i = 0; i < ctx->n; i++) {
if (!blocks[i]) {
if (i < ctx->k) missing++;
continue;
}
uint64_t block_seq = 0;
for (unsigned j = 0; j < 8; j++) block_seq = (block_seq << 8) | blocks[i][j];
if (blocks[i][8] != i || (count && block_seq != seq)) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: invalid header slot=%u subblock=%u seq=%llu expected_seq=%llu",
i, blocks[i][8], (unsigned long long)block_seq, (unsigned long long)(count ? seq : block_seq));
return RS_ERR_HEADER;
}
seq = block_seq;
if (count < ctx->k) {
selected[count] = i;
rows[count] = ctx->generator + i * ctx->k;
}
count++;
}
if (count < ctx->k) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "reed_solomon: insufficient blocks seq=%llu received=%u required=%u n=%u",
(unsigned long long)seq, count, ctx->k, ctx->n);
return RS_ERR_INSUFFICIENT;
}
if (missing) {
int rc = rs_invert(ctx, rows);
if (rc != RS_OK) return rc;
}
for (unsigned i = 0; i < ctx->k; i++) {
uint8_t *target = output + i * payload_size;
if (blocks[i]) {
memcpy(target, blocks[i] + RS_HEADER_SIZE, payload_size);
continue;
}
memset(target, 0, payload_size);
for (unsigned j = 0; j < ctx->k; j++) {
uint8_t factor = ctx->work[i * 2 * ctx->k + ctx->k + j];
if (!factor) continue;
const uint8_t *source = blocks[selected[j]] + RS_HEADER_SIZE;
for (size_t b = 0; b < payload_size; b++) target[b] ^= rs_multiply(ctx, factor, source[b]);
}
DEBUG_TRACE(DEBUG_CATEGORY_GENERAL, "reed_solomon: recovered seq=%llu subblock=%u", (unsigned long long)seq, i);
}
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "reed_solomon: decoded seq=%llu received=%u recovered=%u output_size=%zu",
(unsigned long long)seq, count, missing, ctx->k * payload_size);
return RS_OK;
}
+73
View File
@@ -0,0 +1,73 @@
/**
* Reed–Solomon: кодирование и восстановление одной пачки по известным пропускам.
* rs_init задаёт n кодограмм, r проверочных и максимальный размер каждой кодограммы.
* Первые n-r кодограмм содержат исходные данные, остальные — проверочные.
* Формат: seq (8 байт, big-endian), subblock (1 байт), полезные данные.
* blocks[i] всегда соответствует subblock=i; при decode NULL обозначает пропуск.
* Любые n-r присутствующих кодограмм восстанавливают всю исходную пачку.
* Полученные данные и заголовки должны быть проверены транспортом на повреждения.
* Все входные/выходные буферы принадлежат вызывающему коду и не перекрываются.
* Кодек выделяет рабочую память только при init; один контекст используют последовательно.
* encode принимает действительную длину, выбирает минимальный одинаковый размер частей
* и дополняет хвост максимум n-r-1 нулями. Размер и исходную длину хранит вызывающий код.
*/
#ifndef REED_SOLOMON_H
#define REED_SOLOMON_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define RS_HEADER_SIZE 9
#define RS_MAX_BLOCK_SIZE 4096
#define RS_MAX_BLOCKS 255
enum rs_result {
RS_OK = 0,
RS_ERR_INVALID_ARG = -1,
RS_ERR_MEMORY = -2,
RS_ERR_INSUFFICIENT = -3,
RS_ERR_HEADER = -4,
RS_ERR_MATRIX = -5
};
struct rs_codec;
/* Создать кодек: 1 <= n <= 255, 0 <= r < n, 10 <= max_block_size <= 4096.
* ctx — выходной указатель; при ошибке *ctx=NULL. Старый контекст сначала освобождают.
* Возвращает RS_OK или отрицательный rs_result.
*/
int rs_init(struct rs_codec **ctx, unsigned n, unsigned r, size_t max_block_size);
/* Освободить контекст и рабочую память; NULL допустим. */
void rs_free(struct rs_codec *ctx);
/* Максимальная длина входа encode: (n-r)*(max_block_size-RS_HEADER_SIZE).
* Для NULL возвращает 0 и логирует ошибку.
*/
size_t rs_input_capacity(const struct rs_codec *ctx);
/* Читать 0 < length <= rs_input_capacity(ctx) байт input, записать n кодограмм.
* blocks — n ненулевых указателей на буферы ёмкостью max_block_size каждый.
* block_size получает RS_HEADER_SIZE+ceil(length/(n-r)); хвост за этим размером не меняется.
* При ошибке аргументов выходные буферы и block_size не изменяются.
*/
int rs_encode(struct rs_codec *ctx, uint64_t seq, const uint8_t *input, size_t length,
uint8_t *const blocks[], size_t *block_size);
/* Восстановить (n-r)*(block_size-RS_HEADER_SIZE) байт, включая нулевой хвост.
* Ненулевые буферы содержат block_size байт; NULL обозначает пропуск. Размер общий для пачки.
* output_capacity проверяется до записи; block_size не превышает максимум контекста.
* Проверяет одинаковый seq и subblock=i у всех присутствующих кодограмм.
* Входные буферы не изменяются. При ошибке output не изменяется.
*/
int rs_decode(struct rs_codec *ctx, const uint8_t *const blocks[], size_t block_size,
uint8_t *output, size_t output_capacity);
#ifdef __cplusplus
}
#endif
#endif
+29 -10
View File
@@ -22,17 +22,24 @@
* • Все динамические данные (строки, массивы, списки) выделяются через u_malloc.
* • При encode длина переменных полей кодируется всегда 2 байтами (uint16_t, big-endian).
* • Для linked list в буфере сохраняется только количество узлов + сырые данные узлов
* (next-указатели НЕ сериализуются, они восстанавливаются при decode).
* (включая байты next/padding; decode заменяет next восстановленными ссылками).
* • serialize_decode принимает буфер БЕЗ заголовка.
* • serialize_free освобождает ВСЮ структуру и все вложенные динамические объекты.
* • Поля в schema.fields должны идти в порядке возрастания offset.
* • max_size (если > 0) — жёсткий лимит размера выходного буфера.
* • serialize_free освобождает структуру, динамические поля схемы и узлы списков; обнуляет указатель на структуру.
* Указатели внутри элементов массивов и узлов списков рекурсивно не освобождаются.
* • Поля кодируются в порядке schema.fields; фиксированные данные копируются без смены endian.
* • max_size (если > 0) — лимит encode; header при header_len > 0 обязателен.
* • При encode counts больше 65535 молча усекаются: сохраняются первые 65535 элементов массива/списка или байтов строки.
* • elem_size=1 добавляет NUL и для byte-массивов.
* • Linked nodes копируются целиком, без рекурсивного кодирования вложенных указателей.
* • Этот формат зависит от ABI сырых полей; сериализация не нормализует размер/выравнивание структур.
* • Схема доверенная: offsets, типы, размеры и выравнивание полей/next проверяет её автор, не decode.
* • Независимые операции не используют общее изменяемое состояние; доступ к одной структуре согласует владелец.
*
* @example
* // 1. Описание структуры
* typedef struct Node {
* uint32_t value;
* struct Node* next; // next по offset = 4
* struct Node* next; // положение задаётся offsetof(Node, next)
* } Node;
*
* typedef struct {
@@ -100,7 +107,7 @@ struct SerializeField {
uint16_t offset; ///< смещение поля в структуре
uint8_t elem_size; ///< размер элемента (для fixed = размер данных, для array/linked = размер одного элемента)
uint8_t type; ///< SERIALIZE_TYPE_*
uint16_t len_offset; ///< смещение поля-счётчика (игнорируется для type 0,1,2)
uint16_t len_offset; ///< type 3–5: смещение счётчика в структуре; type 6: смещение next в узле; type 0–2: игнорируется
};
/**
@@ -111,25 +118,37 @@ struct SerializeSchema {
uint16_t struct_size; ///< размер основной структуры
uint16_t max_size; ///< максимальный размер сериализованных данных (0 = без ограничения)
uint16_t header_len; ///< длина заголовка
const struct SerializeField* fields;
const struct SerializeField* fields; ///< field_count описателей в порядке записи; схема заимствуется на время вызова
};
#define SERIALIZE_ERR_OK 0 ///< успех
#define SERIALIZE_ERR_BUF -1 ///< ошибка выделения памяти
#define SERIALIZE_ERR_BUF -1 ///< нехватка памяти; decode также: обрезанные данные или неизвестный тип поля
#define SERIALIZE_ERR_NULL -2 ///< NULL-указатель
#define SERIALIZE_ERR_SIZE -3 ///< превышен max_size
#define SERIALIZE_ERR_NOTSUP -4 ///< неподдерживаемый тип
#define SERIALIZE_ERR_SIZE -3 ///< encode: превышен max_size
#define SERIALIZE_ERR_NOTSUP -4 ///< encode: неподдерживаемый тип поля
/* Кодирует structure по schema; при header_len > 0 header указывает минимум на header_len байт.
* При успехе *out_buf — новый блок u_malloc (освободить u_free), *out_len — фактически записанная длина с header.
* Входная структура и её вложенные данные остаются у владельца. На ошибке выходные значения не меняются.
* Возврат: OK, NULL (обязательные аргументы), BUF (OOM), SIZE (max_size), NOTSUP (неизвестный тип).
* ASCIIZ требует elem_size=1; порядок fields задаёт формат, сортировка по offset не требуется. */
int serialize_encode(const void* structure,
const struct SerializeSchema* schema,
const uint8_t* header,
uint8_t** out_buf,
size_t* out_len);
/* Декодирует in_len байт БЕЗ header: передавать buf + header_len, len - header_len.
* При успехе *structure — новая структура с выделенными полями; очищать serialize_free с той же схемой.
* При ошибке частично созданные объекты освобождаются, *structure = NULL. Старый *structure не освобождает.
* max_size не ограничивает decode; хвост после последнего поля игнорируется.
* Возврат: OK, NULL (аргументы), BUF (OOM, обрезанные данные или неизвестный тип). */
int serialize_decode(const uint8_t* in_buf, size_t in_len,
const struct SerializeSchema* schema,
void** structure);
// Освобождает выделенную через u_malloc структуру, поля схемы и узлы списков, обнуляет *structure; NULL допустим.
// Вложенные указатели внутри элементов массивов/узлов не обходит; к структуре на стеке применять нельзя.
void serialize_free(const struct SerializeSchema* schema, void** structure);
-117
View File
@@ -1,117 +0,0 @@
# Binary Serialization Library
## 1. Назначение
Библиотека бинарной сериализации C-структур с динамическими полями (строки, массивы, singly-linked списки) в компактный бинарный буфер и обратно. Предназначена для кодирования/декодирования сообщений сетевого протокола (контрольный сервер, синхронизация БД, обмен конфигурацией).
Что сериализуется:
- Фиксированные поля (целые, флаги, структуры) — копируются «как есть».
- ASCIIZ-строки (`char*`) — длина определяется через `strlen`, сохраняется с терминальным нулём.
- Динамические массивы (`uint8_t*`) — счётчик элементов в поле `UINT8`/`UINT16`/`UINT32` или ровно 1 элемент (`ARRAY_FIXED`).
- Singly-linked списки — `next`-указатели не сериализуются, сохраняются только `count+data`.
Формат: header (`header_len` байт, передаётся отдельно) + данные полей. Длина переменных полей кодируется 2 байтами (`uint16_t`, big-endian).
Потокобезопасность: нет, однопоточное использование.
## 2. Как пользоваться
```c
#include "../lib/serialize.h"
// --- 1. Определяем структуру ---
typedef struct Node {
uint32_t id;
struct Node *next;
} Node;
typedef struct {
uint8_t version;
uint8_t name_len;
char *name; // ASCIIZ (elem_size=1)
uint16_t addrs_cnt;
uint8_t *addrs; // массив байт (elem_size=1)
Node *list; // linked list
} Message;
// --- 2. Описываем схему ---
static const struct SerializeField msg_fields[] = {
{offsetof(Message, version), sizeof(uint8_t), SERIALIZE_TYPE_FIXED, 0},
{offsetof(Message, name), 1, SERIALIZE_TYPE_ARRAY_U8, offsetof(Message, name_len)},
{offsetof(Message, addrs), 1, SERIALIZE_TYPE_ARRAY_U16, offsetof(Message, addrs_cnt)},
{offsetof(Message, list), sizeof(Node), SERIALIZE_TYPE_LINKED, offsetof(Node, next)}
};
static const struct SerializeSchema msg_schema = {
.field_count = 4,
.struct_size = sizeof(Message),
.max_size = 4096, // 0 = без лимита
.header_len = 4, // байты заголовка (напр. версия протокола)
.fields = msg_fields
};
// --- 3. Сериализация ---
Message msg = { .version = 1, .name = "test", .name_len = 4, .list = NULL };
msg.addrs_cnt = 2;
msg.addrs = u_malloc(2); msg.addrs[0] = 0xAA; msg.addrs[1] = 0xBB;
uint8_t header[] = {0x01, 0x00, 0x00, 0x00}; // 4-байтный заголовок
uint8_t *buf = NULL;
size_t len;
if (serialize_encode(&msg, &msg_schema, header, &buf, &len) != SERIALIZE_ERR_OK)
goto fail;
// buf содержит: header (4) + version (1) + [len=2]name(4) + '\0' + [len=2]addrs(2)
// --- 4. Десериализация (buf без заголовка!) ---
Message *restored = NULL;
uint8_t *data_ptr = buf + msg_schema.header_len; // пропускаем header
if (serialize_decode(data_ptr, len - msg_schema.header_len, &msg_schema, (void**)&restored) != SERIALIZE_ERR_OK)
goto fail;
// restored->name == "test", restored->name_len == 4
// restored->addrs[0] == 0xAA, restored->addrs_cnt == 2
// restored->list == NULL (список пуст — не падает)
// --- 5. Очистка ---
serialize_free(&msg_schema, (void**)&restored); // освобождает всё: name, addrs, list, структуру
u_free(buf);
```
**Ключевые нюансы:**
- Поля в `schema.fields` должны идти в порядке возрастания `offset` в структуре.
- `serialize_decode` принимает буфер **без заголовка** — передаётся `buf + schema.header_len`.
- `serialize_free` освобождает структуру и все вложенные динамические поля (строки, массивы, списки).
- При ошибке `serialize_decode` **автоматически освобождает** всю частично выделенную память → `*structure = NULL`.
- `max_size > 0` — жёсткий лимит, при превышении `SERIALIZE_ERR_SIZE`.
- Длина переменных полей всегда 2 байта (максимум 65535 элементов).
- ASCIIZ-строки (`elem_size=1`) сохраняются с терминальным `\0`; пустая строка → 1 байт (`\0`).
- Для linked list поле `len_offset` указывает смещение `next`-указателя внутри узла.
- Передавать буфер в `serialize_decode` **без заголовка** — он не знает про `header_len`.
## 3. API
| Функция | Назначение |
|---------|-----------|
| `serialize_encode(structure, schema, header, &buf, &len)` | Кодирует структуру. `header` копируется в начало `buf`. |
| `serialize_decode(in_buf, in_len, schema, &structure)` | Декодирует буфер **без заголовка**. При ошибке всё освобождает. |
| `serialize_free(schema, &structure)` | Рекурсивно освобождает структуру и динамические поля, затем обнуляет указатель. |
**Типы полей (`SERIALIZE_TYPE_*`):**
| Тип | `data` | `elem_size` | `len_offset` |
|-----|--------|-------------|--------------|
| `FIXED` (0) | Встроенные данные | Размер поля | Не исп. |
| `ASCIIZ` (1) | `char*` | 1 | Не исп. (длина = `strlen`) |
| `ARRAY_FIXED` (2) | `uint8_t*` (ровно 1 элемент) | Размер элемента | Не исп. |
| `ARRAY_U8` (3) | `uint8_t*` | Размер элемента | Смещение `uint8_t`-счётчика |
| `ARRAY_U16` (4) | `uint8_t*` | Размер элемента | Смещение `uint16_t`-счётчика |
| `ARRAY_U32` (5) | `uint8_t*` | Размер элемента | Смещение `uint32_t`-счётчика |
| `LINKED` (6) | `void*` (голова списка) | Размер узла | Смещение `next` внутри узла |
**Коды возврата:**
- `SERIALIZE_ERR_OK (0)` — успех.
- `SERIALIZE_ERR_BUF (-1)` — ошибка выделения памяти.
- `SERIALIZE_ERR_NULL (-2)` — NULL-указатель во входных параметрах.
- `SERIALIZE_ERR_SIZE (-3)` — превышен `max_size`.
- `SERIALIZE_ERR_NOTSUP (-4)` — неподдерживаемый тип поля.
+312
View File
@@ -0,0 +1,312 @@
#include "silero_vad.h"
#include "debug_config.h"
#include "mem.h"
#ifdef HAVE_SILERO_VAD
#include <stdio.h>
#include <string.h>
#include <onnxruntime/onnxruntime_c_api.h>
#include "silero_vad_model.inc"
/* Число float-элементов рекуррентного состояния модели: 2 * 1 * 128 */
#define SILERO_VAD_STATE_COUNT 256
/* ONNX-вход: 64 отсчёта предыдущего окна + 512 новых отсчётов @16кГц. */
#define SILERO_VAD_CONTEXT_SAMPLES 64
#define SILERO_VAD_INPUT_SAMPLES (SILERO_VAD_CONTEXT_SAMPLES + SILERO_VAD_WINDOW_SAMPLES)
struct silero_vad {
const OrtApi* ort;
OrtEnv* env;
OrtSession* session;
OrtMemoryInfo* mem_info;
float state[SILERO_VAD_STATE_COUNT]; /* рекуррентное состояние между вызовами */
float context[SILERO_VAD_CONTEXT_SAMPLES]; /* звуковой контекст предыдущего окна */
uint8_t* model_data; /* байты .onnx, загруженные в память (для file-версии) */
size_t model_len;
};
/* Освободить OrtStatus и залогировать его сообщение как ошибку */
static void vad_report_ort_error(const OrtApi* ort, OrtStatus* status, const char* what) {
const char* msg = ort->GetErrorMessage(status);
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "%s: %s", what, msg ? msg : "unknown onnxruntime error");
ort->ReleaseStatus(status);
}
/* Прочитать файл целиком в буфер. 0 при успехе: буфер пишется в out/out_len (u_malloc). */
static int vad_read_file(const char* path, uint8_t** out, size_t* out_len) {
FILE* f;
long size;
uint8_t* buf;
*out = NULL;
*out_len = 0;
f = fopen(path, "rb");
if (!f) {
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "vad_read_file: cannot open model '%s'", path);
return -1;
}
if (fseek(f, 0, SEEK_END) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "vad_read_file: fseek failed on '%s'", path);
fclose(f);
return -1;
}
size = ftell(f);
if (size <= 0) {
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "vad_read_file: empty or unreadable model '%s'", path);
fclose(f);
return -1;
}
rewind(f);
buf = (uint8_t*)u_malloc((size_t)size);
if (!buf) {
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "vad_read_file: OOM for %ld bytes", size);
fclose(f);
return -1;
}
if (fread(buf, 1, (size_t)size, f) != (size_t)size) {
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "vad_read_file: short read on '%s'", path);
u_free(buf);
fclose(f);
return -1;
}
fclose(f);
*out = buf;
*out_len = (size_t)size;
return 0;
}
/* Общая инициализация детектора из байтов модели (файл или вшитая модель).
* owned_buf передаётся детектору только при успехе; при ошибке остаётся у вызывающего.
* Для вшитой модели owned_buf == NULL, её байты не освобождаются. */
static silero_vad_t* silero_vad_create_from_bytes(const uint8_t* data, size_t len,
const uint8_t* owned_buf, const char* label) {
silero_vad_t* vad = NULL;
const OrtApi* ort;
OrtStatus* status;
OrtSessionOptions* opts = NULL;
if (!data || len == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_create(%s): empty model", label);
return NULL;
}
ort = OrtGetApiBase() ? OrtGetApiBase()->GetApi(ORT_API_VERSION) : NULL;
if (!ort) {
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_create(%s): onnxruntime C API unavailable", label);
return NULL;
}
vad = (silero_vad_t*)u_calloc(1, sizeof(*vad));
if (!vad) {
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_create(%s): OOM", label);
return NULL;
}
vad->ort = ort;
vad->model_len = len;
status = ort->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "utun_silero_vad", &vad->env);
if (status) { vad_report_ort_error(ort, status, "CreateEnv"); goto fail; }
status = ort->CreateSessionOptions(&opts);
if (status) { vad_report_ort_error(ort, status, "CreateSessionOptions"); goto fail; }
status = ort->SetIntraOpNumThreads(opts, 1);
if (status) { vad_report_ort_error(ort, status, "SetIntraOpNumThreads"); }
status = ort->SetSessionGraphOptimizationLevel(opts, ORT_ENABLE_ALL);
if (status) { vad_report_ort_error(ort, status, "SetSessionGraphOptimizationLevel"); }
status = ort->CreateSessionFromArray(vad->env, data, len, opts, &vad->session);
if (status) { vad_report_ort_error(ort, status, "CreateSessionFromArray"); goto fail; }
status = ort->CreateCpuMemoryInfo(OrtArenaAllocator, OrtMemTypeDefault, &vad->mem_info);
if (status) { vad_report_ort_error(ort, status, "CreateCpuMemoryInfo"); goto fail; }
ort->ReleaseSessionOptions(opts);
vad->model_data = (uint8_t*)owned_buf;
DEBUG_INFO(DEBUG_CATEGORY_VAD, "silero_vad_create(%s): ok bytes=%zu window=%d context=%d input=%d", label, len,
SILERO_VAD_WINDOW_SAMPLES, SILERO_VAD_CONTEXT_SAMPLES, SILERO_VAD_INPUT_SAMPLES);
return vad;
fail:
if (opts) ort->ReleaseSessionOptions(opts);
silero_vad_destroy(vad);
return NULL;
}
silero_vad_t* silero_vad_create(const char* model_path) {
uint8_t* buf = NULL;
size_t len = 0;
silero_vad_t* vad;
if (!model_path || !*model_path) {
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_create: empty model path");
return NULL;
}
if (vad_read_file(model_path, &buf, &len) != 0) return NULL;
vad = silero_vad_create_from_bytes(buf, len, buf, model_path);
if (!vad) u_free(buf);
return vad;
}
silero_vad_t* silero_vad_create_default(void) {
return silero_vad_create_from_bytes(silero_vad_model_data, silero_vad_model_len, NULL, "embedded");
}
void silero_vad_destroy(silero_vad_t* vad) {
if (!vad) return;
const OrtApi* ort = vad->ort;
if (ort) {
if (vad->mem_info) ort->ReleaseMemoryInfo(vad->mem_info);
if (vad->session) ort->ReleaseSession(vad->session);
if (vad->env) ort->ReleaseEnv(vad->env);
}
if (vad->model_data) u_free(vad->model_data);
DEBUG_DEBUG(DEBUG_CATEGORY_VAD, "silero_vad_destroy");
u_free(vad);
}
void silero_vad_reset(silero_vad_t* vad) {
if (!vad) return;
memset(vad->state, 0, sizeof(vad->state));
memset(vad->context, 0, sizeof(vad->context));
DEBUG_DEBUG(DEBUG_CATEGORY_VAD, "silero_vad_reset: recurrent state and audio context cleared");
}
int silero_vad_process(silero_vad_t* vad, const float* samples, float* prob) {
const OrtApi* ort;
OrtStatus* status;
OrtValue* in_input = NULL;
OrtValue* in_state = NULL;
OrtValue* in_sr = NULL;
OrtValue* out_prob = NULL;
OrtValue* out_state = NULL;
const char* input_names[3];
const char* output_names[2];
const OrtValue* inputs[3];
OrtValue* outputs[2];
int64_t shape_input[2] = {1, SILERO_VAD_INPUT_SAMPLES};
int64_t shape_state[3] = {2, 1, 128};
int64_t shape_prob[2] = {1, 1};
int64_t sr_value = SILERO_VAD_SAMPLE_RATE;
float prob_local = 0.0f;
float next_state[SILERO_VAD_STATE_COUNT];
float input[SILERO_VAD_INPUT_SAMPLES];
int rc = -1;
if (!vad || !samples || !prob) {
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_process: invalid args");
return -1;
}
ort = vad->ort;
memcpy(input, vad->context, sizeof(vad->context));
memcpy(input + SILERO_VAD_CONTEXT_SAMPLES, samples, SILERO_VAD_WINDOW_SAMPLES * sizeof(float));
status = ort->CreateTensorWithDataAsOrtValue(vad->mem_info, input,
sizeof(input), shape_input, 2,
ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, &in_input);
if (status) { vad_report_ort_error(ort, status, "input tensor"); return -1; }
status = ort->CreateTensorWithDataAsOrtValue(vad->mem_info, vad->state,
SILERO_VAD_STATE_COUNT * sizeof(float), shape_state, 3,
ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, &in_state);
if (status) { vad_report_ort_error(ort, status, "state tensor"); goto done; }
status = ort->CreateTensorWithDataAsOrtValue(vad->mem_info, &sr_value,
sizeof(sr_value), NULL, 0,
ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64, &in_sr);
if (status) { vad_report_ort_error(ort, status, "sr tensor"); goto done; }
status = ort->CreateTensorWithDataAsOrtValue(vad->mem_info, &prob_local,
sizeof(float), shape_prob, 2,
ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, &out_prob);
if (status) { vad_report_ort_error(ort, status, "output tensor"); goto done; }
status = ort->CreateTensorWithDataAsOrtValue(vad->mem_info, next_state,
SILERO_VAD_STATE_COUNT * sizeof(float), shape_state, 3,
ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, &out_state);
if (status) { vad_report_ort_error(ort, status, "stateN tensor"); goto done; }
input_names[0] = "input";
input_names[1] = "state";
input_names[2] = "sr";
inputs[0] = in_input;
inputs[1] = in_state;
inputs[2] = in_sr;
output_names[0] = "output";
output_names[1] = "stateN";
outputs[0] = out_prob;
outputs[1] = out_state;
status = ort->Run(vad->session, NULL, input_names, inputs, 3, output_names, 2, outputs);
if (status) {
vad_report_ort_error(ort, status, "Run");
goto done;
}
memcpy(vad->state, next_state, sizeof(next_state));
memcpy(vad->context, input + SILERO_VAD_INPUT_SAMPLES - SILERO_VAD_CONTEXT_SAMPLES, sizeof(vad->context));
*prob = prob_local;
rc = 0;
done:
if (in_input) ort->ReleaseValue(in_input);
if (in_state) ort->ReleaseValue(in_state);
if (in_sr) ort->ReleaseValue(in_sr);
if (out_prob) ort->ReleaseValue(out_prob);
if (out_state) ort->ReleaseValue(out_state);
return rc;
}
int silero_vad_process_pcm16(silero_vad_t* vad, const int16_t* pcm, float* prob) {
float samples[SILERO_VAD_WINDOW_SAMPLES];
int i;
if (!pcm) {
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_process_pcm16: pcm == NULL");
return -1;
}
for (i = 0; i < SILERO_VAD_WINDOW_SAMPLES; i++) {
samples[i] = (float)pcm[i] / 32768.0f;
}
return silero_vad_process(vad, samples, prob);
}
#else /* !HAVE_SILERO_VAD — пустые стабы (сборки без onnxruntime) */
silero_vad_t* silero_vad_create(const char* model_path) {
(void)model_path;
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_create: built without onnxruntime (HAVE_SILERO_VAD not defined)");
return NULL;
}
silero_vad_t* silero_vad_create_default(void) {
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_create_default: built without onnxruntime (HAVE_SILERO_VAD not defined)");
return NULL;
}
void silero_vad_destroy(silero_vad_t* vad) {
(void)vad;
}
void silero_vad_reset(silero_vad_t* vad) {
(void)vad;
}
int silero_vad_process(silero_vad_t* vad, const float* samples, float* prob) {
(void)vad; (void)samples; (void)prob;
return -1;
}
int silero_vad_process_pcm16(silero_vad_t* vad, const int16_t* pcm, float* prob) {
(void)vad; (void)pcm; (void)prob;
return -1;
}
#endif /* HAVE_SILERO_VAD */
+77
View File
@@ -0,0 +1,77 @@
/**
* Silero VAD (Voice Activity Detector) — C-обёртка над ONNX Runtime.
*
* Запускает официальную стриминговую модель silero_vad.onnx (v5, ~2.3 МБ),
* которая по окну аудио возвращает вероятность наличия речи [0..1].
* Рекуррентное состояние модели хранится внутри объекта и переносится между
* вызовами. Обёртка также хранит 64 последних отсчёта аудио и добавляет их
* перед новым окном: ONNX получает 576 отсчётов, публичный API принимает 512.
*
* Требования к входному аудио:
* - частота дискретизации 16 кГц (модель поддерживает только 16k);
* - моно;
* - окно ровно SILERO_VAD_WINDOW_SAMPLES = 512 сэмплов (32 мс);
* - float-сэмплы в диапазоне примерно [-1;1] (для int16 есть
* silero_vad_process_pcm16, который сам делит на 32768).
*
* Порог и гистерезис (задержки начала/конца речи) намеренно оставлены
* вызывающему — радио/звонок сами решают, как интерпретировать вероятность.
*
* Зависимость: libonnxruntime (C API). Сборка включается опцией
* `--with-silero-vad` (см. configure.ac) или макросом HAVE_SILERO_VAD в
* CMake-сборках. Без HAVE_SILERO_VAD модуль компилируется в пустые стабы
* (все create-функции возвращают NULL, process — ошибку), чтобы исходники lib/
* можно было GLOB-ить во всех сборках (Android/chatgui) без onnxruntime.
*
* Лицензия модели: MIT (Silero Team, https://github.com/snakers4/silero-vad).
*/
#ifndef SILERO_VAD_H
#define SILERO_VAD_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Фиксированное окно стриминговой модели: 512 сэмплов @ 16 кГц = 32 мс */
#define SILERO_VAD_WINDOW_SAMPLES 512
#define SILERO_VAD_SAMPLE_RATE 16000
typedef struct silero_vad silero_vad_t;
/**
* Создать детектор, загрузив ONNX-модель из файла model_path.
* Возвращает NULL при ошибке (подробности — в лог категории "vad").
*/
silero_vad_t* silero_vad_create(const char* model_path);
/**
* Создать детектор из модели, вшитой в бинарник (silero_vad_model_data в
* silero_vad.c). Не требует файла на диске. NULL при ошибке.
*/
silero_vad_t* silero_vad_create_default(void);
/* Освободить детектор. NULL безопасен. */
void silero_vad_destroy(silero_vad_t* vad);
/* Сбросить рекуррентное состояние и звуковой контекст (начало нового потока / разрыв захвата). */
void silero_vad_reset(silero_vad_t* vad);
/**
* Обработать окно из 512 float-сэмплов @ 16 кГц (примерно [-1;1]).
* При успехе возвращает 0 и заполняет *prob вероятностью речи [0..1];
* при ошибке — отрицательное значение (проб не трогается).
*/
int silero_vad_process(silero_vad_t* vad, const float* samples, float* prob);
/**
* То же, но вход — int16 PCM: сэмплы преобразуются к float делением на 32768.
*/
int silero_vad_process_pcm16(silero_vad_t* vad, const int16_t* pcm, float* prob);
#ifdef __cplusplus
}
#endif
#endif /* SILERO_VAD_H */
Binary file not shown.
File diff suppressed because it is too large. Load diff
+35
View File
@@ -4,6 +4,7 @@
#include "socket_compat.h"
#include "debug_config.h"
#include <limits.h>
#ifdef _WIN32
// ==================== Windows Implementation ====================
@@ -211,6 +212,27 @@ int socket_set_mark(socket_t sock, int mark) {
#endif
}
/* Ошибки send остаются у вызывающего; закрытый пир не завершает процесс сигналом. */
ssize_t socket_send(socket_t sock, const void* buf, size_t len) {
#ifdef _WIN32
if (len > INT_MAX) { WSASetLastError(WSAEMSGSIZE); return SOCKET_ERROR_CODE; }
return send(sock, (const char*)buf, (int)len, 0);
#elif defined(MSG_NOSIGNAL)
return send(sock, buf, len, MSG_NOSIGNAL);
#elif defined(SO_NOSIGPIPE)
int enabled = 1;
if (setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &enabled, sizeof(enabled)) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "cannot disable SIGPIPE for socket=%d: %s", (int)sock, strerror(errno));
return SOCKET_ERROR_CODE;
}
return send(sock, buf, len, 0);
#else
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "safe TCP send is unsupported on this platform");
errno = ENOTSUP;
return SOCKET_ERROR_CODE;
#endif
}
ssize_t socket_sendto(socket_t sock, const void* buf, size_t len,
const struct sockaddr* dest, socklen_t dest_len) {
#ifdef _WIN32
@@ -246,3 +268,16 @@ uint16_t ss_get_port(const struct sockaddr_storage *addr) {
return 0;
}
uint16_t socket_get_bound_port(socket_t sock) {
struct sockaddr_storage address; socklen_t size = sizeof(address);
if (getsockname(sock, (struct sockaddr*)&address, &size) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "getsockname fd=%llu failed: %s",
(unsigned long long)sock, socket_strerror(socket_get_error()));
return 0;
}
uint16_t port = ss_get_port(&address);
if (!port) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socket fd=%llu family=%d has no bound port",
(unsigned long long)sock, address.ss_family);
return port;
}
+26 -8
View File
@@ -1,6 +1,9 @@
/**
* Socket compatibility layer for cross-platform support (POSIX / Windows)
* MSYS2 UCRT64 compatible
* Единые socket_t, коды ошибок и wrappers сокетов POSIX/Windows (включая MSYS2 UCRT64).
* platform_init/cleanup обслуживают Winsock; UDP/nonblocking/options/sendto/recvfrom — операции ОС.
* Созданные сокеты закрывает владелец через socket_close_wrapper; event loop находится в u_async.h.
* Использование: platform_init → create_udp → set_nonblocking/options → sendto/recvfrom → close → cleanup.
* До регистрации в uasync вызывающий должен включить nonblocking. Winsock init/cleanup согласовать между потоками.
*/
#ifndef SOCKET_COMPAT_H
@@ -44,13 +47,16 @@ extern "C" {
#define ERR_INTR EINTR
#endif
// Platform initialization/cleanup
// POSIX: 0 без системной инициализации. Windows: WSAStartup при первом init, счётчик вложенности; 0 / -1.
int socket_platform_init(void);
// Парный cleanup: Windows уменьшает счётчик и вызывает WSACleanup на нуле; POSIX ничего не освобождает.
void socket_platform_cleanup(void);
// Socket operations
// UDP-сокет для family (обычно AF_INET/AF_INET6); SOCKET_INVALID при ошибке. Владение передаётся вызывающему.
socket_t socket_create_udp(int family);
// Включает nonblocking: 0 — успех, -1 — ошибка.
int socket_set_nonblocking(socket_t sock);
/* Закрыть сокет средствами ОС; на POSIX sock=0 оставляет дескриптор открытым и возвращает 0. */
int socket_close_wrapper(socket_t sock);
static inline int socket_get_error(void) {
#ifdef _WIN32
@@ -60,23 +66,35 @@ static inline int socket_get_error(void) {
#endif
}
// Socket options
// Пробует установить оба буфера SO_SNDBUF/SO_RCVBUF: 0 при успехе обоих, -1 при любой ошибке; отката нет.
int socket_set_buffers(socket_t sock, int sndbuf, int rcvbuf);
// Устанавливает SO_REUSEADDR в значение reuse; 0 / -1.
int socket_set_reuseaddr(socket_t sock, int reuse);
// SO_BINDTODEVICE для строки ifname (не NULL); 0 / -1. Без поддержки опции, в том числе Windows, возвращает -1.
int socket_bind_to_device(socket_t sock, const char* ifname);
// SO_MARK в значение mark; 0 / -1. Без поддержки опции, в том числе Windows, возвращает -1.
int socket_set_mark(socket_t sock, int mark);
// I/O operations
/* Один TCP send без SIGPIPE и изменения обработчиков процесса. Буфер заимствован;
* возвращает число байт либо SOCKET_ERROR_CODE, включая EAGAIN. Ошибку логирует владелец.
* MSG_NOSIGNAL либо SO_NOSIGPIPE; без поддержки возвращает ENOTSUP. Windows: len <= INT_MAX. */
ssize_t socket_send(socket_t sock, const void* buf, size_t len);
// Один sendto/recvfrom с flags=0: число байт или SOCKET_ERROR_CODE; errno/WSA-код читать через socket_get_error.
// буферами владеет вызывающий; для Windows len должен помещаться в int. dest_len — размер адреса назначения;
// src_len при приёме — входная ёмкость/выходная длина адреса (может быть NULL по правилам recvfrom ОС).
ssize_t socket_sendto(socket_t sock, const void* buf, size_t len,
const struct sockaddr* dest, socklen_t dest_len);
ssize_t socket_recvfrom(socket_t sock, void* buf, size_t len,
struct sockaddr* src, socklen_t* src_len);
// Utility
// Заимствованный текст ошибки: Windows — один статический буфер (перезаписывается следующим вызовом), POSIX — strerror.
const char* socket_strerror(int err);
// взять порт из sockaddr_storage
// IPv4/IPv6-порт в host byte order; 0 при NULL или другом семействе.
uint16_t ss_get_port(const struct sockaddr_storage *addr);
// Фактический IPv4/IPv6-порт уже привязанного сокета; 0 при ошибке.
uint16_t socket_get_bound_port(socket_t sock);
#ifdef __cplusplus
-99
View File
@@ -1,99 +0,0 @@
# socket_compat — Cross-platform socket abstraction
## 1. Назначение
Унифицирует работу с UDP-сокетами между POSIX и Windows (MSYS2 UCRT64). Скрывает различия в типах дескрипторов (`int` vs `SOCKET`), инициализации подсистемы (WSAStartup/WSACleanup), кодах ошибок и сигнатурах системных вызовов.
## 2. Как пользоваться
### Инициализация (однократно при старте)
```c
if (socket_platform_init() != 0) { /* фатальная ошибка */ }
// ... работа с сокетами ...
socket_platform_cleanup(); // при завершении
```
На Windows WSAStartup/WSACleanup с refcount — можно вызывать init/cleanup вложенно.
### Создание и настройка сокета
```c
socket_t sock = socket_create_udp(AF_INET); // или AF_INET6
if (sock == SOCKET_INVALID) { /* ошибка */ }
socket_set_nonblocking(sock); // обязательно для u_async
socket_set_reuseaddr(sock, 1);
socket_set_buffers(sock, 256*1024, 256*1024); // SO_SNDBUF/SO_RCVBUF
socket_bind_to_device(sock, "eth0"); // только Linux, SO_BINDTODEVICE
socket_set_mark(sock, 42); // только Linux, SO_MARK
```
### Отправка и приём
```c
ssize_t sent = socket_sendto(sock, buf, len, (struct sockaddr*)&addr, addr_len);
ssize_t recv = socket_recvfrom(sock, buf, sizeof(buf), (struct sockaddr*)&src, &src_len);
uint16_t port = ss_get_port(&src); // порт из sockaddr_storage
```
### Обработка ошибок
```c
if (sent == SOCKET_ERROR_CODE) {
int err = socket_get_error();
if (err == ERR_WOULDBLOCK) { /* нормально для неблокирующего */ }
// на POSIX ERR_WOULDBLOCK == EWOULDBLOCK (может совпадать с EAGAIN)
DEBUG_ERROR(..., "%s", socket_strerror(err));
}
```
### Закрытие
```c
socket_close_wrapper(sock); // на POSIX: close() с защитой от fd 0
```
### Ключевые нюансы
- **Всегда вызывать `socket_platform_init()` перед работой** — иначе на Windows сокеты не будут работать
- **Всегда `socket_set_nonblocking()`** — u_async требует неблокирующих сокетов
- `socket_t` — на POSIX `int`, на Windows `SOCKET` (unsigned)
- `SOCKET_INVALID` — константа для невалидного сокета (`-1` на POSIX, `INVALID_SOCKET` на Windows)
- `SOCKET_ERROR_CODE` — код ошибки сокетных вызовов (`-1` на POSIX, `SOCKET_ERROR` на Windows)
- `socket_strerror()` на Windows использует статический буфер — не thread-safe для параллельного использования
- `socket_bind_to_device` и `socket_set_mark` — только Linux, на остальных платформах возвращают -1 с DEBUG-сообщением
- `socket_close_wrapper` на POSIX не закроет fd 0 (защита)
## 3. API
### Типы и константы
| Имя | Назначение |
|-----|------------|
| `socket_t` | Кроссплатформенный тип дескриптора сокета (`int` / `SOCKET`) |
| `SOCKET_INVALID` | Невалидный сокет (`-1` / `INVALID_SOCKET`) |
| `SOCKET_ERROR_CODE` | Код ошибки сокетного вызова (`-1` / `SOCKET_ERROR`) |
| `ERR_WOULDBLOCK` / `ERR_AGAIN` / `ERR_INTR` | Кроссплатформенные коды ошибок |
### Инициализация
| Функция | Назначение |
|---------|------------|
| `socket_platform_init()` | Инициализация сокетной подсистемы (WSAStartup на Windows). 0 — успех |
| `socket_platform_cleanup()` | Деинициализация (WSACleanup на Windows), refcount |
### Создание и настройка
| Функция | Назначение |
|---------|------------|
| `socket_create_udp(family)` | Создать UDP-сокет (AF_INET или AF_INET6) |
| `socket_set_nonblocking(sock)` | Перевести в неблокирующий режим |
| `socket_set_buffers(sock, snd, rcv)` | Установить SO_SNDBUF и SO_RCVBUF |
| `socket_set_reuseaddr(sock, reuse)` | Установить SO_REUSEADDR |
| `socket_bind_to_device(sock, ifname)` | Привязать к интерфейсу (Linux SO_BINDTODEVICE) |
| `socket_set_mark(sock, mark)` | Установить SO_MARK (Linux) |
### I/O
| Функция | Назначение |
|---------|------------|
| `socket_sendto(sock, buf, len, dest, dest_len)` | Отправить UDP-датаграмму |
| `socket_recvfrom(sock, buf, len, src, src_len)` | Принять UDP-датаграмму |
### Утилиты
| Функция | Назначение |
|---------|------------|
| `socket_get_error()` | Текущий код ошибки (inline: `WSAGetLastError()` / `errno`) |
| `socket_strerror(err)` | Текстовое описание ошибки (Windows: статический буфер, не thread-safe) |
| `socket_close_wrapper(sock)` | Закрыть сокет (POSIX: защита от закрытия fd 0) |
| `ss_get_port(addr)` | Извлечь порт в host byte order из `sockaddr_storage` |
+55
View File
@@ -0,0 +1,55 @@
bin
lib
# Win build files
*.dll
*.exe
*.lib
# GNU build files
*.o
*.so*
*.lo
*.P*
*.la*
*.a
*.pc
*config*
Makefile
Makefile.in
.libs
aclocal.m4
autom4te.cache
stamp-*
libtool
soundstretch
# Files generated by MSVC
*.bsc
*.suo
*.sdf
*.filters
*.user
source/SoundTouch/Win32/
source/SoundTouch/x64/
source/SoundStretch/Win32/
source/SoundStretch/x64/
source/SoundTouchDll/Win32/
source/SoundTouchDll/x64/
source/SoundTouchDll/DllTest/Win32/
source/SoundTouchDll/DllTest/x64/
.vs
# Files generated by Android Studio
source/android-lib/.gradle
source/android-lib/.idea
**/*.iml
source/android-lib/local.properties
source/android-lib/build
source/android-lib/.externalNativeBuild
# CMake build directory
build*
CMakeFiles
CMakeCache.txt
*.cmake
+131
View File
@@ -0,0 +1,131 @@
cmake_minimum_required(VERSION 3.16)
project(SoundTouch VERSION 2.3.3 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
include(GNUInstallDirs)
set(COMPILE_OPTIONS)
if(MSVC)
list(APPEND COMPILE_OPTIONS /O2 /fp:fast)
else()
list(APPEND COMPILE_OPTIONS -Wall -Wextra -Wzero-as-null-pointer-constant -Wno-unknown-pragmas)
if(EMSCRIPTEN)
list(APPEND COMPILE_OPTIONS -O3)
else()
list(APPEND COMPILE_OPTIONS -Ofast)
endif()
endif()
set(CMAKE_C_VISIBILITY_PRESET hidden)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
#####################
# SoundTouch library
add_library(SoundTouch STATIC
source/SoundTouch/AAFilter.cpp
source/SoundTouch/BPMDetect.cpp
source/SoundTouch/cpu_detect_x86.cpp
source/SoundTouch/FIFOSampleBuffer.cpp
source/SoundTouch/FIRFilter.cpp
source/SoundTouch/InterpolateCubic.cpp
source/SoundTouch/InterpolateLinear.cpp
source/SoundTouch/InterpolateShannon.cpp
source/SoundTouch/mmx_optimized.cpp
source/SoundTouch/PeakFinder.cpp
source/SoundTouch/RateTransposer.cpp
source/SoundTouch/SoundTouch.cpp
source/SoundTouch/sse_optimized.cpp
source/SoundTouch/TDStretch.cpp
)
target_include_directories(SoundTouch PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
target_include_directories(SoundTouch PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include/soundtouch
)
target_compile_definitions(SoundTouch PRIVATE ${COMPILE_DEFINITIONS})
target_compile_options(SoundTouch PRIVATE ${COMPILE_OPTIONS})
set_target_properties(SoundTouch PROPERTIES OUTPUT_NAME "soundtouch-static")
option(INTEGER_SAMPLES "Use integers instead of floats for samples" OFF)
if(INTEGER_SAMPLES)
target_compile_definitions(SoundTouch PRIVATE SOUNDTOUCH_INTEGER_SAMPLES)
set(SOUNDTOUCH_INTEGER_SAMPLES TRUE)
else()
target_compile_definitions(SoundTouch PRIVATE SOUNDTOUCH_FLOAT_SAMPLES)
set(SOUNDTOUCH_FLOAT_SAMPLES TRUE)
endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(armv7.*)$")
target_compile_options(SoundTouch PRIVATE -mfpu=neon)
endif()
install(
FILES
include/soundtouch/BPMDetect.h
include/soundtouch/FIFOSampleBuffer.h
include/soundtouch/FIFOSamplePipe.h
include/soundtouch/STTypes.h
include/soundtouch/SoundTouch.h
DESTINATION
"${CMAKE_INSTALL_INCLUDEDIR}/soundtouch"
)
########################
# SoundTouchDll library
option(SOUNDTOUCH_DLL "Build SoundTouchDLL C wrapper library" ON)
if(SOUNDTOUCH_DLL)
add_library(SoundTouchDLL SHARED
source/SoundTouchDLL/SoundTouchDLL.cpp
source/SoundTouchDLL/SoundTouchDLL.rc
)
set_target_properties(SoundTouch PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
target_compile_options(SoundTouchDLL PRIVATE ${COMPILE_OPTIONS})
set_target_properties(SoundTouchDLL PROPERTIES CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(SoundTouchDLL PRIVATE DLL_EXPORTS)
target_include_directories(SoundTouchDLL INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
target_include_directories(SoundTouchDLL PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include/soundtouch")
target_link_libraries(SoundTouchDLL PRIVATE SoundTouch)
set_target_properties(SoundTouchDLL PROPERTIES OUTPUT_NAME "soundtouch")
if(NOT ANDROID)
set_target_properties(SoundTouchDLL PROPERTIES VERSION ${CMAKE_PROJECT_VERSION})
if(NOT WIN32)
set_target_properties(SoundTouchDLL PROPERTIES SOVERSION ${PROJECT_VERSION_MAJOR})
endif()
endif()
install(FILES include/soundtouch/SoundTouchDLL.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/soundtouch")
install(TARGETS SoundTouchDLL EXPORT SoundTouchTargets)
endif()
########################
# CMake config
include(CMakePackageConfigHelpers)
set(SOUNDTOUCH_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/SoundTouch")
install(
EXPORT SoundTouchTargets
FILE SoundTouchTargets.cmake
NAMESPACE SoundTouch::
DESTINATION "${SOUNDTOUCH_INSTALL_CMAKEDIR}"
)
configure_package_config_file(SoundTouchConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/SoundTouchConfig.cmake"
INSTALL_DESTINATION "${SOUNDTOUCH_INSTALL_CMAKEDIR}"
)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/SoundTouchConfigVersion.cmake"
VERSION "${CMAKE_PROJECT_VERSION}"
COMPATIBILITY SameMajorVersion
)
install(
FILES
"${CMAKE_CURRENT_BINARY_DIR}/SoundTouchConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/SoundTouchConfigVersion.cmake"
DESTINATION "${SOUNDTOUCH_INSTALL_CMAKEDIR}"
)
+458
View File
@@ -0,0 +1,458 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,11 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/SoundTouchTargets.cmake")
check_required_components(SoundTouch)
if(@SOUNDTOUCH_DLL@)
check_required_components(SoundTouchDLL)
get_target_property(SoundTouchDLL_LOCATION SoundTouch::SoundTouchDLL LOCATION)
message(STATUS "Found SoundTouchDLL: ${SoundTouchDLL_LOCATION}")
endif()
Loaded 100 of 1658 files, more files were not shown because too many files have changed in this diff. Show more