Compare commits

...

353 Commits

Author SHA1 Message Date
Jonas Letzbor ce755cb1a3
Change H264 encoder from baseline to main 2024-06-11 22:25:48 +02:00
Attila Fidan 115346f074 server: Support opening from a bound socket fd
Add a `struct nvnc* nvnc_open_from_fd(int fd)` function which takes an
existing connection-based socket file descriptor bound by the library
user or a parent process and just calls listen() on it, as an
alternative to letting neatvnc handle socket configuration.
2024-06-02 09:44:28 +00:00
Andri Yngvason 0e93aa969f Implement qemu/vmware LED state 2024-04-07 12:28:37 +00:00
Andri Yngvason a77b99f2b4 FUNDING.yml: Add github sponsors 2024-03-26 10:39:54 +00:00
Andri Yngvason 47e714b2bf h264-v4l2m2m: Update copyright
Raspberry Pi Ltd. have kindly allowed me to retain copyright for this work
that they sponsored.
2024-03-26 10:38:05 +00:00
Andri Yngvason 08d0c64ff9 Implement v4l2m2m h264 encoder 2024-03-17 13:53:20 +00:00
Andri Yngvason 0bf53a4843 Create abstract h264 encoder interface 2024-03-17 13:53:20 +00:00
Andri Yngvason b043f004a8 Rename h264-encoder.c -> h264-encoder-ffmpeg-impl.c 2024-03-17 13:53:20 +00:00
Alfred Wingate d95b678d7a server: Remove undeclared variable from tracing macro
* 3647457f6d accidentally referred to
  an nonexistant variable. This leads to build failure if you then
  enable the systemtap feature.
* Downstream bug https://bugs.gentoo.org/902141

Signed-off-by: Alfred Wingate <parona@protonmail.com>
2024-02-26 12:41:15 +00:00
Andri Yngvason 14b78d26d3 meson: Bump minor version to 0.9 2024-02-25 11:13:11 +00:00
Andri Yngvason 46432ce8ca Release v0.8.0 2024-02-25 11:11:28 +00:00
Andri Yngvason c22a0c0379 Add option to enable experimental features
With this, I can release without modifying things specially on the release
branch.
2024-02-25 10:59:56 +00:00
Andri Yngvason dedac2f82f Implement colour map
Instead of dropping the connection, we now implement a simple static
colour map that emulates RGB332.

The quality isn't great, but it's better than dropping the connection
without any explanation.
2024-02-20 21:59:51 +00:00
Andri Yngvason d654e06eea Consolidate security handshake result handling 2024-02-15 10:05:21 +00:00
Andri Yngvason 1647505e94 server: Extract encoder initialisation function 2024-02-02 22:35:23 +00:00
Andri Yngvason 9fa1027353 server: Drop current frame if formats change
If the currently in-flight frame was dispatched before a format change,
it might be the wrong format for the client, so it's better to drop it.
2024-02-02 22:24:03 +00:00
Andri Yngvason ef106b92f1 server: Log encodings reported by client 2024-02-02 22:22:03 +00:00
Andri Yngvason f1a6710bba server: Log pixel format choice 2024-02-02 22:17:52 +00:00
Andri Yngvason 584fb77cc8 pixels: Add strings for RGB222 and BGR222 2024-02-02 22:14:28 +00:00
Andri Yngvason c7d7929f7c Keep zlib streams when switching encodings
Both RealVNC and TigerVNC clients expect zlib streams to remain when
switching encodings, so when they switch back, inflate fails if the
encoder is discared.

fixes #109
2024-02-02 22:10:01 +00:00
Andri Yngvason 58509ca889 Warn when client chooses non-true-color pixel format
This at least lets the user know why things failed.
2024-01-30 21:50:23 +00:00
Andri Yngvason 65fc23c88d server: Allow server to request more than 32 encodings
fixes #108
2024-01-24 18:29:50 +00:00
Andri Yngvason ddd5ee123e h264-encoder: Use AV_FRAME_FLAG_KEY instead of key_frame 2024-01-01 12:27:20 +00:00
Andri Yngvason f503cbef25 Replace nvnc_client_get_hostname with nvnc_client_get_address
This is a more accurate name for what is returned since
c76129b2d2.
2023-12-31 17:59:52 +00:00
Andri Yngvason 524f9e0399 logging: Set log function to default when unset 2023-12-26 15:11:28 +00:00
Andri Yngvason 4691a35b7b logging: Add method to set thread local log function
This allows the user to override the log function in the current thread
without receiving log messages from concurrent tasks.
2023-12-26 11:58:35 +00:00
Andri Yngvason a7f6c50d6d logging: Export default log function
This allows users to intercept log messages without fully overriding the
default log handler.
2023-12-26 11:30:21 +00:00
Andri Yngvason d80b51f650 server: Don't complete fb update more than once
If stream_send in finish_fb_update returns -1, then complete_fb_update
will be called there and in the callback to stream_send.
2023-11-19 20:28:43 +00:00
Andri Yngvason c76129b2d2 server: Remove DNS lookup
DNS lookup is slow and can even fail. Under some circumstances, it will
even block for a significant amount of time until it completes.

The user of this library can do the lookup instead if they wish.
2023-11-05 10:29:04 +00:00
Andri Yngvason 0e262c8f33 crypto: Initialise AES-ECB decode context correctly
This fixes Apple DH
2023-11-04 23:13:12 +00:00
Andri Yngvason 175d53bc41 server: Fix double-free on failed Apple DH 2023-11-04 23:10:15 +00:00
Andri Yngvason 6beb263027 Don't use tag for git version 2023-10-09 22:54:18 +00:00
Andri Yngvason a631809cbb README: Enumerate dependencies for crypto 2023-10-06 20:44:27 +00:00
Philipp Zabel 5b4141ac1d Remove superfluous whitespace
Signed-off-by: Philipp Zabel <philipp.zabel@gmail.com>
2023-10-06 20:41:30 +00:00
Philipp Zabel bc3a47a654 Indent wrapped argument lists with two tabs (function calls)
Do not align wrapped function argument lists with the opening
parenthesis. Indent them with two tabs.

Signed-off-by: Philipp Zabel <philipp.zabel@gmail.com>
2023-10-06 20:41:30 +00:00
Philipp Zabel f04284351e Indent wrapped argument lists with two tabs (function definitions)
Do not align wrapped function argument lists with the opening
parenthesis. Indent them with two tabs.

Signed-off-by: Philipp Zabel <philipp.zabel@gmail.com>
2023-10-06 20:41:30 +00:00
Andri Yngvason 457737de6c Set version for next release 2023-10-04 22:46:37 +00:00
Andri Yngvason 57d3b8d02d damage-refinery: Use scalar xxh3 implementation
This is guaranteed to be portable. It's best to keep it like that until
runtime detection is implemented.
2023-10-04 09:03:11 +00:00
Andri Yngvason dc1d93cadf server: Defer cleaning up client resources on close
When the event is received, the client object may still be processing some
things, so let's allow it to finish.
2023-10-03 22:15:20 +00:00
Andri Yngvason f8f49196e8 server: Free RSA creds on close 2023-10-03 20:45:20 +00:00
Andri Yngvason 4be95d6938 crypto-nettle: Fix use after free 2023-10-03 20:44:46 +00:00
Andri Yngvason 995d678e1e damage-refinery: Replace murmurhash with XXH3 2023-10-03 20:33:27 +00:00
MazTheMan b066536aac zrle: fix for source format of 24 bits 2023-10-03 20:04:21 +00:00
Andri Yngvason 65d1d0e185 server: Use uint32_t for security result failure path 2023-10-02 23:05:19 +00:00
Andri Yngvason d2c8ab0b6c Revert "Export base64 encoder and decoder"
There is currently no use for this.

This reverts commit c38f669e13.
2023-10-02 22:47:04 +00:00
Andri Yngvason a5fecc0b97 stream: rsa-aes: Unref payload after encoding
This fixes a memory leak
2023-10-02 22:38:59 +00:00
Andri Yngvason 913c314b31 server: Use memcpy instead of strncpy for username/password
This fixed zero-termination error
2023-10-02 21:57:22 +00:00
Andri Yngvason f54aeed334 Notify client about NTP support 2023-10-01 10:56:42 +00:00
Andri Yngvason bdadcad1c8 Replace strlcpy with strncpy
The former isn't portable.
2023-09-29 22:00:48 +00:00
Andri Yngvason 3794405101 websocket: Add some missing copyright notices 2023-09-29 21:53:20 +00:00
Andri Yngvason 58d6dff5e5 API: Consolidate setup of security constraints 2023-09-29 21:53:20 +00:00
Andri Yngvason 373e5a0f9e Remove logging of sensitive information 2023-09-29 21:53:20 +00:00
Andri Yngvason d74878fd00 server: Allow arbitrary RSA key length 2023-09-29 21:53:20 +00:00
Andri Yngvason 74e9db19fd API: Add method to set RSA credentials 2023-09-29 21:53:20 +00:00
Andri Yngvason 4220cbb345 crypto: Add method to import RSA private keys 2023-09-29 21:53:20 +00:00
Andri Yngvason c38f669e13 Export base64 encoder and decoder 2023-09-29 21:53:20 +00:00
Andri Yngvason 98f6930580 ws-handshake: Use own base64 and SHA1 implementations 2023-09-29 21:53:20 +00:00
Andri Yngvason a02f578f9e Add base64 encoder & decoder
I prefer to have these independent of the crypto suite that's being used.
2023-09-29 21:53:20 +00:00
Andri Yngvason 4705c0cfcc Implement RSA-AES-256 security type 2023-09-29 21:53:20 +00:00
Andri Yngvason 396f4ed6c5 server: Clean up crypto resources on disconnect 2023-09-29 21:53:20 +00:00
Andri Yngvason 76c832d791 crypto: Make deleting NULL pointers noop 2023-09-29 21:53:20 +00:00
Andri Yngvason 7eb42324bf server: Define rsa-aes server key length constant 2023-09-29 21:53:20 +00:00
Andri Yngvason 08312c3296 crypto: Add sha256 2023-09-29 21:53:20 +00:00
Andri Yngvason d004a2fcb9 crypto: Remove unused code 2023-09-29 21:53:20 +00:00
Andri Yngvason f029484a87 crypto: Add AES256-EAX cipher 2023-09-29 21:53:20 +00:00
Andri Yngvason c6df99ec46 server: Use hash_{one,many} 2023-09-29 21:53:20 +00:00
Andri Yngvason d12973486a crypto: Add helper functions for hashing 2023-09-29 21:53:20 +00:00
Andri Yngvason 9507624cf3 Create dedicated RSA-AES stream
The message format isn't really within the domain of the cipher, so it
doesn't belong to the crypto interface.
2023-09-29 21:53:20 +00:00
Andri Yngvason 625323d8a3 stream-ws: Clean up exec-and-send resources 2023-09-29 21:53:20 +00:00
Andri Yngvason dfc20d065e stream-ws: Inherit stream-tcp
This eliminates the need for implementing all stream functions
2023-09-29 21:53:20 +00:00
Andri Yngvason f90c628e66 Add temporary api function to enable auth without tls 2023-09-29 21:53:20 +00:00
Andri Yngvason e341898bbc Implement RSA-AES 2023-09-29 21:53:20 +00:00
Andri Yngvason 71aa5acfde crypto: Integrate message handling into cipher 2023-09-29 21:53:20 +00:00
Andri Yngvason c12c1c800a crypto: Add RSA and AES-EAX 2023-09-29 21:53:20 +00:00
Andri Yngvason 7b878033f0 Implement Apple's Diffie-Hellman based security type 30 2023-09-29 21:53:20 +00:00
Andri Yngvason da2518e296 stream: Integrate cipher 2023-09-29 21:53:20 +00:00
Andri Yngvason 0c3a98483c Add abstract interface for low level crypto 2023-09-29 21:53:20 +00:00
MazTheMan fd1e18b475 Implement 24 bit pixel formats for raw and tight 2023-09-29 21:46:05 +00:00
Philipp Zabel 56f1c125fa meson: Fix Meson warning about missing check kwarg in run_command() calls
Fixes the following Meson warning:

  WARNING: You should add the boolean check kwarg to the run_command call.
           It currently defaults to false,
           but it will default to true in future releases of meson.
           See also: https://github.com/mesonbuild/meson/issues/9300

Signed-off-by: Philipp Zabel <philipp.zabel@gmail.com>
2023-07-05 10:30:55 +00:00
Andri Yngvason 8872dece0c server: Defer client_unref in close_after_write
This ensures that the stream object stays alive while its write
queue is being processed.
2023-07-04 23:40:32 +00:00
Andri Yngvason 61fad8c96b server: Actually send a reason when handshake fails 2023-07-04 22:56:52 +00:00
Andri Yngvason ade1046391 stream: Allocate enough for tls upgrade 2023-05-30 08:40:56 +00:00
Andri Yngvason b5f37d0227 stream: Move tls specific member into tls impl 2023-05-28 15:50:36 +00:00
Andri Yngvason c006936fd0 http: Only support GET method 2023-04-30 14:31:34 +00:00
Andri Yngvason 2f439b9fa2 http: Stop memory leak in failure path 2023-04-30 14:29:30 +00:00
Andri Yngvason 1fa8d41aef http: Re-order includes 2023-04-30 14:21:35 +00:00
Andri Yngvason a179c83f81 http: Remove unused code 2023-04-30 14:20:53 +00:00
Andri Yngvason c0b3e16bb0 stream-ws: Sanitise handshake input 2023-04-30 14:03:12 +00:00
Andri Yngvason e5e6767c1e ws-handshake: Handle protocol & version fields 2023-04-30 13:44:12 +00:00
Andri Yngvason 58df7dfc5c meson: Ignore format-truncation warnings 2023-04-30 13:30:49 +00:00
Andri Yngvason d7dc9c0db5 server: Set SO_SNDBUF to 65536
The previous value of 4096 caused a very bad performance regression
with GnuTLS.
2023-04-11 20:37:22 +00:00
Andri Yngvason 79d24ae0ca stream-gnutls: Handle EAGAIN correctly 2023-04-11 20:33:13 +00:00
Andri Yngvason 4b5e4d628d stream-gnutls: Fix use after free 2023-04-11 20:32:37 +00:00
Andri Yngvason 6a5ea71289 stream: Add a TODO about cleaning up struct 2023-04-11 19:54:25 +00:00
Andri Yngvason afc0256b2f stream-tcp: EAGAIN is not an error
This fixes inadvertent treatment of a normal situation as an error.
2023-04-10 11:36:59 +00:00
Andri Yngvason 5530b22fde server: Reduce SO_SNDBUF to 4096
This will allow us to more accuately gauge the back-pressure on the socket.
2023-04-08 13:40:24 +00:00
Andri Yngvason aa6fadf2fd server: Use stream_exec_and_send for ntp 2023-04-08 13:40:24 +00:00
Andri Yngvason 0cdbf6a602 stream: Add exec_and_send function
This allows us to execute a function right before a leaves the send queue
and is really only useful for NTP as far as I can tell.
2023-04-08 13:02:45 +00:00
Andri Yngvason 19172140ba Add NTP inspired latency tracking and time sync 2023-04-07 21:24:22 +00:00
Andri Yngvason 8847511596 Implement websocket 2023-04-07 12:47:49 +00:00
Andri Yngvason e385a98238 stream: Add a cork to pause sending 2023-04-07 12:47:49 +00:00
Andri Yngvason 979d10ce62 Turn stream into abstract interface class 2023-04-06 21:02:39 +00:00
Andri Yngvason 2cefc7febb test: pixels: Revert accidental change 2023-03-23 10:18:28 +00:00
Andri Yngvason 1081ff35cd test: pixels: Use unsigned numeric literals
This fixes type promotion issues with the swap32 macro.
2023-03-22 09:28:06 +00:00
Andri Yngvason e6931239bc .github: Add a pull request template 2023-02-26 11:27:01 +00:00
Andri Yngvason 796b9bc20e Add a CONTRIBUTING.md 2023-02-26 11:25:46 +00:00
Philipp Zabel 6f1c12f376 examples: draw: Demonstrate desktop resizing
Let whiteboard size follow client window size for clients that support
the ExtendedDesktopSize pseudo-encoding.
2023-02-25 11:03:44 +00:00
Philipp Zabel e19c9ad600 Implement desktop resizing
Implement minimal support for ExtendedDesktopSize pseudo-encoding
and SetDesktopSize client message.

The opaque nvnc_desktop_layout structure contains all information
from the SetDesktopSize client message.
2023-02-25 11:03:44 +00:00
Andri Yngvason eacebad277 Remove _clang-format
It's not helpful
2023-02-24 15:25:48 +00:00
Andri Yngvason 8b3dc1ae60 Release v0.6.0 2023-01-22 13:06:36 +00:00
Andri Yngvason cdf990db75 meson: Require specific version of aml 2023-01-22 13:06:36 +00:00
Philipp Zabel afe37b983d stream: Fix remote closing TLS connection
If a TLS stream is closed by the remote VNC client,
stream__remote_closed must be called to signal the server.

Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
2022-11-30 15:26:28 +00:00
Andri Yngvason bc87cbbb7d meson: Set default warning level to 2 2022-11-26 18:19:24 +00:00
Jim Ramsay 332be4d471 Add nvnc_client_close API
This allows the user or application to terminate any given nvnc_client
connection at any time.

Signed-off-by: Jim Ramsay <i.am@jimramsay.com>
2022-11-24 16:08:06 +00:00
Andri Yngvason 8f979c9928 Pass nvnc_client to cut-text callback 2022-11-20 22:42:01 +00:00
Andri Yngvason 79f26924ea Add functions for listing clients 2022-11-20 12:03:18 +00:00
Andri Yngvason 8a7509bd3a raw: Allocate conservatively sized buffers 2022-11-05 13:54:01 +00:00
Andri Yngvason f31ddf7fe9 enc-util: Add function to calculate region area 2022-11-05 13:52:55 +00:00
Andri Yngvason 3647457f6d server: Properly handle stream write errors
This fixes leaked rcbufs when clients disconnect.
2022-11-05 13:22:14 +00:00
Andri Yngvason fbe8631add raw: Fix worker data dependencies 2022-11-05 13:22:14 +00:00
Andri Yngvason 935ce4e98d Remove push/pull encoder interface
Think of it as a failed experiment.
2022-11-05 10:15:56 +00:00
Jim Ramsay 036b549fd8 Record authenticated username for each connected client
Signed-off-by: Jim Ramsay <jramsay@redhat.com>
2022-11-04 09:41:14 +00:00
Jim Ramsay 86bd2ced85 Record hostname for each connected client
Signed-off-by: Jim Ramsay <jramsay@redhat.com>
2022-11-04 09:41:14 +00:00
Andri Yngvason 73e1089f06 zrle: Unref result in destroy 2022-10-30 13:53:10 +00:00
Andri Yngvason b525608a06 raw: Unref result in destroy 2022-10-30 13:52:52 +00:00
Andri Yngvason b3c1d5d1dc stream: Use MSG_NOSIGNAL
Without MSG_NOSIGNAL, sending to closed sockets will generate SIGPIPE.
2022-10-30 13:07:13 +00:00
Andri Yngvason 95742676c9 zrle: Keep reference to encoder while encoding 2022-10-30 12:44:05 +00:00
Andri Yngvason 91dc5da243 raw: Keep reference to encoder while encoding 2022-10-30 12:44:05 +00:00
Andri Yngvason 727b2f727e tight: Keep reference to encoder while encoding 2022-10-30 12:44:05 +00:00
Andri Yngvason 8f2d137046 server: Make encoder inert when closing client 2022-10-30 12:44:05 +00:00
Andri Yngvason baaf84eab9 Reference count encoders 2022-10-30 12:44:05 +00:00
Andri Yngvason 48b070af5a server: Free cursor buffers on close 2022-10-30 12:44:05 +00:00
Andri Yngvason adef210252 fb-pool: Add setter for fb allocator 2022-10-29 18:40:02 +00:00
Andri Yngvason 411530b5da meson: Ack aml API changes 2022-10-29 11:50:34 +00:00
Andri Yngvason efb5ab956c Add debug logging for tcp address binding 2022-10-24 20:55:38 +00:00
Andri Yngvason 3588670c81 server: Replace abort() with NVNC_LOG_PANIC 2022-10-24 09:16:59 +00:00
Andri Yngvason 3c4a069ba2 tight: Disable chroma subsampling at q=9
This avoids color banding at the highest quality level.
2022-10-23 19:05:32 +00:00
Philipp Zabel df84f371fe Fix fallthrough warnings in murmurhash
Fix two -Wimplicit-fallthrough warnings in the murmurhash function:

  ../src/murmurhash.c: In function 'murmurhash':
  ../src/murmurhash.c:71:15: warning: this statement may fall through [-Wimplicit-fallthrough=]
     71 |     case 3: k ^= (tail[2] << 16);
        |             ~~^~~~~~~~~~~~~~~~~~
  ../src/murmurhash.c:72:5: note: here
     72 |     case 2: k ^= (tail[1] << 8);
        |     ^~~~
  ../src/murmurhash.c:72:15: warning: this statement may fall through [-Wimplicit-fallthrough=]
     72 |     case 2: k ^= (tail[1] << 8);
        |             ~~^~~~~~~~~~~~~~~~~
  ../src/murmurhash.c:74:5: note: here
     74 |     case 1:
        |     ^~~~
2022-10-15 10:00:43 +00:00
Jeroen Hofstee 5b2a062f0e don't resize an encoder if it is not set 2022-10-15 09:59:05 +00:00
Philipp Zabel 1b929afb2c Only set HAVE_LIBAVUTIL if libav is actually used
src/log.c uses av_log_set_level() and av_log_set_callback() from
libavutil if HAVE_LIBAVUTIL is set, so the libavutil dependence
must be added at the same time. Since libav logging is only used
by the h264 code, enable it all together for now.
2022-10-15 09:57:53 +00:00
Andri Yngvason b932f3e2e0 h264-encoder: Set async_depth=1
This fixes stalling during encoding. The FFmpeg devs seem to think that it's
normal to change the default behaviour or their code, so this needs to be
fixed here instead.

Fixes #73
2022-09-10 15:48:15 +00:00
Andri Yngvason 19538c9435 server: Fix encoding selection for sw frames
This fixes encoding selection when not using the --gpu option. Before this
change, raw encoding would always be selected.

Reported-by: Consolatis
Suggested-by: Consolatis
2022-08-23 22:34:56 +00:00
Andri Yngvason c0e1159a53 Revert "h264-encoder: Add 30 bit color depth formats"
This reverts commit 613761cf5f.

These are not available on older libav version and they don't event work.
2022-08-23 21:13:13 +00:00
Andri Yngvason 5e5d5d6e29 resampler: Use transformed width as destination stride
Fixes #72
2022-08-23 20:23:12 +00:00
Andri Yngvason f00570146b examples: draw: Fix cursor setter argument order
Reported-by: Ronny Nilsson
2022-08-20 11:35:03 +00:00
Andri Yngvason 965837465b README: Upgrade dependency list 2022-07-30 22:10:09 +00:00
Andri Yngvason 86f3106150 README: Reorder dependencies 2022-07-30 22:10:09 +00:00
Andri Yngvason 36fb346da1 README: Remove client compatibility table
It never got updated
2022-07-30 22:10:09 +00:00
Philipp Zabel affd7f3f6d Allow to query client-side cursor support
Add a function nvnc_client_supports_cursor() to enable the API user to
make an informed decision whether nvnc_set_cursor() can be expected to
make the client draw the cursor, or whether it has to be rendered into
the framebuffer.
2022-07-29 08:37:29 +00:00
Andri Yngvason d18cc4fc57 Add constants for left and right scroll 2022-07-28 20:13:48 +00:00
Andri Yngvason 263990f5ef stream: Remove stray ampersand in tls handshake failure code path 2022-07-17 20:17:19 +00:00
Andri Yngvason b9a5b9a3f1 h264: Set quality according to client's wishes 2022-07-10 13:53:32 +00:00
Andri Yngvason 528eac51a3 Fix jpeg quality setting
The encoding identifiers are supposed to be interpreted as a range. I
interpreted the upper and lower limits as two discrete quality settings
instead, which is wrong.
2022-07-10 12:41:20 +00:00
Andri Yngvason 43684ec482 Release v0.5.1 2022-07-09 21:39:53 +00:00
Andri Yngvason 86dd97ed0a test: meson: Add missing libdrm_inc dependency 2022-07-09 21:10:42 +00:00
Andri Yngvason f4f8f0bdb3 meson: Use partial_dependency() for libdrm cflags 2022-07-09 20:52:28 +00:00
Jan Beich 4baeaa43fd server: add missing header after f20ffb5e1e
src/server.c:1119:17: error: use of undeclared identifier 'IPPROTO_TCP'
        setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
                       ^
2022-07-09 20:04:53 +00:00
Andri Yngvason 734e189c7f Release v0.5.0 2022-07-09 18:03:07 +00:00
Andri Yngvason d85347cfe3 h264-encoder: Set quality
This is to get libav to stop complaining about quality not being set
2022-07-09 17:19:46 +00:00
Andri Yngvason 362918a8cf Integrate libav into logging framework 2022-07-09 17:15:35 +00:00
Andri Yngvason 2f4f1a2caf meson_options: Enable h264 by default 2022-07-09 16:38:15 +00:00
Andri Yngvason 863fb0ce80 h264-encoder: Add dtrace probes 2022-07-09 16:30:25 +00:00
Andri Yngvason f20ffb5e1e server: Turn off Nagle's algorithm 2022-07-09 16:29:18 +00:00
Andri Yngvason d86af88573 server: Log encoder choices 2022-07-09 16:28:09 +00:00
Andri Yngvason dc82b3f29d h264-encoder: Clean up current_packet in destroy 2022-06-29 11:39:46 +00:00
Andri Yngvason b8122725e2 server: Reset encoder callback on frame done 2022-06-29 11:36:05 +00:00
Andri Yngvason 4eb7a3a559 h264-encoder: Call on_packet_ready last in on_work_done
The callback can result in the encoder being destroyed, so we can't
dereference access the encoder object after calling it.
2022-06-29 11:34:12 +00:00
Andri Yngvason e8f2481aa4 open-h264: unref result when finished with it 2022-06-26 14:12:37 +00:00
Andri Yngvason 8bde568112 server: Add dtrace probes with pts 2022-06-26 13:57:32 +00:00
Andri Yngvason 1e3819b6fb open-h264: Add dtrace probes 2022-06-26 13:57:32 +00:00
Andri Yngvason e05e812d93 resampler: Add dtrace probes 2022-06-26 13:57:32 +00:00
Andri Yngvason ee64bc2722 display: Add dtrace probes 2022-06-26 13:57:32 +00:00
Andri Yngvason 4e9eb98301 open-h264: Convert to normal encoder
The push/pull code path is buggy.
2022-06-26 13:56:17 +00:00
Andri Yngvason e6a12ffda7 server: Promote some log levels 2022-06-25 16:32:05 +00:00
Andri Yngvason ad4a834cfc Use new logging system 2022-06-25 16:15:32 +00:00
Andri Yngvason 45da0fc157 Add simple logging system 2022-06-25 16:15:32 +00:00
Andri Yngvason 9285594e9d Call encoder.on_done in a function 2022-06-11 11:44:51 +00:00
Andri Yngvason d4258a0aab cursor: Handle rotated cursors 2022-06-11 11:44:51 +00:00
Andri Yngvason bab78857e1 resampler: Extract function: resample_now() 2022-06-11 11:44:51 +00:00
Harm te Hennepe 7205139b11 Don't call gbm symbols when they are not available 2022-05-24 22:55:34 +00:00
Jan Beich da290abc25 server: consistently use builtin byteswap after 53f88894d5
src/server.c:48:10: fatal error: 'byteswap.h' file not found
 #include <byteswap.h>
          ^~~~~~~~~~~~
2022-04-24 17:56:03 +00:00
Andri Yngvason 53f88894d5 Add presentation timestamps 2022-04-14 18:10:09 +00:00
Andri Yngvason e2e117b02f h264-encoder: Fully flush output packets 2022-04-10 16:21:28 +00:00
Andri Yngvason 3ea068b90c server: Decrement n_pending_requests after dispathing encoding job
The request is as good as handled at that point. We don't want to squeeze
in another frame that the client didn't request before the encoding job
finishes. That would cause a negative pending count and the client would
stop getting updates.
2022-04-03 20:52:24 +00:00
Andri Yngvason 9d8c956983 h264-encoder: Fix copy-pasta 2022-03-06 11:24:33 +00:00
Andri Yngvason aca09358ea Add back damage argument to nvnc_set_cursor
It's better to keep feeding buffers to keep buffers in rotation for damage
tracking purposes.
2022-02-21 21:43:41 +00:00
Andri Yngvason 9c02e6afaf Implement hiding cursors 2022-02-20 14:48:24 +00:00
Andri Yngvason 1553c88f5e Add width and height arguments to nvnc_set_cursor 2022-02-19 23:06:15 +00:00
Andri Yngvason afc0f018da cursor: Use the right scan-line length when width != stride 2022-02-19 21:51:24 +00:00
Andri Yngvason cb282c57c4 cursor: Map cursor buffers before access 2022-02-13 18:44:27 +00:00
Andri Yngvason adce5170ee examples: draw: Handle different endianness for cursor colour 2022-02-12 13:28:15 +00:00
Andri Yngvason c876b91541 pixels: Add function to get rfb pixel format name 2022-02-12 13:15:30 +00:00
Andri Yngvason 0e0fe5b73a pixels: Add function to convert drm format to string 2022-02-12 12:30:12 +00:00
Andri Yngvason 4dcf8ec25b test: pixels: Add a unit test for pixel32_to_cpixel 2022-02-12 12:13:42 +00:00
Andri Yngvason 1f043d6992 Add some unit tests for pixel conversions 2022-02-10 21:53:33 +00:00
Andri Yngvason 70784e1bcc cursor: Fix alpha mask stride 2022-02-10 21:53:33 +00:00
Andri Yngvason 9a292afd52 pixels: Handle different endianness for alpha mask 2022-02-10 21:53:33 +00:00
Andri Yngvason c0d1455686 examples: draw: Add a client-side cursor 2022-02-06 16:33:46 +00:00
Andri Yngvason 48baf74560 Implement client side cursor rendering 2022-02-06 16:33:46 +00:00
Andri Yngvason 258dccd768 Add a cursor encoder 2022-02-06 16:33:46 +00:00
Andri Yngvason 0cc6be091c pixels: Add function to extract alpha mask 2022-02-06 15:01:33 +00:00
Andri Yngvason 613761cf5f h264-encoder: Add 30 bit color depth formats 2022-01-27 22:31:32 +00:00
Andri Yngvason 648255769a pixels: Add 10-bits-per-colour formats 2022-01-27 22:11:51 +00:00
Andri Yngvason fd23cb8c2f enc-util: Round up division in calc_bytes_per_cpixel
Otherwise 10 bit formats will be mistaken for 8 bit formats.
2022-01-27 22:10:37 +00:00
Andri Yngvason 5dc6a28828 h264-encoder: Automatically find a render node 2021-12-26 13:10:41 +00:00
Andri Yngvason 5a75fdf2bc display: Only run damage refinery when it's needed 2021-12-26 13:10:41 +00:00
Andri Yngvason c5a5437a9e server: Keep a count of clients that use damage 2021-12-26 13:10:41 +00:00
Andri Yngvason 55beea3464 open-h264: Set "ignores-damage" flag 2021-12-26 13:10:41 +00:00
Andri Yngvason d702939969 encoder: Add impl flags 2021-12-26 13:10:41 +00:00
Andri Yngvason 90f61f03c6 Plug open h264 2021-12-26 13:10:41 +00:00
Andri Yngvason 0a70f7fa6a encoder: Add push/pull encoder interface 2021-12-26 13:10:41 +00:00
Andri Yngvason e1ba4e1085 API: Add nvnc_fb_get_type 2021-12-26 13:10:41 +00:00
Andri Yngvason 15c14d7d4b Create an Open h.264 encoder 2021-12-26 13:10:41 +00:00
Andri Yngvason 1113b6b12a rfb-proto: Add identifier for Open H.264 2021-12-26 13:10:41 +00:00
Andri Yngvason b71598b334 Create h264-encoder 2021-12-26 13:10:41 +00:00
Andri Yngvason 65c0e91c37 Move update header out of encoders 2021-12-12 16:05:29 +00:00
Andri Yngvason 8b2c81c3dd Add offset coordinates to encoders 2021-12-11 21:55:56 +00:00
Andri Yngvason 42b102df0c Remove unused headers 2021-12-11 21:07:41 +00:00
Andri Yngvason a7241658b0 Create encoder abstraction 2021-12-11 21:03:18 +00:00
Andri Yngvason 66942ab913 display: Clean up transformed damage region
This fixes a memory leak
2021-12-11 20:58:17 +00:00
Andri Yngvason 783ac9d99d fb: Unmap released fbs 2021-09-20 22:06:08 +00:00
Andri Yngvason 10c0b9131c fb: Fix mapped gbm buffer stride 2021-09-20 21:57:56 +00:00
Andri Yngvason 26ff812ea6 Add damage refinery from wayvnc 2021-09-20 21:40:21 +00:00
Andri Yngvason ff3dc13f0b resampler: Track buffer damage 2021-09-20 21:40:21 +00:00
Andri Yngvason 943bd33993 resampler: Make resampler object opaque 2021-09-20 21:40:21 +00:00
Andri Yngvason 3b24dbd6a4 resampler: Transform output buffer dimensions based on input transform 2021-09-20 21:40:21 +00:00
Andri Yngvason 0d4ab56568 transform-util: Add dimensions transform function 2021-09-19 21:11:54 +00:00
Andri Yngvason 02559a7f7e Re-sample transformed framebuffers 2021-09-19 20:12:30 +00:00
Andri Yngvason 691e835d1b fb: Add transform attribute 2021-09-19 19:56:56 +00:00
Andri Yngvason 784af9fa5d Add transform utility functions from wayvnc 2021-09-19 19:54:59 +00:00
Andri Yngvason dad7312814 pixels: Add fourcc_to_pixman_fmt 2021-09-19 19:54:23 +00:00
Andri Yngvason cf42f76f56 Add gbm_bo nvnc_fb type 2021-09-12 18:51:22 +00:00
Andri Yngvason a14b829743 fb: Fix buffer allocation
Width and height got mixed up.
2021-09-11 18:17:01 +00:00
Andri Yngvason dad1948e98 Remove nvnc_fb_flags 2021-09-05 00:46:24 +00:00
Andri Yngvason b75eeac03d Add API function for creating nvnc_fb from an pre-allocated buffer 2021-09-05 00:46:24 +00:00
Andri Yngvason 96886e21d5 Add a API function to get the pixel size of nvnc_fb 2021-09-05 00:46:24 +00:00
Andri Yngvason c7dd062498 Add a stride parameter to nvnc_fb 2021-09-05 00:46:24 +00:00
Andri Yngvason 4594517571 .gitignore: add .vimrc and sandbox 2021-09-05 00:46:24 +00:00
Andri Yngvason e8e4a9469a Remove damage checker
There's a much better one in wayvnc
2021-09-05 00:46:24 +00:00
Andri Yngvason 965dbd6eca examples: draw: Use new buffer submission API 2021-09-04 21:21:23 +00:00
Andri Yngvason 2095913688 display & fb_pool: Clean up memory leaks 2021-09-04 21:21:23 +00:00
Andri Yngvason 77d8efcbe3 fb_pool: Return true from resize when dimensions change 2021-09-04 21:21:23 +00:00
Andri Yngvason 1b7b51af44 Add a cleanup callback to nvnc_set_userdata 2021-09-04 21:21:23 +00:00
Andri Yngvason 41c9ebe960 server: process fb update requests in fb update request message handler
Otherwise, the client won't get the initial frame
2021-09-04 21:21:23 +00:00
Andri Yngvason e16a64a67d fb_pool: Fix reference counting error 2021-09-04 21:21:23 +00:00
Andri Yngvason f5b0f508f0 fb: Use special context pointer for release callback 2021-09-04 21:21:23 +00:00
Andri Yngvason f566105ab5 Remove nvnc_set_render_fn 2021-09-04 21:21:23 +00:00
Andri Yngvason 981256d8d5 Add a buffer pool 2021-09-04 21:21:23 +00:00
Andri Yngvason d63feadeab Notify the user when an fb is released 2021-09-04 21:21:23 +00:00
Andri Yngvason 031555c85d fb: Add hold/release logic 2021-09-04 21:21:23 +00:00
Marco Felsch d0cf1595af meson: Fix host leakage
Commit d2d2f32 ("Add libdrm include path to cflags") fixed the build for
FreeBSD but introduced host leakage which breaks cross-compile builds.
To fix this we need to specifying the include path by unsing '-I=' so
the compiler searches within the specified sysroot dir.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
2021-09-04 18:15:48 +00:00
Ryan Farley b320723049 Support UNIX sockets
Adds support for UNIX domain sockets with `nvnc_open_unix()` function.
Closes #1.
2021-04-04 20:24:33 +00:00
Andri Yngvason 019d6eda36 Display Patreon account on GitHub page 2021-01-31 12:21:20 +00:00
Andri Yngvason d2d2f32f17 Add libdrm include path to cflags
This seems to be required for this to build on FreeBSD
2020-12-31 02:32:33 +00:00
Andri Yngvason b1d32694d0 Release v0.4.0 2020-12-06 14:02:50 +00:00
Andri Yngvason 1359b987a3 Translate XT keycodes to linux keycodes 2020-11-29 20:48:31 +00:00
Andri Yngvason f547ed72f9 Decrement pending requests when sending pseudo frames 2020-11-29 18:36:48 +00:00
Andri Yngvason ea98af75de Notify client about qemu key capability 2020-11-29 18:36:48 +00:00
Andri Yngvason dbe35bc3ad Add qemu extended key events 2020-11-29 18:36:48 +00:00
Andri Yngvason efaa2bf265 rfb-proto: Add qemu key event extension 2020-11-29 18:36:48 +00:00
Aisha Tammy 9a41f96ad0 add systemtap option for sys/std.h
Signed-off-by: Aisha Tammy <gentoo@aisha.cc>
2020-09-29 14:51:08 +00:00
Andri Yngvason e507a76d8d Release v0.3.2 2020-09-27 17:31:06 +00:00
Andri Yngvason e69006fc48 tight: Hold fb and client refs while encoding 2020-09-27 17:17:22 +00:00
Andri Yngvason af38a643d9 tight: Don't block 1 worker while encoding tiles 2020-09-27 12:37:00 +00:00
Andri Yngvason 9e84000e0a server: Extract finish_fb_update() from on_client_update_fb_done() 2020-09-26 22:29:02 +00:00
Andri Yngvason add8d8e8db server: Move pixel format conversion out of do_client_update_fb 2020-09-26 22:17:02 +00:00
Andri Yngvason 7e2b4fef8c server: Refactor schedule_client_update_fb 2020-09-26 22:00:53 +00:00
Andri Yngvason 24a6e29cf2 raw-encoder: Use encder utils 2020-09-26 16:19:02 +00:00
Andri Yngvason 413e6e7e72 zrle: Use encoder utils 2020-09-26 16:16:36 +00:00
Andri Yngvason 31b7077bc5 tight: Use encoder utils 2020-09-26 16:13:17 +00:00
Andri Yngvason 25a533e22b Add common utilities for encoders 2020-09-26 16:10:25 +00:00
Scott Moreau 81a8ba9d0e server: Handle cut text messages of up to 10MB 2020-09-25 21:47:59 +00:00
Scott Moreau 783807c0b9 server: Fix possible crash on fragmented packet messages
The packets sent from the client especially for client cut text,
are typically sent in two packets, one for the message containing
the type and length and the other for the actual data. Sometimes
the first message is read but we still don't have the data yet.
We need to continue reading data to use the structure but this
revealed a bug. The client event handler was calling memmove()
with buffer_index as the size argument. This meant that it was
copying the wrong amount of data, resulting in garbage at the
end of the expected data. This patch fixes the problem by first
subtracting buffer_index from buffer_len and then moving buffer_len
worth of data, which is what was read into msg_buffer. The problem
possibly manifested itself with random crashes, after reading
random data.
2020-09-21 21:15:45 +00:00
Scott Moreau 6d29937e15 server: Add remaining support for simple copy/paste
- Add function to set callback for client_cut_text
- Add server_cut_text structure and function

This in conjunction with wayvnc #66 closes #4.
2020-09-21 21:15:45 +00:00
Andri Yngvason 6ad4aba374 examples: Make all functions static
This fixes compiler warnings
2020-07-26 13:52:00 +00:00
Andri Yngvason bb5e4ef7e1 README: Remove text about beta release 2020-07-26 13:49:00 +00:00
Andri Yngvason addcc50483 Bake version info into library 2020-07-26 11:43:29 +00:00
Andri Yngvason 53db2b8c1b Remove optimisation flags
Let's leave it to the package maintainers
2020-07-26 11:43:29 +00:00
Andri Yngvason e1c0923915 stream: Add byte counters 2020-07-26 11:43:29 +00:00
Jan Beich e862347ab5 meson: allow static linking 2020-07-22 21:15:49 +00:00
Andri Yngvason b52f5cd6c9 Resize tight encoder on frame resize 2020-07-19 16:58:55 +00:00
Andri Yngvason 76beec6415 tight: Add method to resize encoder grid 2020-07-19 16:57:44 +00:00
Andri Yngvason d4a5ed4133 stream: Ignore events after close instead of aborting
Aborting places an unnecessary restriction on the event loop library.
2020-07-19 13:22:57 +00:00
Andri Yngvason a0801f04e4 Implement server-side resizing 2020-07-19 11:54:39 +00:00
Andri Yngvason eb4b9d71a4 Return failure if display buffer is not set in on_connect 2020-07-18 14:04:12 +00:00
Andri Yngvason 683776cdf0 server: Assert that a client has a stream open when processing a message 2020-07-16 18:14:52 +00:00
Andri Yngvason 33eda8c5d0 Always enable tight, encoding but allow disabling lossy tight encoding 2020-07-11 20:16:27 +00:00
Andri Yngvason 497f9adb29 tight: Re-implement with threads 2020-07-11 20:06:45 +00:00
Andri Yngvason 9b54f6d936 Clean up dispatch hander on exit 2020-07-06 16:43:53 +00:00
Andri Yngvason 52e30e795b Align with neatvnc API changes 2020-07-06 16:43:15 +00:00
Andri Yngvason 7fb2215c0f Use raw encoding by default if no encoding has been selected
This complies with the standard.
2020-05-31 22:56:27 +00:00
Andri Yngvason 569ad287c5 Fix version error handling 2020-05-29 20:18:05 +00:00
Andri Yngvason 91bd49848f Build benchmarks again 2020-05-27 21:59:34 +00:00
Stefan Agner d2527e57f0 fix build warnings when building without tls
Fix trivial build warnings when building without TLS support.
2020-05-22 20:54:53 +00:00
Stefan Agner 8f9c71bb33 fix below zero message count check
size_t is unsigned and hence can't be below zero, triggering this gcc
warning with gcc 10:
  warning: comparison of unsigned expression in ‘< 0’ is always false [-Wtype-limits]

It seems this if statement is meant to check if there are messages to
process (larger than 0). If there are no messages, we should jump out
early.
2020-05-21 17:44:31 +00:00
Stefan Agner a37eed4a4a remove fb_lock/unlock
The two functions have been removed from the external header file.
Remove them and the now unnecessary field is_locked.
2020-05-21 17:44:31 +00:00
Stefan Agner afbeae8410 add warning if a function is not declared
Add a warning if a function is not declared. Functions used only inside
a compile unit still can be used, but have to be declared with the
static keyword.
2020-05-21 17:44:31 +00:00
Stefan Agner 8316994dfa use static for functions not used outside this compile unit
Add static for all functions only used inside the individual compile
units. This helps the compiler to potentially inline these functions.

This allows to use neatvnc as a Meson subproject in Weston which has
-Wmissing-prototypes enabled by default.
2020-05-21 17:44:31 +00:00
Andri Yngvason c6c567cfaf meson_options: Set the default SIMD extension to sse2
Too may people have old hardware.
2020-05-18 20:11:32 +00:00
Andri Yngvason 477ab4c481 tight: Allow lossy encoding with 16 bpp 2020-05-05 21:34:45 +00:00
Andri Yngvason f3e09fd622 Prepare API for multi-display support
These changes are made now to make it possible to add multi-display
support in the future while keeping the public interface stable.
2020-04-12 18:16:19 +00:00
Andri Yngvason 737dd311a0 Remove nvnc_set_dimensions()
This information is now figured out based on the nvnc_fb passed into
nvnc_set_buffer()
2020-04-12 16:08:33 +00:00
Andri Yngvason 97899ed045 meson: Set -DNDEBUG for all other build types than debug or debugoptimized 2020-04-12 14:25:49 +00:00
Andri Yngvason d12e66b043 raw-encoding: Add copyright notice 2020-04-12 13:18:42 +00:00
Andri Yngvason 2b2d12c755 damage: Add copyright notice 2020-04-12 13:18:28 +00:00
Andri Yngvason a616423c49 fb: Add copyright notice 2020-04-12 13:17:54 +00:00
Andri Yngvason c6f1ab616e tight: Replace an assert with return -1 2020-04-10 12:40:57 +00:00
Andri Yngvason 0350ba1d16 zrle: Replace an assert with return -1 2020-04-10 12:36:51 +00:00
Andri Yngvason a0d49f774a meson: Let user choose x86_64 SIMD extension for release build 2020-04-10 12:35:05 +00:00
Andri Yngvason 297e22b588 Don't render when a client is still encoding 2020-04-10 12:07:35 +00:00
Andri Yngvason 1c2a2231d6 tight: Don't drop MSB when encoding rect size 2020-04-10 11:51:40 +00:00
Andri Yngvason ee2adedfd1 examples: draw: Draw a larget dot 2020-04-08 22:58:14 +00:00
Andri Yngvason 77b866096d Redesign framebuffer update loop
Rendering may now only happen inside the rendering callback. The user is
also allowed to change out the entire buffer in the callback.

The callback is triggered by nvnc_damage_region(), nvnc_damage_whole()
and/or framebuffer update requests.

This fixes #26
2020-04-07 23:35:57 +00:00
Andri Yngvason 048b796ff5 Enable SO_REUSEADDR again 2020-04-06 18:29:17 +00:00
Andri Yngvason 11a73c5cb0 Revert "tight: Limit rectangle max width according to spec"
This reverts commit 999c1ef255.

This sends more rects than the original rect count allows for.
2020-04-06 00:01:32 +00:00
Andri Yngvason 1976221afe Resolve host names in nvnc_open()
This fixes #23
2020-04-04 22:26:00 +00:00
Andri Yngvason e3fae7b2bf Fix turbojpeg dependency
It is now actually optional
2020-04-04 20:10:06 +00:00
Andri Yngvason 8fc5c18b28 Don't show partial frames to new clients 2020-04-04 14:00:03 +00:00
Andri Yngvason f45f90ed9b nvnc_fb: Add a mechanism for signaling that a frame is in use 2020-04-04 12:33:18 +00:00
Andri Yngvason 8c27878dd1 README: Add zlib to list of runtime dependencies 2020-04-03 23:30:35 +00:00
Andri Yngvason b0799f7490 server: Add dtrace probe for stream_send inside update_fb_done 2020-04-03 23:11:12 +00:00
Andri Yngvason 14fed5c182 Enable tight encoding by default 2020-04-03 22:55:55 +00:00
Andri Yngvason a54b9ddd8e tight: Check quality level at start of each frame 2020-04-03 22:54:46 +00:00
Andri Yngvason b44d1a1f58 Replace miniz with system provided zlib
miniz is simply broken, so I can't use it.
2020-04-03 22:39:55 +00:00
Andri Yngvason 25626a9048 tight: Implement quality control 2020-04-03 20:09:26 +00:00
Andri Yngvason 17e0d6036f tight: Implement bare minimum "basic" encoding 2020-04-03 00:18:54 +00:00
Andri Yngvason cfb2abfc58 tight: Prepare for "basic" encoding method 2020-04-02 21:52:04 +00:00
Andri Yngvason 999c1ef255 tight: Limit rectangle max width according to spec 2020-04-02 21:26:12 +00:00
Andri Yngvason 1724797a27 tight: Add some error handling 2020-04-02 21:12:09 +00:00
Andri Yngvason 10a3fd6238 Add dtrace probes for framebuffer updates 2020-04-01 22:53:22 +00:00
Andri Yngvason dc70f2b409 Add dtrace probe infrastructure 2020-04-01 22:49:58 +00:00
Andri Yngvason 22eba2bed8 Make sure framebuffers are properly aligned 2020-03-29 13:16:05 +00:00
Andri Yngvason f0974e5af6 stream: Close if read() == 0 2020-03-24 19:15:27 +00:00
Andri Yngvason b3b41d312a
Merge pull request #24 from johnae/master
Remove libuv header include
2020-03-24 09:03:07 +00:00
John Axel Eriksson d1c7402acd
Remove libuv header include 2020-03-24 07:04:05 +01:00
Andri Yngvason a77eb34fba Spawn as many workers as there are CPU cores 2020-03-21 17:27:30 +00:00
Andri Yngvason 9bf961992b README: Replace libuv with aml 2020-03-21 17:07:42 +00:00
Andri Yngvason be085c9a79 examples: Add signal handler and clean up resources 2020-03-21 16:59:10 +00:00
Andri Yngvason 76e721c4cd Replace libuv with aml 2020-03-21 16:59:10 +00:00
Andri Yngvason 3b7839e53b .gitignore: Add subprojects 2020-03-16 20:08:48 +00:00
Andri Yngvason bc0cea86ae Release 0.1.0 2020-02-21 23:04:19 +00:00
Andri Yngvason 992b4445ed tight: Add copyright notice 2020-02-09 12:03:14 +00:00
Andri Yngvason d2dc5ff91c Remove makefiles in favour of meson
I'd rather maintain a single set of build scripts
2020-02-09 11:53:47 +00:00
Andri Yngvason 3ecede86c3 Build examples using meson 2020-02-09 11:41:43 +00:00
Andri Yngvason 61efca48f0 damage: Increment/decrement reference count before/after check
This is prudent. The user might throw away these buffers before the
damage check finishes.
2020-01-29 21:33:13 +00:00
Andri Yngvason c29e747ecf bsd queue: Remove #include <sys/cdefs.h>
This fixes #17, failing compilation with musl
2020-01-29 19:59:14 +00:00
Andri Yngvason d0a92c2e8d COPYING: The year is 2020 2020-01-29 19:49:11 +00:00
Andri Yngvason 10473597c5 stream: Fix flushing when tls is disabled 2020-01-29 17:03:55 +00:00
Andri Yngvason 844645d63b
Merge pull request #15 from agners/example-fixes
Fix examples
2020-01-27 00:17:09 +00:00
Stefan Agner 965db59ecf use installed header file
Treat neatvnc as a proper library when using examples.
2020-01-26 23:24:09 +01:00
Stefan Agner 1e0612a81e Remove unused/unexisting include util.h
The header file util.h has been remvoed from the codebase with
commit caf9fe0130 ("Remove unused code"), remove it from pngfb.c as
well. This allows to build the examples again.
2020-01-26 12:45:06 +01:00
Andri Yngvason 993ad80b86 README: Add compatibility table 2020-01-25 20:26:31 +00:00
Andri Yngvason 32f953e3a2 README: Add gnutls to the dependency list 2020-01-25 20:00:17 +00:00
93 changed files with 19038 additions and 11773 deletions

View File

@ -0,0 +1 @@
Please read CONTRIBUTING.md before making a pull request.

4
.gitignore vendored
View File

@ -1,7 +1,11 @@
.clang_complete
.ycm_extra_conf.py
.vimrc
vgcore.*
perf.data
perf.data.old
build
experiments
subprojects
sandbox
.vscode

1
CONTRIBUTING.md 100644
View File

@ -0,0 +1 @@
See wayvnc's [CONTRIBUTING.md](https://github.com/any1/wayvnc/blob/master/CONTRIBUTING.md).

View File

@ -1,4 +1,4 @@
Copyright (c) 2019 Andri Yngvason
Copyright (c) 2019 - 2020 Andri Yngvason
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice

2
FUNDING.yml 100644
View File

@ -0,0 +1,2 @@
github: any1
patreon: andriyngvason

View File

@ -1,68 +0,0 @@
DEPENDENCIES := pixman-1 libuv libturbojpeg
SOURCES := \
src/server.c \
src/util.c \
src/vec.c \
src/zrle.c \
src/tight.c \
src/raw-encoding.c \
src/pixels.c \
src/damage.c \
src/fb.c \
include common.mk
VERSION=0.0.0
DSO_NAME=libneatvnc
DSO_MAJOR=0
DSO_MINOR=0
CFLAGS += -fvisibility=hidden -Icontrib/miniz
OBJECTS += $(BUILD_DIR)/miniz.o
DSO_PATH := $(BUILD_DIR)/$(DSO_NAME)
DSO := $(DSO_PATH).so.$(DSO_MAJOR).$(DSO_MINOR)
ifndef DONT_STRIP
INSTALL_STRIP := -s --strip-program=$(STRIP)
endif
$(DSO): $(OBJECTS)
$(LINK_DSO)
ln -sf $(DSO_NAME).so.$(DSO_MAJOR).$(DSO_MINOR) $(DSO_PATH).so.$(DSO_MINOR)
ln -sf $(DSO_NAME).so.$(DSO_MAJOR).$(DSO_MINOR) $(DSO_PATH).so
$(BUILD_DIR)/%.o: src/%.c | $(BUILD_DIR) ; $(CC_OBJ)
$(BUILD_DIR)/miniz.o: contrib/miniz/miniz.c | $(BUILD_DIR) ; $(CC_OBJ)
$(BUILD_DIR)/neatvnc.pc:
PREFIX=$(PREFIX) VERSION=$(VERSION) ./gen-pkgconfig.sh >$@
BENCH_DIR = $(BUILD_DIR)/bench
$(BENCH_DIR)/%.o: bench/%.c | $(BENCH_DIR)
$(CC_OBJ) $(shell $(PKGCONFIG) --cflags libpng)
$(BENCH_DIR): ; mkdir -p $@
$(BENCH_DIR)/zrle-bench:
$(BENCH_DIR)/zrle-bench: $(OBJECTS) $(BUILD_DIR)/pngfb.o \
$(BENCH_DIR)/zrle-bench.o
$(LINK_EXE) $(shell $(PKGCONFIG) --libs libpng)
.PHONY: install
install: $(DSO) $(BUILD_DIR)/neatvnc.pc
install $(INSTALL_STRIP) -Dt $(DESTDIR)$(PREFIX)/lib $(BUILD_DIR)/*.so*
install -Dt $(DESTDIR)$(PREFIX)/lib/pkgconfig $(BUILD_DIR)/neatvnc.pc
install -Dt $(DESTDIR)$(PREFIX)/include include/neatvnc.h
.PHONY: bench
bench: $(BENCH_DIR)/zrle-bench
./$(BENCH_DIR)/zrle-bench
.PHONY: examples
examples: $(DSO)
make -C examples \
BUILD_DIR=../$(BUILD_DIR)/examples \
LIB_PATH=../$(BUILD_DIR)

View File

@ -1,8 +1,8 @@
# Neat VNC (Beta)
# Neat VNC
## Introduction
This is a liberally licensed VNC server library that's intended to be fast and
neat. Note: This is a beta release, so the interface is not yet stable.
neat.
## Goals
* Speed.
@ -12,14 +12,22 @@ neat. Note: This is a beta release, so the interface is not yet stable.
## Building
### Runtime Dependencies
* pixman
* libuv
* aml - https://github.com/any1/aml/
* ffmpeg (optional)
* gbm (optional)
* gnutls (optional)
* libdrm (optional)
* libturbojpeg (optional)
* nettle (optional)
* hogweed (optional)
* gmp (optional)
* pixman
* zlib
### Build Dependencies
* libdrm
* meson
* pkg-config
* libdrm
To build just run:
```

View File

@ -1,24 +0,0 @@
---
BasedOnStyle: LLVM
SortIncludes: false
IndentWidth: 8
ContinuationIndentWidth: 8
UseTab: ForIndentation
BreakBeforeBraces: Linux
AllowShortIfStatementsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
IndentCaseLabels: false
#IndentGotoLabels: false
PointerAlignment: Left
ForEachMacros:
- LIST_FOREACH
PenaltyBreakAssignment: 2
PenaltyExcessCharacter: 10
...

20
bench/meson.build 100644
View File

@ -0,0 +1,20 @@
libpng = dependency('libpng', required: false)
if libpng.found()
executable(
'zrle-bench',
[
'zrle-bench.c',
'../src/zrle.c',
'../src/pngfb.c',
'../src/pixels.c',
'../src/vec.c',
],
dependencies: [
neatvnc_dep,
pixman,
aml,
libpng,
]
)
endif

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Andri Yngvason
* Copyright (c) 2019 - 2021 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -16,35 +16,35 @@
#include "zrle.h"
#include "rfb-proto.h"
#include "util.h"
#include "vec.h"
#include "neatvnc.h"
#include "miniz.h"
#include "pixels.h"
#include <stdio.h>
#include <stdlib.h>
#include <libdrm/drm_fourcc.h>
#include <pixman.h>
#include <time.h>
#include <inttypes.h>
uint64_t gettime_us(clockid_t clock)
static uint64_t gettime_us(clockid_t clock)
{
struct timespec ts = { 0 };
clock_gettime(clock, &ts);
return ts.tv_sec * 1000000ULL + ts.tv_nsec / 1000ULL;
}
#pragma push_options
#pragma GCC push_options
#pragma GCC optimize ("-O0")
void memcpy_unoptimized(void* dst, const void* src, size_t len)
static void memcpy_unoptimized(void* dst, const void* src, size_t len)
{
memcpy(dst, src, len);
}
#pragma pop_options
#pragma GCC pop_options
struct nvnc_fb* read_png_file(const char *filename);
int run_benchmark(const char *image)
static int run_benchmark(const char *image)
{
int rc = -1;
@ -55,6 +55,7 @@ int run_benchmark(const char *image)
void *addr = nvnc_fb_get_addr(fb);
int width = nvnc_fb_get_width(fb);
int height = nvnc_fb_get_height(fb);
int stride = nvnc_fb_get_stride(fb);
struct rfb_pixel_format pixfmt;
rfb_pixfmt_from_fourcc(&pixfmt, DRM_FORMAT_ARGB8888);
@ -65,7 +66,7 @@ int run_benchmark(const char *image)
pixman_region_union_rect(&region, &region, 0, 0, width, height);
struct vec frame;
vec_init(&frame, width * height * 3 / 2);
vec_init(&frame, stride * height * 3 / 2);
z_stream zs = { 0 };
@ -75,13 +76,13 @@ int run_benchmark(const char *image)
/* mem level: */ 9,
/* strategy: */ Z_DEFAULT_STRATEGY);
void *dummy = malloc(width * height * 4);
void *dummy = malloc(stride * height * 4);
if (!dummy)
goto failure;
uint64_t start_time = gettime_us(CLOCK_PROCESS_CPUTIME_ID);
memcpy_unoptimized(dummy, addr, width * height * 4);
memcpy_unoptimized(dummy, addr, stride * height * 4);
uint64_t end_time = gettime_us(CLOCK_PROCESS_CPUTIME_ID);
printf("memcpy baseline for %s took %"PRIu64" micro seconds\n", image,
@ -96,7 +97,7 @@ int run_benchmark(const char *image)
printf("Encoding %s took %"PRIu64" micro seconds\n", image,
end_time - start_time);
double orig_size = width * height * 4;
double orig_size = stride * height * 4;
double compressed_size = frame.len;
double reduction = (orig_size - compressed_size) / orig_size;

View File

@ -1,51 +0,0 @@
MACHINE := $(shell $(CC) -dumpmachine)
ARCH := $(firstword $(subst -, ,$(MACHINE)))
BUILD_DIR ?= build-$(MACHINE)
PREFIX ?= /usr/local
ifeq ($(ARCH),x86_64)
ARCH_CFLAGS := -mavx
else
ifeq ($(ARCH),arm)
ARCH_CFLAGS := -mfpu=neon
endif # end arm block
endif # end x86_64 block
ifeq (, $(shell which $(MACHINE)-strip 2>/dev/null))
STRIP ?= strip
else
STRIP ?= $(MACHINE)-strip
endif
ifeq (, $(shell which $(MACHINE)-pkg-config 2>/dev/null))
PKGCONFIG ?= pkg-config
else
PKGCONFIG ?= $(MACHINE)-pkg-config
endif
CFLAGS ?= -g -O3 $(ARCH_CFLAGS) -flto -DNDEBUG
LDFLAGS ?= -flto
CFLAGS += -std=gnu11 -D_GNU_SOURCE -Iinclude
CC_OBJ = $(CC) -c $(CFLAGS) $< -o $@ -MMD -MP -MF $(@:.o=.deps)
LINK_EXE = $(CC) $^ $(LDFLAGS) -o $@
LINK_DSO = $(CC) -fPIC -shared $^ $(LDFLAGS) -o $@
CFLAGS += $(foreach dep,$(DEPENDENCIES),$(shell $(PKGCONFIG) --cflags $(dep)))
LDFLAGS += $(foreach dep,$(DEPENDENCIES),$(shell $(PKGCONFIG) --libs $(dep)))
OBJECTS := $(SOURCES:src/%.c=$(BUILD_DIR)/%.o)
$(BUILD_DIR): ; mkdir -p $(BUILD_DIR)
.PHONY: clean
clean: ; rm -rf $(BUILD_DIR)
-include $(BUILD_DIR)/*.deps
.SUFFIXES:
.SECONDARY:
# This clears the default target set by this file
.DEFAULT_GOAL :=

View File

@ -1,176 +0,0 @@
## Changelog
### 2.1.0
- More instances of memcpy instead of cast and use memcpy per default
- Remove inline for c90 support
- New function to read files via callback functions when adding them
- Fix out of bounds read while reading Zip64 extended information
- guard memcpy when n == 0 because buffer may be NULL
- Implement inflateReset() function
- Move comp/decomp alloc/free prototypes under guarding #ifndef MZ_NO_MALLOC
- Fix large file support under Windows
- Don't warn if _LARGEFILE64_SOURCE is not defined to 1
- Fixes for MSVC warnings
- Remove check that path of file added to archive contains ':' or '\'
- Add !defined check on MINIZ_USE_ALIGNED_LOADS_AND_STORES
### 2.0.8
- Remove unimplemented functions (mz_zip_locate_file and mz_zip_locate_file_v2)
- Add license, changelog, readme and example files to release zip
- Fix heap overflow to user buffer in tinfl_status tinfl_decompress
- Fix corrupt archive if uncompressed file smaller than 4 byte and the file is added by mz_zip_writer_add_mem*
### 2.0.7
- Removed need in C++ compiler in cmake build
- Fixed a lot of uninitialized value errors found with Valgrind by memsetting m_dict to 0 in tdefl_init
- Fix resource leak in mz_zip_reader_init_file_v2
- Fix assert with mz_zip_writer_add_mem* w/MZ_DEFAULT_COMPRESSION
- cmake build: install library and headers
- Remove _LARGEFILE64_SOURCE requirement from apple defines for large files
### 2.0.6
- Improve MZ_ZIP_FLAG_WRITE_ZIP64 documentation
- Remove check for cur_archive_file_ofs > UINT_MAX because cur_archive_file_ofs is not used after this point
- Add cmake debug configuration
- Fix PNG height when creating png files
- Add "iterative" file extraction method based on mz_zip_reader_extract_to_callback.
- Option to use memcpy for unaligned data access
- Define processor/arch macros as zero if not set to one
### 2.0.4/2.0.5
- Fix compilation with the various omission compile definitions
### 2.0.3
- Fix GCC/clang compile warnings
- Added callback for periodic flushes (for ZIP file streaming)
- Use UTF-8 for file names in ZIP files per default
### 2.0.2
- Fix source backwards compatibility with 1.x
- Fix a ZIP bit not being set correctly
### 2.0.1
- Added some tests
- Added CI
- Make source code ANSI C compatible
### 2.0.0 beta
- Matthew Sitton merged miniz 1.x to Rich Geldreich's vogl ZIP64 changes. Miniz is now licensed as MIT since the vogl code base is MIT licensed
- Miniz is now split into several files
- Miniz does now not seek backwards when creating ZIP files. That is the ZIP files can be streamed
- Miniz automatically switches to the ZIP64 format when the created ZIP files goes over ZIP file limits
- Similar to [SQLite](https://www.sqlite.org/amalgamation.html) the Miniz source code is amalgamated into one miniz.c/miniz.h pair in a build step (amalgamate.sh). Please use miniz.c/miniz.h in your projects
- Miniz 2 is only source back-compatible with miniz 1.x. It breaks binary compatibility because structures changed
### v1.16 BETA Oct 19, 2013
Still testing, this release is downloadable from [here](http://www.tenacioussoftware.com/miniz_v116_beta_r1.7z). Two key inflator-only robustness and streaming related changes. Also merged in tdefl_compressor_alloc(), tdefl_compressor_free() helpers to make script bindings easier for rustyzip. I would greatly appreciate any help with testing or any feedback.
The inflator in raw (non-zlib) mode is now usable on gzip or similar streams that have a bunch of bytes following the raw deflate data (problem discovered by rustyzip author williamw520). This version should never read beyond the last byte of the raw deflate data independent of how many bytes you pass into the input buffer.
The inflator now has a new failure status TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS (-4). Previously, if the inflator was starved of bytes and could not make progress (because the input buffer was empty and the caller did not set the TINFL_FLAG_HAS_MORE_INPUT flag - say on truncated or corrupted compressed data stream) it would append all 0's to the input and try to soldier on. This is scary behavior if the caller didn't know when to stop accepting output (because it didn't know how much uncompressed data was expected, or didn't enforce a sane maximum). v1.16 will instead return TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS immediately if it needs 1 or more bytes to make progress, the input buf is empty, and the caller has indicated that no more input is available. This is a "soft" failure, so you can call the inflator again with more input and it will try to continue, or you can give up and fail. This could be very useful in network streaming scenarios.
- The inflator coroutine func. is subtle and complex so I'm being cautious about this release. I would greatly appreciate any help with testing or any feedback.
I feel good about these changes, and they've been through several hours of automated testing, but they will probably not fix anything for the majority of prev. users so I'm
going to mark this release as beta for a few weeks and continue testing it at work/home on various things.
- The inflator in raw (non-zlib) mode is now usable on gzip or similiar data streams that have a bunch of bytes following the raw deflate data (problem discovered by rustyzip author williamw520).
This version should *never* read beyond the last byte of the raw deflate data independent of how many bytes you pass into the input buffer. This issue was caused by the various Huffman bitbuffer lookahead optimizations, and
would not be an issue if the caller knew and enforced the precise size of the raw compressed data *or* if the compressed data was in zlib format (i.e. always followed by the byte aligned zlib adler32).
So in other words, you can now call the inflator on deflate streams that are followed by arbitrary amounts of data and it's guaranteed that decompression will stop exactly on the last byte.
- The inflator now has a new failure status: TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS (-4). Previously, if the inflator was starved of bytes and could not make progress (because the input buffer was empty and the
caller did not set the TINFL_FLAG_HAS_MORE_INPUT flag - say on truncated or corrupted compressed data stream) it would append all 0's to the input and try to soldier on.
This is scary, because in the worst case, I believe it was possible for the prev. inflator to start outputting large amounts of literal data. If the caller didn't know when to stop accepting output
(because it didn't know how much uncompressed data was expected, or didn't enforce a sane maximum) it could continue forever. v1.16 cannot fall into this failure mode, instead it'll return
TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS immediately if it needs 1 or more bytes to make progress, the input buf is empty, and the caller has indicated that no more input is available. This is a "soft"
failure, so you can call the inflator again with more input and it will try to continue, or you can give up and fail. This could be very useful in network streaming scenarios.
- Added documentation to all the tinfl return status codes, fixed miniz_tester so it accepts double minus params for Linux, tweaked example1.c, added a simple "follower bytes" test to miniz_tester.cpp.
### v1.15 r4 STABLE - Oct 13, 2013
Merged over a few very minor bug fixes that I fixed in the zip64 branch. This is downloadable from [here](http://code.google.com/p/miniz/downloads/list) and also in SVN head (as of 10/19/13).
### v1.15 - Oct. 13, 2013
Interim bugfix release while I work on the next major release with zip64 and streaming compression/decompression support. Fixed the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com), which could cause the locate files func to not find files when this flag was specified. Also fixed a bug in mz_zip_reader_extract_to_mem_no_alloc() with user provided read buffers (thanks kymoon). I also merged lots of compiler fixes from various github repo branches and Google Code issue reports. I finally added cmake support (only tested under for Linux so far), compiled and tested with clang v3.3 and gcc 4.6 (under Linux), added defl_write_image_to_png_file_in_memory_ex() (supports Y flipping for OpenGL use, real-time compression), added a new PNG example (example6.c - Mandelbrot), and I added 64-bit file I/O support (stat64(), etc.) for glibc.
- Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug
would only have occured in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place()
(which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag).
- Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size
- Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries.
Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice).
- Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes
- mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed
- Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6.
- Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti
- Merged MZ_FORCEINLINE fix from hdeanclark
- Fix <time.h> include before config #ifdef, thanks emil.brink
- Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can
set it to 1 for real-time compression).
- Merged in some compiler fixes from paulharris's github repro.
- Retested this build under Windows (VS 2010, including static analysis), tcc 0.9.26, gcc v4.6 and clang v3.3.
- Added example6.c, which dumps an image of the mandelbrot set to a PNG file.
- Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more.
- In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled
- In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch
### v1.14 - May 20, 2012
(SVN Only) Minor tweaks to get miniz.c compiling with the Tiny C Compiler, added #ifndef MINIZ_NO_TIME guards around utime.h includes. Adding mz_free() function, so the caller can free heap blocks returned by miniz using whatever heap functions it has been configured to use, MSVC specific fixes to use "safe" variants of several functions (localtime_s, fopen_s, freopen_s).
MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include <time.h> (thanks fermtect).
Compiler specific fixes, some from fermtect. I upgraded to TDM GCC 4.6.1 and now static __forceinline is giving it fits, so I'm changing all usage of __forceinline to MZ_FORCEINLINE and forcing gcc to use __attribute__((__always_inline__)) (and MSVC to use __forceinline). Also various fixes from fermtect for MinGW32: added #include , 64-bit ftell/fseek fixes.
### v1.13 - May 19, 2012
From jason@cornsyrup.org and kelwert@mtu.edu - Most importantly, fixed mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bits. Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. Other stuff:
Eliminated a bunch of warnings when compiling with GCC 32-bit/64. Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning).
Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.)
Fix ftell() usage in a few of the examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). Fix fail logic handling in mz_zip_add_mem_to_archive_file_in_place() so it always calls mz_zip_writer_finalize_archive() and mz_zip_writer_end(), even if the file add fails.
- From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit.
- Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files.
- Eliminated a bunch of warnings when compiling with GCC 32-bit/64.
- Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly
"Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning).
- Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64.
- Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test.
- Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives.
- Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.)
- Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself).
### v1.12 - 4/12/12
More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's.
level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson <bruced@valvesoftware.com> for the feedback/bug report.
### v1.11 - 5/28/11
Added statement from unlicense.org
### v1.10 - 5/27/11
- Substantial compressor optimizations:
- Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86).
- Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types.
- Refactored the compression code for better readability and maintainability.
- Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large drop in throughput on some files).
### v1.09 - 5/15/11
Initial stable release.

View File

@ -1,22 +0,0 @@
Copyright 2013-2014 RAD Game Tools and Valve Software
Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
All Rights Reserved.
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.

View File

@ -1,105 +0,0 @@
// example1.c - Demonstrates miniz.c's compress() and uncompress() functions (same as zlib's).
// Public domain, May 15 2011, Rich Geldreich, richgel99@gmail.com. See "unlicense" statement at the end of tinfl.c.
#include <stdio.h>
#include "miniz.h"
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint;
// The string to compress.
static const char *s_pStr = "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \
"Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \
"Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \
"Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \
"Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \
"Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \
"Good morning Dr. Chandra. This is Hal. I am ready for my first lesson.";
int main(int argc, char *argv[])
{
uint step = 0;
int cmp_status;
uLong src_len = (uLong)strlen(s_pStr);
uLong cmp_len = compressBound(src_len);
uLong uncomp_len = src_len;
uint8 *pCmp, *pUncomp;
uint total_succeeded = 0;
(void)argc, (void)argv;
printf("miniz.c version: %s\n", MZ_VERSION);
do
{
// Allocate buffers to hold compressed and uncompressed data.
pCmp = (mz_uint8 *)malloc((size_t)cmp_len);
pUncomp = (mz_uint8 *)malloc((size_t)src_len);
if ((!pCmp) || (!pUncomp))
{
printf("Out of memory!\n");
return EXIT_FAILURE;
}
// Compress the string.
cmp_status = compress(pCmp, &cmp_len, (const unsigned char *)s_pStr, src_len);
if (cmp_status != Z_OK)
{
printf("compress() failed!\n");
free(pCmp);
free(pUncomp);
return EXIT_FAILURE;
}
printf("Compressed from %u to %u bytes\n", (mz_uint32)src_len, (mz_uint32)cmp_len);
if (step)
{
// Purposely corrupt the compressed data if fuzzy testing (this is a very crude fuzzy test).
uint n = 1 + (rand() % 3);
while (n--)
{
uint i = rand() % cmp_len;
pCmp[i] ^= (rand() & 0xFF);
}
}
// Decompress.
cmp_status = uncompress(pUncomp, &uncomp_len, pCmp, cmp_len);
total_succeeded += (cmp_status == Z_OK);
if (step)
{
printf("Simple fuzzy test: step %u total_succeeded: %u\n", step, total_succeeded);
}
else
{
if (cmp_status != Z_OK)
{
printf("uncompress failed!\n");
free(pCmp);
free(pUncomp);
return EXIT_FAILURE;
}
printf("Decompressed from %u to %u bytes\n", (mz_uint32)cmp_len, (mz_uint32)uncomp_len);
// Ensure uncompress() returned the expected data.
if ((uncomp_len != src_len) || (memcmp(pUncomp, s_pStr, (size_t)src_len)))
{
printf("Decompression failed!\n");
free(pCmp);
free(pUncomp);
return EXIT_FAILURE;
}
}
free(pCmp);
free(pUncomp);
step++;
// Keep on fuzzy testing if there's a non-empty command line.
} while (argc >= 2);
printf("Success.\n");
return EXIT_SUCCESS;
}

View File

@ -1,164 +0,0 @@
// example2.c - Simple demonstration of miniz.c's ZIP archive API's.
// Note this test deletes the test archive file "__mz_example2_test__.zip" in the current directory, then creates a new one with test data.
// Public domain, May 15 2011, Rich Geldreich, richgel99@gmail.com. See "unlicense" statement at the end of tinfl.c.
#if defined(__GNUC__)
// Ensure we get the 64-bit variants of the CRT's file I/O calls
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE 1
#endif
#endif
#include <stdio.h>
#include "miniz_zip.h"
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint;
// The string to compress.
static const char *s_pTest_str =
"MISSION CONTROL I wouldn't worry too much about the computer. First of all, there is still a chance that he is right, despite your tests, and" \
"if it should happen again, we suggest eliminating this possibility by allowing the unit to remain in place and seeing whether or not it" \
"actually fails. If the computer should turn out to be wrong, the situation is still not alarming. The type of obsessional error he may be" \
"guilty of is not unknown among the latest generation of HAL 9000 computers. It has almost always revolved around a single detail, such as" \
"the one you have described, and it has never interfered with the integrity or reliability of the computer's performance in other areas." \
"No one is certain of the cause of this kind of malfunctioning. It may be over-programming, but it could also be any number of reasons. In any" \
"event, it is somewhat analogous to human neurotic behavior. Does this answer your query? Zero-five-three-Zero, MC, transmission concluded.";
static const char *s_pComment = "This is a comment";
int main(int argc, char *argv[])
{
int i, sort_iter;
mz_bool status;
size_t uncomp_size;
mz_zip_archive zip_archive;
void *p;
const int N = 50;
char data[2048];
char archive_filename[64];
static const char *s_Test_archive_filename = "__mz_example2_test__.zip";
assert((strlen(s_pTest_str) + 64) < sizeof(data));
printf("miniz.c version: %s\n", MZ_VERSION);
(void)argc, (void)argv;
// Delete the test archive, so it doesn't keep growing as we run this test
remove(s_Test_archive_filename);
// Append a bunch of text files to the test archive
for (i = (N - 1); i >= 0; --i)
{
sprintf(archive_filename, "%u.txt", i);
sprintf(data, "%u %s %u", (N - 1) - i, s_pTest_str, i);
// Add a new file to the archive. Note this is an IN-PLACE operation, so if it fails your archive is probably hosed (its central directory may not be complete) but it should be recoverable using zip -F or -FF. So use caution with this guy.
// A more robust way to add a file to an archive would be to read it into memory, perform the operation, then write a new archive out to a temp file and then delete/rename the files.
// Or, write a new archive to disk to a temp file, then delete/rename the files. For this test this API is fine.
status = mz_zip_add_mem_to_archive_file_in_place(s_Test_archive_filename, archive_filename, data, strlen(data) + 1, s_pComment, (uint16)strlen(s_pComment), MZ_BEST_COMPRESSION);
if (!status)
{
printf("mz_zip_add_mem_to_archive_file_in_place failed!\n");
return EXIT_FAILURE;
}
}
// Add a directory entry for testing
status = mz_zip_add_mem_to_archive_file_in_place(s_Test_archive_filename, "directory/", NULL, 0, "no comment", (uint16)strlen("no comment"), MZ_BEST_COMPRESSION);
if (!status)
{
printf("mz_zip_add_mem_to_archive_file_in_place failed!\n");
return EXIT_FAILURE;
}
// Now try to open the archive.
memset(&zip_archive, 0, sizeof(zip_archive));
status = mz_zip_reader_init_file(&zip_archive, s_Test_archive_filename, 0);
if (!status)
{
printf("mz_zip_reader_init_file() failed!\n");
return EXIT_FAILURE;
}
// Get and print information about each file in the archive.
for (i = 0; i < (int)mz_zip_reader_get_num_files(&zip_archive); i++)
{
mz_zip_archive_file_stat file_stat;
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat))
{
printf("mz_zip_reader_file_stat() failed!\n");
mz_zip_reader_end(&zip_archive);
return EXIT_FAILURE;
}
printf("Filename: \"%s\", Comment: \"%s\", Uncompressed size: %u, Compressed size: %u, Is Dir: %u\n", file_stat.m_filename, file_stat.m_comment, (uint)file_stat.m_uncomp_size, (uint)file_stat.m_comp_size, mz_zip_reader_is_file_a_directory(&zip_archive, i));
if (!strcmp(file_stat.m_filename, "directory/"))
{
if (!mz_zip_reader_is_file_a_directory(&zip_archive, i))
{
printf("mz_zip_reader_is_file_a_directory() didn't return the expected results!\n");
mz_zip_reader_end(&zip_archive);
return EXIT_FAILURE;
}
}
}
// Close the archive, freeing any resources it was using
mz_zip_reader_end(&zip_archive);
// Now verify the compressed data
for (sort_iter = 0; sort_iter < 2; sort_iter++)
{
memset(&zip_archive, 0, sizeof(zip_archive));
status = mz_zip_reader_init_file(&zip_archive, s_Test_archive_filename, sort_iter ? MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY : 0);
if (!status)
{
printf("mz_zip_reader_init_file() failed!\n");
return EXIT_FAILURE;
}
for (i = 0; i < N; i++)
{
sprintf(archive_filename, "%u.txt", i);
sprintf(data, "%u %s %u", (N - 1) - i, s_pTest_str, i);
// Try to extract all the files to the heap.
p = mz_zip_reader_extract_file_to_heap(&zip_archive, archive_filename, &uncomp_size, 0);
if (!p)
{
printf("mz_zip_reader_extract_file_to_heap() failed!\n");
mz_zip_reader_end(&zip_archive);
return EXIT_FAILURE;
}
// Make sure the extraction really succeeded.
if ((uncomp_size != (strlen(data) + 1)) || (memcmp(p, data, strlen(data))))
{
printf("mz_zip_reader_extract_file_to_heap() failed to extract the proper data\n");
mz_free(p);
mz_zip_reader_end(&zip_archive);
return EXIT_FAILURE;
}
printf("Successfully extracted file \"%s\", size %u\n", archive_filename, (uint)uncomp_size);
printf("File data: \"%s\"\n", (const char *)p);
// We're done.
mz_free(p);
}
// Close the archive, freeing any resources it was using
mz_zip_reader_end(&zip_archive);
}
printf("Success.\n");
return EXIT_SUCCESS;
}

View File

@ -1,269 +0,0 @@
// example3.c - Demonstrates how to use miniz.c's deflate() and inflate() functions for simple file compression.
// Public domain, May 15 2011, Rich Geldreich, richgel99@gmail.com. See "unlicense" statement at the end of tinfl.c.
// For simplicity, this example is limited to files smaller than 4GB, but this is not a limitation of miniz.c.
#include <stdio.h>
#include <limits.h>
#include "miniz.h"
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint;
#define my_max(a,b) (((a) > (b)) ? (a) : (b))
#define my_min(a,b) (((a) < (b)) ? (a) : (b))
#define BUF_SIZE (1024 * 1024)
static uint8 s_inbuf[BUF_SIZE];
static uint8 s_outbuf[BUF_SIZE];
int main(int argc, char *argv[])
{
const char *pMode;
FILE *pInfile, *pOutfile;
uint infile_size;
int level = Z_BEST_COMPRESSION;
z_stream stream;
int p = 1;
const char *pSrc_filename;
const char *pDst_filename;
long file_loc;
printf("miniz.c version: %s\n", MZ_VERSION);
if (argc < 4)
{
printf("Usage: example3 [options] [mode:c or d] infile outfile\n");
printf("\nModes:\n");
printf("c - Compresses file infile to a zlib stream in file outfile\n");
printf("d - Decompress zlib stream in file infile to file outfile\n");
printf("\nOptions:\n");
printf("-l[0-10] - Compression level, higher values are slower.\n");
return EXIT_FAILURE;
}
while ((p < argc) && (argv[p][0] == '-'))
{
switch (argv[p][1])
{
case 'l':
{
level = atoi(&argv[1][2]);
if ((level < 0) || (level > 10))
{
printf("Invalid level!\n");
return EXIT_FAILURE;
}
break;
}
default:
{
printf("Invalid option: %s\n", argv[p]);
return EXIT_FAILURE;
}
}
p++;
}
if ((argc - p) < 3)
{
printf("Must specify mode, input filename, and output filename after options!\n");
return EXIT_FAILURE;
}
else if ((argc - p) > 3)
{
printf("Too many filenames!\n");
return EXIT_FAILURE;
}
pMode = argv[p++];
if (!strchr("cCdD", pMode[0]))
{
printf("Invalid mode!\n");
return EXIT_FAILURE;
}
pSrc_filename = argv[p++];
pDst_filename = argv[p++];
printf("Mode: %c, Level: %u\nInput File: \"%s\"\nOutput File: \"%s\"\n", pMode[0], level, pSrc_filename, pDst_filename);
// Open input file.
pInfile = fopen(pSrc_filename, "rb");
if (!pInfile)
{
printf("Failed opening input file!\n");
return EXIT_FAILURE;
}
// Determine input file's size.
fseek(pInfile, 0, SEEK_END);
file_loc = ftell(pInfile);
fseek(pInfile, 0, SEEK_SET);
if ((file_loc < 0) || (file_loc > INT_MAX))
{
// This is not a limitation of miniz or tinfl, but this example.
printf("File is too large to be processed by this example.\n");
return EXIT_FAILURE;
}
infile_size = (uint)file_loc;
// Open output file.
pOutfile = fopen(pDst_filename, "wb");
if (!pOutfile)
{
printf("Failed opening output file!\n");
return EXIT_FAILURE;
}
printf("Input file size: %u\n", infile_size);
// Init the z_stream
memset(&stream, 0, sizeof(stream));
stream.next_in = s_inbuf;
stream.avail_in = 0;
stream.next_out = s_outbuf;
stream.avail_out = BUF_SIZE;
if ((pMode[0] == 'c') || (pMode[0] == 'C'))
{
// Compression.
uint infile_remaining = infile_size;
if (deflateInit(&stream, level) != Z_OK)
{
printf("deflateInit() failed!\n");
return EXIT_FAILURE;
}
for ( ; ; )
{
int status;
if (!stream.avail_in)
{
// Input buffer is empty, so read more bytes from input file.
uint n = my_min(BUF_SIZE, infile_remaining);
if (fread(s_inbuf, 1, n, pInfile) != n)
{
printf("Failed reading from input file!\n");
return EXIT_FAILURE;
}
stream.next_in = s_inbuf;
stream.avail_in = n;
infile_remaining -= n;
//printf("Input bytes remaining: %u\n", infile_remaining);
}
status = deflate(&stream, infile_remaining ? Z_NO_FLUSH : Z_FINISH);
if ((status == Z_STREAM_END) || (!stream.avail_out))
{
// Output buffer is full, or compression is done, so write buffer to output file.
uint n = BUF_SIZE - stream.avail_out;
if (fwrite(s_outbuf, 1, n, pOutfile) != n)
{
printf("Failed writing to output file!\n");
return EXIT_FAILURE;
}
stream.next_out = s_outbuf;
stream.avail_out = BUF_SIZE;
}
if (status == Z_STREAM_END)
break;
else if (status != Z_OK)
{
printf("deflate() failed with status %i!\n", status);
return EXIT_FAILURE;
}
}
if (deflateEnd(&stream) != Z_OK)
{
printf("deflateEnd() failed!\n");
return EXIT_FAILURE;
}
}
else if ((pMode[0] == 'd') || (pMode[0] == 'D'))
{
// Decompression.
uint infile_remaining = infile_size;
if (inflateInit(&stream))
{
printf("inflateInit() failed!\n");
return EXIT_FAILURE;
}
for ( ; ; )
{
int status;
if (!stream.avail_in)
{
// Input buffer is empty, so read more bytes from input file.
uint n = my_min(BUF_SIZE, infile_remaining);
if (fread(s_inbuf, 1, n, pInfile) != n)
{
printf("Failed reading from input file!\n");
return EXIT_FAILURE;
}
stream.next_in = s_inbuf;
stream.avail_in = n;
infile_remaining -= n;
}
status = inflate(&stream, Z_SYNC_FLUSH);
if ((status == Z_STREAM_END) || (!stream.avail_out))
{
// Output buffer is full, or decompression is done, so write buffer to output file.
uint n = BUF_SIZE - stream.avail_out;
if (fwrite(s_outbuf, 1, n, pOutfile) != n)
{
printf("Failed writing to output file!\n");
return EXIT_FAILURE;
}
stream.next_out = s_outbuf;
stream.avail_out = BUF_SIZE;
}
if (status == Z_STREAM_END)
break;
else if (status != Z_OK)
{
printf("inflate() failed with status %i!\n", status);
return EXIT_FAILURE;
}
}
if (inflateEnd(&stream) != Z_OK)
{
printf("inflateEnd() failed!\n");
return EXIT_FAILURE;
}
}
else
{
printf("Invalid mode!\n");
return EXIT_FAILURE;
}
fclose(pInfile);
if (EOF == fclose(pOutfile))
{
printf("Failed writing to output file!\n");
return EXIT_FAILURE;
}
printf("Total input bytes: %u\n", (mz_uint32)stream.total_in);
printf("Total output bytes: %u\n", (mz_uint32)stream.total_out);
printf("Success.\n");
return EXIT_SUCCESS;
}

View File

@ -1,102 +0,0 @@
// example4.c - Uses tinfl.c to decompress a zlib stream in memory to an output file
// Public domain, May 15 2011, Rich Geldreich, richgel99@gmail.com. See "unlicense" statement at the end of tinfl.c.
#include "miniz_tinfl.h"
#include <stdio.h>
#include <limits.h>
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint;
#define my_max(a,b) (((a) > (b)) ? (a) : (b))
#define my_min(a,b) (((a) < (b)) ? (a) : (b))
static int tinfl_put_buf_func(const void* pBuf, int len, void *pUser)
{
return len == (int)fwrite(pBuf, 1, len, (FILE*)pUser);
}
int main(int argc, char *argv[])
{
int status;
FILE *pInfile, *pOutfile;
uint infile_size, outfile_size;
size_t in_buf_size;
uint8 *pCmp_data;
long file_loc;
if (argc != 3)
{
printf("Usage: example4 infile outfile\n");
printf("Decompresses zlib stream in file infile to file outfile.\n");
printf("Input file must be able to fit entirely in memory.\n");
printf("example3 can be used to create compressed zlib streams.\n");
return EXIT_FAILURE;
}
// Open input file.
pInfile = fopen(argv[1], "rb");
if (!pInfile)
{
printf("Failed opening input file!\n");
return EXIT_FAILURE;
}
// Determine input file's size.
fseek(pInfile, 0, SEEK_END);
file_loc = ftell(pInfile);
fseek(pInfile, 0, SEEK_SET);
if ((file_loc < 0) || (file_loc > INT_MAX))
{
// This is not a limitation of miniz or tinfl, but this example.
printf("File is too large to be processed by this example.\n");
return EXIT_FAILURE;
}
infile_size = (uint)file_loc;
pCmp_data = (uint8 *)malloc(infile_size);
if (!pCmp_data)
{
printf("Out of memory!\n");
return EXIT_FAILURE;
}
if (fread(pCmp_data, 1, infile_size, pInfile) != infile_size)
{
printf("Failed reading input file!\n");
return EXIT_FAILURE;
}
// Open output file.
pOutfile = fopen(argv[2], "wb");
if (!pOutfile)
{
printf("Failed opening output file!\n");
return EXIT_FAILURE;
}
printf("Input file size: %u\n", infile_size);
in_buf_size = infile_size;
status = tinfl_decompress_mem_to_callback(pCmp_data, &in_buf_size, tinfl_put_buf_func, pOutfile, TINFL_FLAG_PARSE_ZLIB_HEADER);
if (!status)
{
printf("tinfl_decompress_mem_to_callback() failed with status %i!\n", status);
return EXIT_FAILURE;
}
outfile_size = ftell(pOutfile);
fclose(pInfile);
if (EOF == fclose(pOutfile))
{
printf("Failed writing to output file!\n");
return EXIT_FAILURE;
}
printf("Total input bytes: %u\n", (uint)in_buf_size);
printf("Total output bytes: %u\n", outfile_size);
printf("Success.\n");
return EXIT_SUCCESS;
}

View File

@ -1,327 +0,0 @@
// example5.c - Demonstrates how to use miniz.c's low-level tdefl_compress() and tinfl_inflate() API's for simple file to file compression/decompression.
// The low-level API's are the fastest, make no use of dynamic memory allocation, and are the most flexible functions exposed by miniz.c.
// Public domain, April 11 2012, Rich Geldreich, richgel99@gmail.com. See "unlicense" statement at the end of tinfl.c.
// For simplicity, this example is limited to files smaller than 4GB, but this is not a limitation of miniz.c.
// Purposely disable a whole bunch of stuff this low-level example doesn't use.
#define MINIZ_NO_STDIO
#define MINIZ_NO_ARCHIVE_APIS
#define MINIZ_NO_TIME
#define MINIZ_NO_ZLIB_APIS
#define MINIZ_NO_MALLOC
#include "miniz.h"
// Now include stdio.h because this test uses fopen(), etc. (but we still don't want miniz.c's stdio stuff, for testing).
#include <stdio.h>
#include <limits.h>
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint;
#define my_max(a,b) (((a) > (b)) ? (a) : (b))
#define my_min(a,b) (((a) < (b)) ? (a) : (b))
// IN_BUF_SIZE is the size of the file read buffer.
// IN_BUF_SIZE must be >= 1
#define IN_BUF_SIZE (1024*512)
static uint8 s_inbuf[IN_BUF_SIZE];
// COMP_OUT_BUF_SIZE is the size of the output buffer used during compression.
// COMP_OUT_BUF_SIZE must be >= 1 and <= OUT_BUF_SIZE
#define COMP_OUT_BUF_SIZE (1024*512)
// OUT_BUF_SIZE is the size of the output buffer used during decompression.
// OUT_BUF_SIZE must be a power of 2 >= TINFL_LZ_DICT_SIZE (because the low-level decompressor not only writes, but reads from the output buffer as it decompresses)
//#define OUT_BUF_SIZE (TINFL_LZ_DICT_SIZE)
#define OUT_BUF_SIZE (1024*512)
static uint8 s_outbuf[OUT_BUF_SIZE];
// tdefl_compressor contains all the state needed by the low-level compressor so it's a pretty big struct (~300k).
// This example makes it a global vs. putting it on the stack, of course in real-world usage you'll probably malloc() or new it.
tdefl_compressor g_deflator;
int main(int argc, char *argv[])
{
const char *pMode;
FILE *pInfile, *pOutfile;
uint infile_size;
int level = 9;
int p = 1;
const char *pSrc_filename;
const char *pDst_filename;
const void *next_in = s_inbuf;
size_t avail_in = 0;
void *next_out = s_outbuf;
size_t avail_out = OUT_BUF_SIZE;
size_t total_in = 0, total_out = 0;
long file_loc;
assert(COMP_OUT_BUF_SIZE <= OUT_BUF_SIZE);
printf("miniz.c example5 (demonstrates tinfl/tdefl)\n");
if (argc < 4)
{
printf("File to file compression/decompression using the low-level tinfl/tdefl API's.\n");
printf("Usage: example5 [options] [mode:c or d] infile outfile\n");
printf("\nModes:\n");
printf("c - Compresses file infile to a zlib stream in file outfile\n");
printf("d - Decompress zlib stream in file infile to file outfile\n");
printf("\nOptions:\n");
printf("-l[0-10] - Compression level, higher values are slower, 0 is none.\n");
return EXIT_FAILURE;
}
while ((p < argc) && (argv[p][0] == '-'))
{
switch (argv[p][1])
{
case 'l':
{
level = atoi(&argv[1][2]);
if ((level < 0) || (level > 10))
{
printf("Invalid level!\n");
return EXIT_FAILURE;
}
break;
}
default:
{
printf("Invalid option: %s\n", argv[p]);
return EXIT_FAILURE;
}
}
p++;
}
if ((argc - p) < 3)
{
printf("Must specify mode, input filename, and output filename after options!\n");
return EXIT_FAILURE;
}
else if ((argc - p) > 3)
{
printf("Too many filenames!\n");
return EXIT_FAILURE;
}
pMode = argv[p++];
if (!strchr("cCdD", pMode[0]))
{
printf("Invalid mode!\n");
return EXIT_FAILURE;
}
pSrc_filename = argv[p++];
pDst_filename = argv[p++];
printf("Mode: %c, Level: %u\nInput File: \"%s\"\nOutput File: \"%s\"\n", pMode[0], level, pSrc_filename, pDst_filename);
// Open input file.
pInfile = fopen(pSrc_filename, "rb");
if (!pInfile)
{
printf("Failed opening input file!\n");
return EXIT_FAILURE;
}
// Determine input file's size.
fseek(pInfile, 0, SEEK_END);
file_loc = ftell(pInfile);
fseek(pInfile, 0, SEEK_SET);
if ((file_loc < 0) || (file_loc > INT_MAX))
{
// This is not a limitation of miniz or tinfl, but this example.
printf("File is too large to be processed by this example.\n");
return EXIT_FAILURE;
}
infile_size = (uint)file_loc;
// Open output file.
pOutfile = fopen(pDst_filename, "wb");
if (!pOutfile)
{
printf("Failed opening output file!\n");
return EXIT_FAILURE;
}
printf("Input file size: %u\n", infile_size);
if ((pMode[0] == 'c') || (pMode[0] == 'C'))
{
// The number of dictionary probes to use at each compression level (0-10). 0=implies fastest/minimal possible probing.
static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };
tdefl_status status;
uint infile_remaining = infile_size;
// create tdefl() compatible flags (we have to compose the low-level flags ourselves, or use tdefl_create_comp_flags_from_zip_params() but that means MINIZ_NO_ZLIB_APIS can't be defined).
mz_uint comp_flags = TDEFL_WRITE_ZLIB_HEADER | s_tdefl_num_probes[MZ_MIN(10, level)] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0);
if (!level)
comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS;
// Initialize the low-level compressor.
status = tdefl_init(&g_deflator, NULL, NULL, comp_flags);
if (status != TDEFL_STATUS_OKAY)
{
printf("tdefl_init() failed!\n");
return EXIT_FAILURE;
}
avail_out = COMP_OUT_BUF_SIZE;
// Compression.
for ( ; ; )
{
size_t in_bytes, out_bytes;
if (!avail_in)
{
// Input buffer is empty, so read more bytes from input file.
uint n = my_min(IN_BUF_SIZE, infile_remaining);
if (fread(s_inbuf, 1, n, pInfile) != n)
{
printf("Failed reading from input file!\n");
return EXIT_FAILURE;
}
next_in = s_inbuf;
avail_in = n;
infile_remaining -= n;
//printf("Input bytes remaining: %u\n", infile_remaining);
}
in_bytes = avail_in;
out_bytes = avail_out;
// Compress as much of the input as possible (or all of it) to the output buffer.
status = tdefl_compress(&g_deflator, next_in, &in_bytes, next_out, &out_bytes, infile_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH);
next_in = (const char *)next_in + in_bytes;
avail_in -= in_bytes;
total_in += in_bytes;
next_out = (char *)next_out + out_bytes;
avail_out -= out_bytes;
total_out += out_bytes;
if ((status != TDEFL_STATUS_OKAY) || (!avail_out))
{
// Output buffer is full, or compression is done or failed, so write buffer to output file.
uint n = COMP_OUT_BUF_SIZE - (uint)avail_out;
if (fwrite(s_outbuf, 1, n, pOutfile) != n)
{
printf("Failed writing to output file!\n");
return EXIT_FAILURE;
}
next_out = s_outbuf;
avail_out = COMP_OUT_BUF_SIZE;
}
if (status == TDEFL_STATUS_DONE)
{
// Compression completed successfully.
break;
}
else if (status != TDEFL_STATUS_OKAY)
{
// Compression somehow failed.
printf("tdefl_compress() failed with status %i!\n", status);
return EXIT_FAILURE;
}
}
}
else if ((pMode[0] == 'd') || (pMode[0] == 'D'))
{
// Decompression.
uint infile_remaining = infile_size;
tinfl_decompressor inflator;
tinfl_init(&inflator);
for ( ; ; )
{
size_t in_bytes, out_bytes;
tinfl_status status;
if (!avail_in)
{
// Input buffer is empty, so read more bytes from input file.
uint n = my_min(IN_BUF_SIZE, infile_remaining);
if (fread(s_inbuf, 1, n, pInfile) != n)
{
printf("Failed reading from input file!\n");
return EXIT_FAILURE;
}
next_in = s_inbuf;
avail_in = n;
infile_remaining -= n;
}
in_bytes = avail_in;
out_bytes = avail_out;
status = tinfl_decompress(&inflator, (const mz_uint8 *)next_in, &in_bytes, s_outbuf, (mz_uint8 *)next_out, &out_bytes, (infile_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0) | TINFL_FLAG_PARSE_ZLIB_HEADER);
avail_in -= in_bytes;
next_in = (const mz_uint8 *)next_in + in_bytes;
total_in += in_bytes;
avail_out -= out_bytes;
next_out = (mz_uint8 *)next_out + out_bytes;
total_out += out_bytes;
if ((status <= TINFL_STATUS_DONE) || (!avail_out))
{
// Output buffer is full, or decompression is done, so write buffer to output file.
uint n = OUT_BUF_SIZE - (uint)avail_out;
if (fwrite(s_outbuf, 1, n, pOutfile) != n)
{
printf("Failed writing to output file!\n");
return EXIT_FAILURE;
}
next_out = s_outbuf;
avail_out = OUT_BUF_SIZE;
}
// If status is <= TINFL_STATUS_DONE then either decompression is done or something went wrong.
if (status <= TINFL_STATUS_DONE)
{
if (status == TINFL_STATUS_DONE)
{
// Decompression completed successfully.
break;
}
else
{
// Decompression failed.
printf("tinfl_decompress() failed with status %i!\n", status);
return EXIT_FAILURE;
}
}
}
}
else
{
printf("Invalid mode!\n");
return EXIT_FAILURE;
}
fclose(pInfile);
if (EOF == fclose(pOutfile))
{
printf("Failed writing to output file!\n");
return EXIT_FAILURE;
}
printf("Total input bytes: %u\n", (mz_uint32)total_in);
printf("Total output bytes: %u\n", (mz_uint32)total_out);
printf("Success.\n");
return EXIT_SUCCESS;
}

View File

@ -1,162 +0,0 @@
// example6.c - Demonstrates how to miniz's PNG writer func
// Public domain, April 11 2012, Rich Geldreich, richgel99@gmail.com. See "unlicense" statement at the end of tinfl.c.
// Mandlebrot set code from http://rosettacode.org/wiki/Mandelbrot_set#C
// Must link this example against libm on Linux.
// Purposely disable a whole bunch of stuff this low-level example doesn't use.
#define MINIZ_NO_STDIO
#define MINIZ_NO_TIME
#define MINIZ_NO_ZLIB_APIS
#include "miniz.h"
// Now include stdio.h because this test uses fopen(), etc. (but we still don't want miniz.c's stdio stuff, for testing).
#include <stdio.h>
#include <limits.h>
#include <math.h>
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint;
typedef struct
{
uint8 r, g, b;
} rgb_t;
static void hsv_to_rgb(int hue, int min, int max, rgb_t *p)
{
const int invert = 0;
const int saturation = 1;
const int color_rotate = 0;
if (min == max) max = min + 1;
if (invert) hue = max - (hue - min);
if (!saturation) {
p->r = p->g = p->b = 255 * (max - hue) / (max - min);
return;
}
double h = fmod(color_rotate + 1e-4 + 4.0 * (hue - min) / (max - min), 6);
double c = 255.0f * saturation;
double X = c * (1 - fabs(fmod(h, 2) - 1));
p->r = p->g = p->b = 0;
switch((int)h) {
case 0: p->r = c; p->g = X; return;
case 1: p->r = X; p->g = c; return;
case 2: p->g = c; p->b = X; return;
case 3: p->g = X; p->b = c; return;
case 4: p->r = X; p->b = c; return;
default:p->r = c; p->b = X;
}
}
int main(int argc, char *argv[])
{
(void)argc, (void)argv;
// Image resolution
const int iXmax = 4096;
const int iYmax = 4096;
// Output filename
static const char *pFilename = "mandelbrot.png";
int iX, iY;
const double CxMin = -2.5;
const double CxMax = 1.5;
const double CyMin = -2.0;
const double CyMax = 2.0;
double PixelWidth = (CxMax - CxMin) / iXmax;
double PixelHeight = (CyMax - CyMin) / iYmax;
// Z=Zx+Zy*i ; Z0 = 0
double Zx, Zy;
double Zx2, Zy2; // Zx2=Zx*Zx; Zy2=Zy*Zy
int Iteration;
const int IterationMax = 200;
// bail-out value , radius of circle
const double EscapeRadius = 2;
double ER2=EscapeRadius * EscapeRadius;
uint8 *pImage = (uint8 *)malloc(iXmax * 3 * iYmax);
// world ( double) coordinate = parameter plane
double Cx,Cy;
int MinIter = 9999, MaxIter = 0;
for(iY = 0; iY < iYmax; iY++)
{
Cy = CyMin + iY * PixelHeight;
if (fabs(Cy) < PixelHeight/2)
Cy = 0.0; // Main antenna
for(iX = 0; iX < iXmax; iX++)
{
uint8 *color = pImage + (iX * 3) + (iY * iXmax * 3);
Cx = CxMin + iX * PixelWidth;
// initial value of orbit = critical point Z= 0
Zx = 0.0;
Zy = 0.0;
Zx2 = Zx * Zx;
Zy2 = Zy * Zy;
for (Iteration=0;Iteration<IterationMax && ((Zx2+Zy2)<ER2);Iteration++)
{
Zy = 2 * Zx * Zy + Cy;
Zx =Zx2 - Zy2 + Cx;
Zx2 = Zx * Zx;
Zy2 = Zy * Zy;
};
color[0] = (uint8)Iteration;
color[1] = (uint8)Iteration >> 8;
color[2] = 0;
if (Iteration < MinIter)
MinIter = Iteration;
if (Iteration > MaxIter)
MaxIter = Iteration;
}
}
for(iY = 0; iY < iYmax; iY++)
{
for(iX = 0; iX < iXmax; iX++)
{
uint8 *color = (uint8 *)(pImage + (iX * 3) + (iY * iXmax * 3));
uint Iterations = color[0] | (color[1] << 8U);
hsv_to_rgb(Iterations, MinIter, MaxIter, (rgb_t *)color);
}
}
// Now write the PNG image.
{
size_t png_data_size = 0;
void *pPNG_data = tdefl_write_image_to_png_file_in_memory_ex(pImage, iXmax, iYmax, 3, &png_data_size, 6, MZ_FALSE);
if (!pPNG_data)
fprintf(stderr, "tdefl_write_image_to_png_file_in_memory_ex() failed!\n");
else
{
FILE *pFile = fopen(pFilename, "wb");
fwrite(pPNG_data, 1, png_data_size, pFile);
fclose(pFile);
printf("Wrote %s\n", pFilename);
}
// mz_free() is by default just an alias to free() internally, but if you've overridden miniz's allocation funcs you'll probably need to call mz_free().
mz_free(pPNG_data);
}
free(pImage);
return EXIT_SUCCESS;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,37 +0,0 @@
## Miniz
Miniz is a lossless, high performance data compression library in a single source file that implements the zlib (RFC 1950) and Deflate (RFC 1951) compressed data format specification standards. It supports the most commonly used functions exported by the zlib library, but is a completely independent implementation so zlib's licensing requirements do not apply. Miniz also contains simple to use functions for writing .PNG format image files and reading/writing/appending .ZIP format archives. Miniz's compression speed has been tuned to be comparable to zlib's, and it also has a specialized real-time compressor function designed to compare well against fastlz/minilzo.
## Usage
Please use the files from the [releases page](https://github.com/richgel999/miniz/releases) in your projects. Do not use the git checkout directly! The different source and header files are [amalgamated](https://www.sqlite.org/amalgamation.html) into one `miniz.c`/`miniz.h` pair in a build step (`amalgamate.sh`). Include `miniz.c` and `miniz.h` in your project to use Miniz.
## Features
* MIT licensed
* A portable, single source and header file library written in plain C. Tested with GCC, clang and Visual Studio.
* Easily tuned and trimmed down by defines
* A drop-in replacement for zlib's most used API's (tested in several open source projects that use zlib, such as libpng and libzip).
* Fills a single threaded performance vs. compression ratio gap between several popular real-time compressors and zlib. For example, at level 1, miniz.c compresses around 5-9% better than minilzo, but is approx. 35% slower. At levels 2-9, miniz.c is designed to compare favorably against zlib's ratio and speed. See the miniz performance comparison page for example timings.
* Not a block based compressor: miniz.c fully supports stream based processing using a coroutine-style implementation. The zlib-style API functions can be called a single byte at a time if that's all you've got.
* Easy to use. The low-level compressor (tdefl) and decompressor (tinfl) have simple state structs which can be saved/restored as needed with simple memcpy's. The low-level codec API's don't use the heap in any way.
* Entire inflater (including optional zlib header parsing and Adler-32 checking) is implemented in a single function as a coroutine, which is separately available in a small (~550 line) source file: miniz_tinfl.c
* A fairly complete (but totally optional) set of .ZIP archive manipulation and extraction API's. The archive functionality is intended to solve common problems encountered in embedded, mobile, or game development situations. (The archive API's are purposely just powerful enough to write an entire archiver given a bit of additional higher-level logic.)
## Known Problems
* No support for encrypted archives. Not sure how useful this stuff is in practice.
* Minimal documentation. The assumption is that the user is already familiar with the basic zlib API. I need to write an API wiki - for now I've tried to place key comments before each enum/API, and I've included 6 examples that demonstrate how to use the module's major features.
## Special Thanks
Thanks to Alex Evans for the PNG writer function. Also, thanks to Paul Holden and Thorsten Scheuermann for feedback and testing, Matt Pritchard for all his encouragement, and Sean Barrett's various public domain libraries for inspiration (and encouraging me to write miniz.c in C, which was much more enjoyable and less painful than I thought it would be considering I've been programming in C++ for so long).
Thanks to Bruce Dawson for reporting a problem with the level_and_flags archive API parameter (which is fixed in v1.12) and general feedback, and Janez Zemva for indirectly encouraging me into writing more examples.
## Patents
I was recently asked if miniz avoids patent issues. miniz purposely uses the same core algorithms as the ones used by zlib. The compressor uses vanilla hash chaining as described [here](http://www.gzip.org/zlib/rfc-deflate.html#algorithm). Also see the [gzip FAQ](http://www.gzip.org/#faq11). In my opinion, if miniz falls prey to a patent attack then zlib/gzip are likely to be at serious risk too.
[![Build Status](https://travis-ci.org/uroni/miniz.svg?branch=master)](https://travis-ci.org/uroni/miniz)

View File

@ -1,24 +0,0 @@
DEPENDENCIES := libpng pixman-1 libuv
include ../common.mk
ifdef LIB_PATH
LDFLAGS += -L$(LIB_PATH) -Wl,-rpath=$(shell pwd)/$(LIB_PATH)
endif
LDFLAGS += -lneatvnc
all: \
$(BUILD_DIR)/png-server \
$(BUILD_DIR)/draw \
$(BUILD_DIR)/png-server: $(BUILD_DIR)/png-server.o $(BUILD_DIR)/pngfb.o
$(LINK_EXE)
$(BUILD_DIR)/draw: $(BUILD_DIR)/draw.o ; $(LINK_EXE)
$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR) ; $(CC_OBJ) -I../include
$(BUILD_DIR)/pngfb.o: ../src/pngfb.c | $(BUILD_DIR)
$(CC_OBJ) -I../include

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Andri Yngvason
* Copyright (c) 2019 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -18,62 +18,332 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <uv.h>
#include <tgmath.h>
#include <aml.h>
#include <signal.h>
#include <assert.h>
#include <pixman.h>
#include <sys/param.h>
#include <libdrm/drm_fourcc.h>
struct draw {
struct nvnc_fb* fb;
#include "sys/queue.h"
// TODO: Align pixel formats
struct coord {
int x, y;
};
void on_pointer_event(struct nvnc_client* client, uint16_t x, uint16_t y,
struct fb_side_data {
struct pixman_region16 damage;
LIST_ENTRY(fb_side_data) link;
};
LIST_HEAD(fb_side_data_list, fb_side_data);
struct draw {
int width;
int height;
uint32_t format;
pixman_image_t* whiteboard;
uint32_t* whiteboard_buffer;
struct nvnc_display* display;
struct nvnc_fb_pool* fb_pool;
struct fb_side_data_list fb_side_data_list;
};
static struct nvnc_fb* create_cursor()
{
static char ascii_art[] =
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXXXXXXXXXXXXXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXXX "
"XXXXXXX "
"XXXXXX "
"XXXXX "
"XXXX "
"XXX "
"XX "
"X ";
struct nvnc_fb* fb = nvnc_fb_new(32, 32, DRM_FORMAT_RGBA8888, 32);
assert(fb);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
uint32_t colour = 0x00ff00ffULL;
#else
uint32_t colour = 0xff00ff00ULL;
#endif
uint32_t* pixels = nvnc_fb_get_addr(fb);
for (int i = 0; i < 32 * 32; ++i) {
pixels[i] = ascii_art[i] != ' ' ? colour : 0;
}
return fb;
}
static void fb_side_data_destroy(void* userdata)
{
struct fb_side_data* fb_side_data = userdata;
LIST_REMOVE(fb_side_data, link);
pixman_region_fini(&fb_side_data->damage);
free(fb_side_data);
}
static int coord_distance_between(struct coord a, struct coord b)
{
float x = abs(a.x - b.x);
float y = abs(a.y - b.y);
return round(sqrt(x * x + y * y));
}
static void damage_all_buffers(struct draw* draw,
struct pixman_region16* region)
{
struct fb_side_data *item;
LIST_FOREACH(item, &draw->fb_side_data_list, link)
pixman_region_union(&item->damage, &item->damage, region);
}
static void update_vnc_buffer(struct draw* draw,
struct pixman_region16* frame_damage)
{
struct nvnc_fb *fb = nvnc_fb_pool_acquire(draw->fb_pool);
assert(fb);
struct fb_side_data* fb_side_data = nvnc_get_userdata(fb);
if (!fb_side_data) {
fb_side_data = calloc(1, sizeof(*fb_side_data));
assert(fb_side_data);
/* This is a new buffer, so the whole surface is damaged. */
pixman_region_init_rect(&fb_side_data->damage, 0, 0,
draw->width, draw->height);
nvnc_set_userdata(fb, fb_side_data, fb_side_data_destroy);
LIST_INSERT_HEAD(&draw->fb_side_data_list, fb_side_data, link);
}
pixman_image_t* dstimg = pixman_image_create_bits_no_clear(
PIXMAN_r8g8b8x8, draw->width, draw->height,
nvnc_fb_get_addr(fb), 4 * draw->width);
/* Clip region is set to limit copying to only the damaged region. */
pixman_image_set_clip_region(dstimg, &fb_side_data->damage);
pixman_image_composite(PIXMAN_OP_OVER, draw->whiteboard, NULL, dstimg,
0, 0,
0, 0,
0, 0,
draw->width, draw->height);
pixman_image_unref(dstimg);
/* The buffer is now up to date, so the damage region can be cleared. */
pixman_region_clear(&fb_side_data->damage);
nvnc_display_feed_buffer(draw->display, fb, frame_damage);
nvnc_fb_unref(fb);
}
static void composite_dot(struct draw *draw, uint32_t* image,
struct coord coord, int radius, uint32_t colour,
struct pixman_region16* damage)
{
int width = draw->width;
int height = draw->height;
struct coord start, stop;
start.x = MAX(0, coord.x - radius);
start.y = MAX(0, coord.y - radius);
stop.x = MIN(width, coord.x + radius);
stop.y = MIN(height, coord.y + radius);
/* The brute force method. ;) */
for (int y = start.y; y < stop.y; ++y)
for (int x = start.x; x < stop.x; ++x) {
struct coord point = { .x = x, .y = y };
if (coord_distance_between(point, coord) <= radius)
image[x + y * width] = colour;
}
pixman_region_init_rect(damage, start.x, start.y,
stop.x - start.x, stop.y - start.y);
}
static void draw_dot(struct draw *draw, struct coord coord, int radius,
uint32_t colour)
{
struct pixman_region16 region;
composite_dot(draw, draw->whiteboard_buffer, coord, radius, colour,
&region);
/* All the buffers that are currently in the pool will need to be
* upgraded in this region before being sent to nvnc.
*/
damage_all_buffers(draw, &region);
update_vnc_buffer(draw, &region);
pixman_region_fini(&region);
}
static void on_pointer_event(struct nvnc_client* client, uint16_t x, uint16_t y,
enum nvnc_button_mask buttons)
{
if (!(buttons & NVNC_BUTTON_LEFT))
return;
struct nvnc* server = nvnc_get_server(client);
struct nvnc* server = nvnc_client_get_server(client);
assert(server);
struct draw* draw = nvnc_get_userdata(server);
assert(draw);
uint32_t* image = nvnc_fb_get_addr(draw->fb);
int width = nvnc_fb_get_width(draw->fb);
int height = nvnc_fb_get_height(draw->fb);
struct coord coord = { x, y };
draw_dot(draw, coord, 16, 0);
}
image[x + y * width] = 0;
static bool on_desktop_layout_event(struct nvnc_client* client,
const struct nvnc_desktop_layout* layout)
{
uint16_t width = nvnc_desktop_layout_get_width(layout);
uint16_t height = nvnc_desktop_layout_get_height(layout);
struct nvnc* server = nvnc_client_get_server(client);
assert(server);
struct pixman_region16 region;
pixman_region_init_rect(&region, 0, 0, width, height);
pixman_region_intersect_rect(&region, &region, x, y, 1, 1);
nvnc_feed_frame(server, draw->fb, &region);
pixman_region_fini(&region);
struct draw* draw = nvnc_get_userdata(server);
assert(draw);
nvnc_fb_pool_resize(draw->fb_pool, width, height, draw->format, width);
uint32_t* buffer = malloc(width * height * 4);
assert(buffer);
memset(buffer, 0xff, width * height * 4);
pixman_image_t* image = pixman_image_create_bits_no_clear(
PIXMAN_r8g8b8x8, width, height, buffer, width * 4);
assert(image);
pixman_image_composite(PIXMAN_OP_OVER, draw->whiteboard, NULL, image, 0,
0, 0, 0,
width > draw->width ? (width - draw->width) / 2 : 0,
height > draw->height ? (height - draw->height) / 2 : 0,
draw->width, draw->height);
pixman_image_unref(draw->whiteboard);
free(draw->whiteboard_buffer);
draw->whiteboard_buffer = buffer;
draw->whiteboard = image;
draw->width = width;
draw->height = height;
struct pixman_region16 damage;
pixman_region_init_rect(&damage, 0, 0, width, height);
update_vnc_buffer(draw, &damage);
pixman_region_fini(&damage);
return true;
}
static void on_sigint()
{
aml_exit(aml_get_default());
}
int main(int argc, char* argv[])
{
struct draw draw;
struct draw draw = { 0 };
int width = 500, height = 500;
uint32_t format = DRM_FORMAT_RGBX8888;
draw.fb = nvnc_fb_new(width, height, format);
assert(draw.fb);
LIST_INIT(&draw.fb_side_data_list);
void* addr = nvnc_fb_get_addr(draw.fb);
struct aml* aml = aml_new();
aml_set_default(aml);
memset(addr, 0xff, width * height * 4);
draw.width = 500;
draw.height = 500;
draw.format = DRM_FORMAT_RGBX8888;
draw.whiteboard_buffer = malloc(draw.width * draw.height * 4);
assert(draw.whiteboard_buffer);
memset(draw.whiteboard_buffer, 0xff, draw.width * draw.height * 4);
draw.whiteboard = pixman_image_create_bits_no_clear(PIXMAN_r8g8b8x8,
draw.width, draw.height, draw.whiteboard_buffer,
draw.width * 4);
assert(draw.whiteboard);
draw.fb_pool = nvnc_fb_pool_new(draw.width, draw.height, draw.format,
draw.width);
assert(draw.fb_pool);
struct nvnc* server = nvnc_open("127.0.0.1", 5900);
assert(server);
draw.display = nvnc_display_new(0, 0);
assert(draw.display);
nvnc_add_display(server, draw.display);
nvnc_set_dimensions(server, width, height, format);
nvnc_set_name(server, "Draw");
nvnc_set_pointer_fn(server, on_pointer_event);
nvnc_set_userdata(server, &draw);
nvnc_set_desktop_layout_fn(server, on_desktop_layout_event);
nvnc_set_userdata(server, &draw, NULL);
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
struct nvnc_fb* cursor = create_cursor();
assert(cursor);
nvnc_set_cursor(server, cursor, 32, 32, 0, 0, true);
nvnc_fb_unref(cursor);
struct aml_signal* sig = aml_signal_new(SIGINT, on_sigint, NULL, NULL);
aml_start(aml_get_default(), sig);
aml_unref(sig);
struct pixman_region16 damage;
pixman_region_init_rect(&damage, 0, 0, draw.width, draw.height);
update_vnc_buffer(&draw, &damage);
pixman_region_fini(&damage);
aml_run(aml);
nvnc_close(server);
nvnc_display_unref(draw.display);
nvnc_fb_pool_unref(draw.fb_pool);
pixman_image_unref(draw.whiteboard);
free(draw.whiteboard_buffer);
aml_unref(aml);
}

View File

@ -0,0 +1,30 @@
libpng = dependency('libpng', required: false)
executable(
'draw',
[
'draw.c',
],
dependencies: [
neatvnc_dep,
pixman,
aml,
libm,
]
)
if libpng.found()
executable(
'png-server',
[
'png-server.c',
'../src/pngfb.c',
],
dependencies: [
neatvnc_dep,
pixman,
aml,
libpng,
]
)
endif

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Andri Yngvason
* Copyright (c) 2019 - 2021 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -14,15 +14,21 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "neatvnc.h"
#include <neatvnc.h>
#include <stdio.h>
#include <uv.h>
#include <aml.h>
#include <signal.h>
#include <assert.h>
#include <pixman.h>
struct nvnc_fb* read_png_file(const char* filename);
static void on_sigint()
{
aml_exit(aml_get_default());
}
int main(int argc, char* argv[])
{
const char* file = argv[1];
@ -38,23 +44,32 @@ int main(int argc, char* argv[])
return 1;
}
struct aml* aml = aml_new();
aml_set_default(aml);
struct nvnc* server = nvnc_open("127.0.0.1", 5900);
assert(server);
int width = nvnc_fb_get_width(fb);
int height = nvnc_fb_get_height(fb);
uint32_t fourcc_format = nvnc_fb_get_fourcc_format(fb);
struct nvnc_display* display = nvnc_display_new(0, 0);
assert(display);
nvnc_set_dimensions(server, width, height, fourcc_format);
nvnc_add_display(server, display);
nvnc_set_name(server, file);
struct pixman_region16 region;
pixman_region_init_rect(&region, 0, 0, nvnc_fb_get_width(fb),
struct pixman_region16 damage;
pixman_region_init_rect(&damage, 0, 0, nvnc_fb_get_width(fb),
nvnc_fb_get_height(fb));
nvnc_feed_frame(server, fb, &region);
pixman_region_fini(&region);
nvnc_display_feed_buffer(display, fb, &damage);
pixman_region_fini(&damage);
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
struct aml_signal* sig = aml_signal_new(SIGINT, on_sigint, NULL, NULL);
aml_start(aml_get_default(), sig);
aml_unref(sig);
aml_run(aml);
nvnc_close(server);
nvnc_display_unref(display);
nvnc_fb_unref(fb);
aml_unref(aml);
}

View File

@ -1,13 +0,0 @@
#!/usr/bin/bash
cat <<EOF
prefix=$PREFIX
libdir=\${prefix}/lib
includedir=\${prefix}/include
Name: neatvnc
Description: A neat VNC server library
Version: $VERSION
Libs: -L\${libdir} -lneatvnc
Cflags: -I\${includedir}
EOF

25
include/base64.h 100644
View File

@ -0,0 +1,25 @@
/* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdint.h>
#include <unistd.h>
#define BASE64_ENCODED_SIZE(x) ((((x) + 2) / 3) * 4 + 1)
#define BASE64_DECODED_MAX_SIZE(x) ((((x) + 3) / 4) * 3)
void base64_encode(char* dst, const uint8_t* src, size_t src_len);
ssize_t base64_decode(uint8_t* dst, const char* src);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 - 2020 Andri Yngvason
* Copyright (c) 2019 - 2024 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -16,17 +16,20 @@
#pragma once
#include <uv.h>
#include <stdbool.h>
#include <pixman.h>
#include <zlib.h>
#include "rfb-proto.h"
#include "sys/queue.h"
#include "neatvnc.h"
#include "miniz.h"
#include "config.h"
#ifdef HAVE_CRYPTO
#include "crypto.h"
#endif
#ifdef ENABLE_TLS
#include <gnutls/gnutls.h>
#endif
@ -34,6 +37,7 @@
#define MAX_ENCODINGS 32
#define MAX_OUTGOING_FRAMES 4
#define MSG_BUFFER_SIZE 4096
#define MAX_CUT_TEXT_SIZE 10000000
enum nvnc_client_state {
VNC_CLIENT_STATE_ERROR = -1,
@ -43,6 +47,13 @@ enum nvnc_client_state {
VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_VERSION,
VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_SUBTYPE,
VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_PLAIN_AUTH,
#endif
#ifdef HAVE_CRYPTO
VNC_CLIENT_STATE_WAITING_FOR_APPLE_DH_RESPONSE,
VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_PUBLIC_KEY,
VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CHALLENGE,
VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CLIENT_HASH,
VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CREDENTIALS,
#endif
VNC_CLIENT_STATE_WAITING_FOR_INIT,
VNC_CLIENT_STATE_READY,
@ -50,15 +61,29 @@ enum nvnc_client_state {
struct nvnc;
struct stream;
struct aml_handler;
struct aml_idle;
struct nvnc_display;
struct crypto_key;
struct crypto_rsa_pub_key;
struct crypto_rsa_priv_key;
struct nvnc_common {
void* userdata;
nvnc_cleanup_fn cleanup_fn;
};
struct cut_text {
char* buffer;
size_t length;
size_t index;
};
struct nvnc_client {
struct nvnc_common common;
int ref;
struct stream* net_stream;
char username[256];
struct nvnc* server;
enum nvnc_client_state state;
bool has_pixfmt;
@ -69,38 +94,84 @@ struct nvnc_client {
struct pixman_region16 damage;
int n_pending_requests;
bool is_updating;
struct nvnc_fb* current_fb;
nvnc_client_fn cleanup_fn;
z_stream z_stream;
size_t buffer_index;
size_t buffer_len;
uint8_t msg_buffer[MSG_BUFFER_SIZE];
uint32_t known_width;
uint32_t known_height;
struct cut_text cut_text;
bool is_ext_notified;
struct encoder* encoder;
struct encoder* zrle_encoder;
struct encoder* tight_encoder;
uint32_t cursor_seq;
int quality;
bool formats_changed;
enum nvnc_keyboard_led_state led_state;
enum nvnc_keyboard_led_state pending_led_state;
#ifdef HAVE_CRYPTO
struct crypto_key* apple_dh_secret;
struct {
enum crypto_hash_type hash_type;
enum crypto_cipher_type cipher_type;
size_t challenge_len;
uint8_t challenge[32];
struct crypto_rsa_pub_key* pub;
} rsa;
#endif
};
LIST_HEAD(nvnc_client_list, nvnc_client);
struct vnc_display {
uint16_t width;
uint16_t height;
uint32_t pixfmt; /* fourcc pixel format */
char name[256];
enum nvnc__socket_type {
NVNC__SOCKET_TCP,
NVNC__SOCKET_UNIX,
NVNC__SOCKET_WEBSOCKET,
NVNC__SOCKET_FROM_FD,
};
struct nvnc {
struct nvnc_common common;
int fd;
uv_poll_t poll_handle;
enum nvnc__socket_type socket_type;
struct aml_handler* poll_handle;
struct nvnc_client_list clients;
struct vnc_display display;
char name[256];
void* userdata;
nvnc_key_fn key_fn;
nvnc_key_fn key_code_fn;
nvnc_pointer_fn pointer_fn;
nvnc_fb_req_fn fb_req_fn;
nvnc_client_fn new_client_fn;
struct nvnc_fb* frame;
nvnc_cut_text_fn cut_text_fn;
nvnc_desktop_layout_fn desktop_layout_fn;
struct nvnc_display* display;
struct {
struct nvnc_fb* buffer;
uint32_t width, height;
uint32_t hotspot_x, hotspot_y;
} cursor;
uint32_t cursor_seq;
enum nvnc_auth_flags auth_flags;
nvnc_auth_fn auth_fn;
void* auth_ud;
#ifdef ENABLE_TLS
gnutls_certificate_credentials_t tls_creds;
nvnc_auth_fn auth_fn;
void* auth_ud;
#endif
#ifdef HAVE_CRYPTO
struct crypto_rsa_pub_key* rsa_pub;
struct crypto_rsa_priv_key* rsa_priv;
#endif
uint32_t n_damage_clients;
};
void nvnc__damage_region(struct nvnc* self,
const struct pixman_region16* damage);

113
include/crypto.h 100644
View File

@ -0,0 +1,113 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
struct crypto_key;
struct crypto_cipher;
struct crypto_hash;
struct crypto_rsa_pub_key;
struct crypto_rsa_priv_key;
struct vec;
enum crypto_cipher_type {
CRYPTO_CIPHER_INVALID = 0,
CRYPTO_CIPHER_AES128_ECB,
CRYPTO_CIPHER_AES_EAX,
CRYPTO_CIPHER_AES256_EAX,
};
enum crypto_hash_type {
CRYPTO_HASH_INVALID = 0,
CRYPTO_HASH_MD5,
CRYPTO_HASH_SHA1,
CRYPTO_HASH_SHA256,
};
struct crypto_data_entry {
uint8_t* data;
size_t len;
};
void crypto_dump_base16(const char* msg, const uint8_t* bytes, size_t len);
void crypto_dump_base64(const char* msg, const uint8_t* bytes, size_t len);
void crypto_random(uint8_t* dst, size_t len);
// Key generation
struct crypto_key* crypto_key_new(int g, const uint8_t *p, uint32_t p_len,
const uint8_t* q, uint32_t q_len);
void crypto_key_del(struct crypto_key* key);
int crypto_key_g(const struct crypto_key* key);
uint32_t crypto_key_p(const struct crypto_key* key, uint8_t* dst,
uint32_t dst_size);
uint32_t crypto_key_q(const struct crypto_key* key, uint8_t* dst,
uint32_t dst_size);
struct crypto_key* crypto_keygen(void);
// Diffie-Hellman
struct crypto_key* crypto_derive_public_key(const struct crypto_key* priv);
struct crypto_key* crypto_derive_shared_secret(
const struct crypto_key* own_secret,
const struct crypto_key* remote_public_key);
// Ciphers
struct crypto_cipher* crypto_cipher_new(const uint8_t* enc_key,
const uint8_t* dec_key, enum crypto_cipher_type type);
void crypto_cipher_del(struct crypto_cipher* self);
bool crypto_cipher_encrypt(struct crypto_cipher* self, struct vec* dst,
uint8_t* mac, const uint8_t* src, size_t len,
const uint8_t* ad, size_t ad_len);
ssize_t crypto_cipher_decrypt(struct crypto_cipher* self, uint8_t* dst,
uint8_t* mac, const uint8_t* src, size_t len,
const uint8_t* ad, size_t ad_len);
// Hashing
struct crypto_hash* crypto_hash_new(enum crypto_hash_type type);
void crypto_hash_del(struct crypto_hash* self);
void crypto_hash_append(struct crypto_hash* self, const uint8_t* src,
size_t len);
void crypto_hash_digest(struct crypto_hash* self, uint8_t* dst,
size_t len);
void crypto_hash_one(uint8_t* dst, size_t dst_len, enum crypto_hash_type type,
const uint8_t* src, size_t src_len);
void crypto_hash_many(uint8_t* dst, size_t dst_len, enum crypto_hash_type type,
const struct crypto_data_entry *src);
// RSA
struct crypto_rsa_pub_key* crypto_rsa_pub_key_new(void);
void crypto_rsa_pub_key_del(struct crypto_rsa_pub_key*);
// Returns length in bytes
size_t crypto_rsa_pub_key_length(const struct crypto_rsa_pub_key* key);
struct crypto_rsa_pub_key* crypto_rsa_pub_key_import(const uint8_t* modulus,
const uint8_t* exponent, size_t size);
void crypto_rsa_pub_key_modulus(const struct crypto_rsa_pub_key* key,
uint8_t* dst, size_t dst_size);
void crypto_rsa_pub_key_exponent(const struct crypto_rsa_pub_key* key,
uint8_t* dst, size_t dst_size);
bool crypto_rsa_priv_key_import_pkcs1_der(struct crypto_rsa_priv_key* priv,
struct crypto_rsa_pub_key* pub, const uint8_t* key,
size_t size);
bool crypto_rsa_priv_key_load(struct crypto_rsa_priv_key* priv,
struct crypto_rsa_pub_key* pub, const char* path);
struct crypto_rsa_priv_key *crypto_rsa_priv_key_new(void);
void crypto_rsa_priv_key_del(struct crypto_rsa_priv_key*);
bool crypto_rsa_keygen(struct crypto_rsa_pub_key*, struct crypto_rsa_priv_key*);
ssize_t crypto_rsa_encrypt(struct crypto_rsa_pub_key* pub, uint8_t* dst,
size_t dst_size, const uint8_t* src, size_t src_size);
ssize_t crypto_rsa_decrypt(struct crypto_rsa_priv_key* priv, uint8_t* dst,
size_t dst_size, const uint8_t* src, size_t src_size);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Andri Yngvason
* Copyright (c) 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -17,17 +17,11 @@
#pragma once
#include <stdint.h>
#include <unistd.h>
#include "miniz.h"
struct vec;
struct nvnc_fb;
struct rfb_pixel_format;
struct pixman_region16;
struct vec;
int zrle_encode_frame(z_stream* zs, struct vec* dst,
const struct rfb_pixel_format* dst_fmt,
const struct nvnc_fb* src,
const struct rfb_pixel_format* src_fmt,
struct pixman_region16* region);
int cursor_encode(struct vec* dst, struct rfb_pixel_format* pixfmt,
struct nvnc_fb* image, uint32_t width, uint32_t height,
uint32_t x_hotspot, uint32_t y_hotspot);

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020 - 2021 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdint.h>
struct pixman_region16;
struct nvnc_fb;
struct XXH3_state_s;
struct damage_refinery {
struct XXH3_state_s* state;
uint32_t* hashes;
uint32_t width;
uint32_t height;
};
int damage_refinery_init(struct damage_refinery* self, uint32_t width,
uint32_t height);
int damage_refinery_resize(struct damage_refinery* self, uint32_t width,
uint32_t height);
void damage_refinery_destroy(struct damage_refinery* self);
void damage_refine(struct damage_refinery* self,
struct pixman_region16* refined,
struct pixman_region16* hint,
struct nvnc_fb* buffer);

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2023 Philipp Zabel
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdint.h>
struct nvnc_display;
struct rfb_screen;
struct nvnc_display_layout {
struct nvnc_display* display;
uint32_t id;
uint16_t x_pos, y_pos;
uint16_t width, height;
};
struct nvnc_desktop_layout {
uint16_t width, height;
uint8_t n_display_layouts;
struct nvnc_display_layout display_layouts[0];
};
void nvnc_display_layout_init(
struct nvnc_display_layout* display, struct rfb_screen* screen);

36
include/display.h 100644
View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "neatvnc.h"
#include "damage-refinery.h"
#include <stdint.h>
#include <pixels.h>
struct nvnc;
struct nvnc_fb;
struct resampler;
struct nvnc_display {
int ref;
struct nvnc* server;
uint16_t x_pos, y_pos;
struct nvnc_fb* buffer;
struct resampler* resampler;
struct damage_refinery damage_refinery;
};

29
include/enc-util.h 100644
View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2019 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "rfb-proto.h"
#include <stdint.h>
struct vec;
struct pixman_region16;
int encode_rect_head(struct vec* dst, enum rfb_encodings encoding,
uint32_t x, uint32_t y, uint32_t width, uint32_t height);
uint32_t calc_bytes_per_cpixel(const struct rfb_pixel_format* fmt);
uint32_t calculate_region_area(struct pixman_region16* region);

86
include/encoder.h 100644
View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2021 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "rfb-proto.h"
#include <stdint.h>
#include <unistd.h>
struct encoder;
struct nvnc_fb;
struct pixman_region16;
struct rcbuf;
enum encoder_impl_flags {
ENCODER_IMPL_FLAG_NONE = 0,
ENCODER_IMPL_FLAG_IGNORES_DAMAGE = 1 << 0,
};
struct encoder_impl {
enum encoder_impl_flags flags;
void (*destroy)(struct encoder*);
void (*set_output_format)(struct encoder*,
const struct rfb_pixel_format*);
void (*set_quality)(struct encoder*, int quality);
int (*resize)(struct encoder*, uint16_t width, uint16_t height);
int (*encode)(struct encoder*, struct nvnc_fb* fb,
struct pixman_region16* damage);
void (*request_key_frame)(struct encoder*);
};
struct encoder {
struct encoder_impl* impl;
int ref;
uint16_t x_pos;
uint16_t y_pos;
int n_rects;
void (*on_done)(struct encoder*, struct rcbuf* result, uint64_t pts);
void* userdata;
};
struct encoder* encoder_new(enum rfb_encodings type, uint16_t width,
uint16_t height);
void encoder_ref(struct encoder* self);
void encoder_unref(struct encoder* self);
void encoder_init(struct encoder* self, struct encoder_impl*);
enum rfb_encodings encoder_get_type(const struct encoder* self);
enum encoder_kind encoder_get_kind(const struct encoder* self);
void encoder_set_output_format(struct encoder* self,
const struct rfb_pixel_format*);
void encoder_set_quality(struct encoder* self, int value);
int encoder_resize(struct encoder* self, uint16_t width, uint16_t height);
int encoder_encode(struct encoder* self, struct nvnc_fb* fb,
struct pixman_region16* damage);
void encoder_request_key_frame(struct encoder* self);
void encoder_finish_frame(struct encoder* self, struct rcbuf* result,
uint64_t pts);

View File

@ -1,14 +1,55 @@
/*
* Copyright (c) 2019 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <unistd.h>
#include <stdint.h>
#include <stdatomic.h>
#include <stdbool.h>
#include "neatvnc.h"
#include "common.h"
struct gbm_bo;
struct nvnc_fb {
struct nvnc_common common;
enum nvnc_fb_type type;
int ref;
void* addr;
size_t size;
int hold_count;
nvnc_fb_release_fn on_release;
void* release_context;
bool is_external;
uint16_t width;
uint16_t height;
uint32_t fourcc_format;
uint64_t fourcc_modifier;
enum nvnc_transform transform;
uint64_t pts; // in micro seconds
/* main memory buffer attributes */
void* addr;
int32_t stride;
/* dmabuf attributes */
struct gbm_bo* bo;
void* bo_map_handle;
};
void nvnc_fb_hold(struct nvnc_fb* fb);
void nvnc_fb_release(struct nvnc_fb* fb);
int nvnc_fb_map(struct nvnc_fb* fb);
void nvnc_fb_unmap(struct nvnc_fb* fb);

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2021 - 2024 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdint.h>
#include <unistd.h>
#include <stdbool.h>
struct nvnc_fb;
struct h264_encoder;
typedef void (*h264_encoder_packet_handler_fn)(const void* payload, size_t size,
uint64_t pts, void* userdata);
struct h264_encoder_impl {
struct h264_encoder* (*create)(uint32_t width, uint32_t height,
uint32_t format, int quality);
void (*destroy)(struct h264_encoder*);
void (*feed)(struct h264_encoder*, struct nvnc_fb*);
};
struct h264_encoder {
struct h264_encoder_impl *impl;
h264_encoder_packet_handler_fn on_packet_ready;
void* userdata;
bool next_frame_should_be_keyframe;
};
struct h264_encoder* h264_encoder_create(uint32_t width, uint32_t height,
uint32_t format, int quality);
void h264_encoder_destroy(struct h264_encoder*);
void h264_encoder_set_packet_handler_fn(struct h264_encoder*,
h264_encoder_packet_handler_fn);
void h264_encoder_set_userdata(struct h264_encoder*, void* userdata);
void h264_encoder_feed(struct h264_encoder*, struct nvnc_fb*);
void h264_encoder_request_keyframe(struct h264_encoder*);

37
include/http.h 100644
View File

@ -0,0 +1,37 @@
/* Copyright (c) 2014-2016, Marel
* Copyright (c) 2023, Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#define HTTP_FIELD_INDEX_MAX 32
#include <stddef.h>
struct http_kv {
char* key;
char* value;
};
struct http_req {
size_t header_length;
size_t content_length;
char* content_type;
size_t field_index;
struct http_kv field[HTTP_FIELD_INDEX_MAX];
};
int http_req_parse(struct http_req* req, const char* head);
void http_req_free(struct http_req* req);

View File

@ -1,5 +1,7 @@
#pragma once
/*
* Copyright (c) 2019 - 2020 Andri Yngvason
* Copyright (c) 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -14,16 +16,4 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdio.h>
#ifdef NDEBUG
#define log_debug(...)
#else
#define log_debug(fmt, ...) \
fprintf(stderr, "%s:%d: " fmt, __FILE__, __LINE__, ## __VA_ARGS__)
#endif
#define log_error(...) \
fprintf(stderr, "ERROR: " __VA_ARGS__)
void nvnc__log_init(void);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 - 2020 Andri Yngvason
* Copyright (c) 2019 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -18,11 +18,45 @@
#include <stdint.h>
#include <stdbool.h>
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/socket.h>
#define NVNC_NO_PTS UINT64_MAX
#define nvnc_log(lvl, fmt, ...) do { \
assert(lvl != NVNC_LOG_TRACE); \
struct nvnc_log_data ld = { \
.level = lvl, \
.file = __FILE__, \
.line = __LINE__, \
}; \
nvnc__log(&ld, fmt, ## __VA_ARGS__); \
} while(0)
#ifndef NDEBUG
#define nvnc_trace(fmt, ...) do { \
struct nvnc_log_data ld = { \
.level = NVNC_LOG_TRACE, \
.file = __FILE__, \
.line = __LINE__, \
}; \
nvnc__log(&ld, fmt, ## __VA_ARGS__); \
} while(0)
#else
#define nvnc_trace(...)
#endif
struct nvnc;
struct nvnc_client;
struct nvnc_desktop_layout;
struct nvnc_display;
struct nvnc_fb;
struct nvnc_fb_pool;
struct pixman_region16;
struct gbm_bo;
enum nvnc_button_mask {
NVNC_BUTTON_LEFT = 1 << 0,
@ -30,9 +64,55 @@ enum nvnc_button_mask {
NVNC_BUTTON_RIGHT = 1 << 2,
NVNC_SCROLL_UP = 1 << 3,
NVNC_SCROLL_DOWN = 1 << 4,
NVNC_SCROLL_LEFT = 1 << 5,
NVNC_SCROLL_RIGHT = 1 << 6,
};
typedef void (*nvnc_key_fn)(struct nvnc_client*, uint32_t keysym,
enum nvnc_fb_type {
NVNC_FB_UNSPEC = 0,
NVNC_FB_SIMPLE,
NVNC_FB_GBM_BO,
};
/* This is the same as wl_output_transform */
enum nvnc_transform {
NVNC_TRANSFORM_NORMAL = 0,
NVNC_TRANSFORM_90 = 1,
NVNC_TRANSFORM_180 = 2,
NVNC_TRANSFORM_270 = 3,
NVNC_TRANSFORM_FLIPPED = 4,
NVNC_TRANSFORM_FLIPPED_90 = 5,
NVNC_TRANSFORM_FLIPPED_180 = 6,
NVNC_TRANSFORM_FLIPPED_270 = 7,
};
enum nvnc_keyboard_led_state {
NVNC_KEYBOARD_LED_SCROLL_LOCK = 1 << 0,
NVNC_KEYBOARD_LED_NUM_LOCK = 1 << 1,
NVNC_KEYBOARD_LED_CAPS_LOCK = 1 << 2,
};
enum nvnc_log_level {
NVNC_LOG_PANIC = 0,
NVNC_LOG_ERROR = 1,
NVNC_LOG_WARNING = 2,
NVNC_LOG_INFO = 3,
NVNC_LOG_DEBUG = 4,
NVNC_LOG_TRACE = 5,
};
enum nvnc_auth_flags {
NVNC_AUTH_REQUIRE_AUTH = 1 << 0,
NVNC_AUTH_REQUIRE_ENCRYPTION = 1 << 1,
};
struct nvnc_log_data {
enum nvnc_log_level level;
const char* file;
int line;
};
typedef void (*nvnc_key_fn)(struct nvnc_client*, uint32_t key,
bool is_pressed);
typedef void (*nvnc_pointer_fn)(struct nvnc_client*, uint16_t x, uint16_t y,
enum nvnc_button_mask);
@ -43,54 +123,133 @@ typedef void (*nvnc_client_fn)(struct nvnc_client*);
typedef void (*nvnc_damage_fn)(struct pixman_region16* damage, void* userdata);
typedef bool (*nvnc_auth_fn)(const char* username, const char* password,
void* userdata);
typedef void (*nvnc_cut_text_fn)(struct nvnc_client*, const char* text,
uint32_t len);
typedef void (*nvnc_fb_release_fn)(struct nvnc_fb*, void* context);
typedef struct nvnc_fb* (*nvnc_fb_alloc_fn)(uint16_t width, uint16_t height,
uint32_t format, uint16_t stride);
typedef void (*nvnc_cleanup_fn)(void* userdata);
typedef void (*nvnc_log_fn)(const struct nvnc_log_data*, const char* message);
typedef bool (*nvnc_desktop_layout_fn)(
struct nvnc_client*, const struct nvnc_desktop_layout*);
extern const char nvnc_version[];
struct nvnc* nvnc_open(const char* addr, uint16_t port);
struct nvnc* nvnc_open_unix(const char *addr);
struct nvnc* nvnc_open_websocket(const char* addr, uint16_t port);
struct nvnc* nvnc_open_from_fd(int fd);
void nvnc_close(struct nvnc* self);
void nvnc_set_userdata(void* self, void* userdata);
void nvnc_add_display(struct nvnc*, struct nvnc_display*);
void nvnc_remove_display(struct nvnc*, struct nvnc_display*);
void nvnc_set_userdata(void* self, void* userdata, nvnc_cleanup_fn);
void* nvnc_get_userdata(const void* self);
struct nvnc* nvnc_get_server(const struct nvnc_client* client);
struct nvnc* nvnc_client_get_server(const struct nvnc_client* client);
bool nvnc_client_supports_cursor(const struct nvnc_client* client);
int nvnc_client_get_address(const struct nvnc_client* client,
struct sockaddr* restrict addr, socklen_t* restrict addrlen);
const char* nvnc_client_get_auth_username(const struct nvnc_client* client);
void nvnc_set_dimensions(struct nvnc* self, uint16_t width, uint16_t height,
uint32_t fourcc_format);
struct nvnc_client* nvnc_client_first(struct nvnc* self);
struct nvnc_client* nvnc_client_next(struct nvnc_client* client);
void nvnc_client_close(struct nvnc_client* client);
void nvnc_client_set_led_state(struct nvnc_client*,
enum nvnc_keyboard_led_state);
void nvnc_set_name(struct nvnc* self, const char* name);
void nvnc_set_key_fn(struct nvnc* self, nvnc_key_fn);
void nvnc_set_key_code_fn(struct nvnc* self, nvnc_key_fn);
void nvnc_set_pointer_fn(struct nvnc* self, nvnc_pointer_fn);
void nvnc_set_fb_req_fn(struct nvnc* self, nvnc_fb_req_fn);
void nvnc_set_new_client_fn(struct nvnc* self, nvnc_client_fn);
void nvnc_set_client_cleanup_fn(struct nvnc_client* self, nvnc_client_fn fn);
void nvnc_set_cut_text_fn(struct nvnc*, nvnc_cut_text_fn fn);
void nvnc_set_desktop_layout_fn(struct nvnc* self, nvnc_desktop_layout_fn);
bool nvnc_has_auth(void);
int nvnc_enable_auth(struct nvnc* self, const char* privkey_path,
const char* cert_path, nvnc_auth_fn, void* userdata);
int nvnc_enable_auth(struct nvnc* self, enum nvnc_auth_flags flags,
nvnc_auth_fn, void* userdata);
int nvnc_set_tls_creds(struct nvnc* self, const char* privkey_path,
const char* cert_path);
int nvnc_set_rsa_creds(struct nvnc* self, const char* private_key_path);
struct nvnc_fb* nvnc_fb_new(uint16_t width, uint16_t height,
uint32_t fourcc_format);
uint32_t fourcc_format, uint16_t stride);
struct nvnc_fb* nvnc_fb_from_buffer(void* buffer, uint16_t width,
uint16_t height, uint32_t fourcc_format,
int32_t stride);
struct nvnc_fb* nvnc_fb_from_gbm_bo(struct gbm_bo* bo);
void nvnc_fb_ref(struct nvnc_fb* fb);
void nvnc_fb_unref(struct nvnc_fb* fb);
void nvnc_fb_set_release_fn(struct nvnc_fb* fb, nvnc_fb_release_fn fn,
void* context);
void nvnc_fb_set_transform(struct nvnc_fb* fb, enum nvnc_transform);
void nvnc_fb_set_pts(struct nvnc_fb* fb, uint64_t pts);
void* nvnc_fb_get_addr(const struct nvnc_fb* fb);
uint16_t nvnc_fb_get_width(const struct nvnc_fb* fb);
uint16_t nvnc_fb_get_height(const struct nvnc_fb* fb);
uint32_t nvnc_fb_get_fourcc_format(const struct nvnc_fb* fb);
int32_t nvnc_fb_get_stride(const struct nvnc_fb* fb);
int nvnc_fb_get_pixel_size(const struct nvnc_fb* fb);
struct gbm_bo* nvnc_fb_get_gbm_bo(const struct nvnc_fb* fb);
enum nvnc_transform nvnc_fb_get_transform(const struct nvnc_fb* fb);
enum nvnc_fb_type nvnc_fb_get_type(const struct nvnc_fb* fb);
uint64_t nvnc_fb_get_pts(const struct nvnc_fb* fb);
/*
* Feed a new frame to the server. The damaged region is sent to clients
* immediately.
*/
int nvnc_feed_frame(struct nvnc* self, struct nvnc_fb* fb,
const struct pixman_region16* damage);
struct nvnc_fb_pool* nvnc_fb_pool_new(uint16_t width, uint16_t height,
uint32_t fourcc_format, uint16_t stride);
bool nvnc_fb_pool_resize(struct nvnc_fb_pool*, uint16_t width, uint16_t height,
uint32_t fourcc_format, uint16_t stride);
/*
* Find the regions that differ between fb0 and fb1. Regions outside the hinted
* rectangle region are not guaranteed to be checked.
*
* This is a utility function that may be used to reduce network traffic.
*/
int nvnc_check_damage(const struct nvnc_fb* fb0, const struct nvnc_fb* fb1,
int x_hint, int y_hint, int width_hint, int height_hint,
nvnc_damage_fn on_check_done, void* userdata);
void nvnc_fb_pool_set_alloc_fn(struct nvnc_fb_pool*, nvnc_fb_alloc_fn);
void nvnc_fb_pool_ref(struct nvnc_fb_pool*);
void nvnc_fb_pool_unref(struct nvnc_fb_pool*);
struct nvnc_fb* nvnc_fb_pool_acquire(struct nvnc_fb_pool*);
void nvnc_fb_pool_release(struct nvnc_fb_pool*, struct nvnc_fb*);
struct nvnc_display* nvnc_display_new(uint16_t x_pos, uint16_t y_pos);
void nvnc_display_ref(struct nvnc_display*);
void nvnc_display_unref(struct nvnc_display*);
struct nvnc* nvnc_display_get_server(const struct nvnc_display*);
void nvnc_display_feed_buffer(struct nvnc_display*, struct nvnc_fb*,
struct pixman_region16* damage);
uint16_t nvnc_desktop_layout_get_width(const struct nvnc_desktop_layout*);
uint16_t nvnc_desktop_layout_get_height(const struct nvnc_desktop_layout*);
uint8_t nvnc_desktop_layout_get_display_count(const struct nvnc_desktop_layout*);
uint16_t nvnc_desktop_layout_get_display_x_pos(
const struct nvnc_desktop_layout*, uint8_t display_index);
uint16_t nvnc_desktop_layout_get_display_y_pos(
const struct nvnc_desktop_layout*, uint8_t display_index);
uint16_t nvnc_desktop_layout_get_display_width(
const struct nvnc_desktop_layout*, uint8_t display_index);
uint16_t nvnc_desktop_layout_get_display_height(
const struct nvnc_desktop_layout*, uint8_t display_index);
struct nvnc_display* nvnc_desktop_layout_get_display(
const struct nvnc_desktop_layout*, uint8_t display_index);
void nvnc_send_cut_text(struct nvnc*, const char* text, uint32_t len);
void nvnc_set_cursor(struct nvnc*, struct nvnc_fb*, uint16_t width,
uint16_t height, uint16_t hotspot_x, uint16_t hotspot_y,
bool is_damaged);
void nvnc_default_logger(const struct nvnc_log_data* meta, const char* message);
void nvnc_set_log_fn(nvnc_log_fn);
void nvnc_set_log_fn_thread_local(nvnc_log_fn fn);
void nvnc_set_log_level(enum nvnc_log_level);
void nvnc__log(const struct nvnc_log_data*, const char* fmt, ...);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Andri Yngvason
* Copyright (c) 2019 - 2021 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -18,14 +18,28 @@
#include <stdint.h>
#include <unistd.h>
#include <pixman.h>
#include <stdbool.h>
struct rfb_pixel_format;
struct rfb_set_colour_map_entries_msg;
void pixel32_to_cpixel(uint8_t* restrict dst,
void pixel_to_cpixel(uint8_t* restrict dst,
const struct rfb_pixel_format* dst_fmt,
const uint32_t* restrict src,
const uint8_t* restrict src,
const struct rfb_pixel_format* src_fmt,
size_t bytes_per_cpixel, size_t len);
int rfb_pixfmt_from_fourcc(struct rfb_pixel_format *dst, uint32_t src);
uint32_t rfb_pixfmt_to_fourcc(const struct rfb_pixel_format* fmt);
int pixel_size_from_fourcc(uint32_t fourcc);
bool fourcc_to_pixman_fmt(pixman_format_code_t* dst, uint32_t src);
bool extract_alpha_mask(uint8_t* dst, const void* src, uint32_t format,
size_t len);
const char* drm_format_to_string(uint32_t fmt);
const char* rfb_pixfmt_to_string(const struct rfb_pixel_format* fmt);
void make_rgb332_pal8_map(struct rfb_set_colour_map_entries_msg* msg);

View File

@ -1,11 +0,0 @@
#pragma once
struct nvnc_fb;
struct rfb_pixel_format;
struct pixman_region16;
struct vec;
int raw_encode_frame(struct vec* dst, const struct rfb_pixel_format* dst_fmt,
const struct nvnc_fb* src,
const struct rfb_pixel_format* src_fmt,
struct pixman_region16* region);

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2021 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdint.h>
struct nvnc_fb;
struct pixman_region16;
struct resampler;
typedef void (*resampler_fn)(struct nvnc_fb*, struct pixman_region16* damage,
void* userdata);
struct resampler* resampler_create(void);
void resampler_destroy(struct resampler*);
int resampler_feed(struct resampler*, struct nvnc_fb* fb,
struct pixman_region16* damage, resampler_fn on_done,
void* userdata);
void resample_now(struct nvnc_fb* dst, struct nvnc_fb* src,
struct pixman_region16* damage);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Andri Yngvason
* Copyright (c) 2019 - 2024 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -29,8 +29,11 @@ enum rfb_security_type {
RFB_SECURITY_TYPE_INVALID = 0,
RFB_SECURITY_TYPE_NONE = 1,
RFB_SECURITY_TYPE_VNC_AUTH = 2,
RFB_SECURITY_TYPE_RSA_AES = 5,
RFB_SECURITY_TYPE_TIGHT = 16,
RFB_SECURITY_TYPE_VENCRYPT = 19,
RFB_SECURITY_TYPE_APPLE_DH = 30,
RFB_SECURITY_TYPE_RSA_AES256 = 129,
};
enum rfb_security_handshake_result {
@ -45,6 +48,13 @@ enum rfb_client_to_server_msg_type {
RFB_CLIENT_TO_SERVER_KEY_EVENT = 4,
RFB_CLIENT_TO_SERVER_POINTER_EVENT = 5,
RFB_CLIENT_TO_SERVER_CLIENT_CUT_TEXT = 6,
RFB_CLIENT_TO_SERVER_NTP = 160,
RFB_CLIENT_TO_SERVER_SET_DESKTOP_SIZE = 251,
RFB_CLIENT_TO_SERVER_QEMU = 255,
};
enum rfb_client_to_server_qemu_msg_type {
RFB_CLIENT_TO_SERVER_QEMU_KEY_EVENT = 0,
};
enum rfb_encodings {
@ -55,15 +65,26 @@ enum rfb_encodings {
RFB_ENCODING_TIGHT = 7,
RFB_ENCODING_TRLE = 15,
RFB_ENCODING_ZRLE = 16,
RFB_ENCODING_OPEN_H264 = 50,
RFB_ENCODING_CURSOR = -239,
RFB_ENCODING_DESKTOPSIZE = -223,
RFB_ENCODING_QEMU_EXT_KEY_EVENT = -258,
RFB_ENCODING_QEMU_LED_STATE = -261,
RFB_ENCODING_EXTENDEDDESKTOPSIZE = -308,
RFB_ENCODING_PTS = -1000,
RFB_ENCODING_NTP = -1001,
RFB_ENCODING_VMWARE_LED_STATE = 0x574d5668,
};
#define RFB_ENCODING_JPEG_HIGHQ -23
#define RFB_ENCODING_JPEG_LOWQ -32
enum rfb_server_to_client_msg_type {
RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE = 0,
RFB_SERVER_TO_CLIENT_SET_COLOUR_MAP_ENTRIES = 1,
RFB_SERVER_TO_CLIENT_BELL = 2,
RFB_SERVER_TO_CLIENT_SERVER_CUT_TEXT = 3,
RFB_SERVER_TO_CLIENT_NTP = 160,
};
enum rfb_vencrypt_subtype {
@ -76,9 +97,35 @@ enum rfb_vencrypt_subtype {
RFB_VENCRYPT_X509_PLAIN,
};
enum rfb_resize_initiator {
RFB_RESIZE_INITIATOR_SERVER = 0,
RFB_RESIZE_INITIATOR_THIS_CLIENT = 1,
RFB_RESIZE_INITIATOR_OTHER_CLIENT = 2,
};
enum rfb_resize_status {
RFB_RESIZE_STATUS_SUCCESS = 0,
RFB_RESIZE_STATUS_PROHIBITED = 1,
RFB_RESIZE_STATUS_OUT_OF_RESOURCES = 2,
RFB_RESIZE_STATUS_INVALID_LAYOUT = 3,
RFB_RESIZE_STATUS_REQUEST_FORWARDED = 4,
};
enum rfb_rsa_aes_cred_subtype {
RFB_RSA_AES_CRED_SUBTYPE_USER_AND_PASS = 1,
RFB_RSA_AES_CRED_SUBTYPE_ONLY_PASS = 2,
};
// This is the same for both qemu and vmware extensions
enum rfb_led_state {
RFB_LED_STATE_SCROLL_LOCK = 1 << 0,
RFB_LED_STATE_NUM_LOCK = 1 << 1,
RFB_LED_STATE_CAPS_LOCK = 1 << 2,
};
struct rfb_security_types_msg {
uint8_t n;
uint8_t types[1];
uint8_t types[0];
} RFB_PACKED;
struct rfb_error_reason {
@ -131,6 +178,14 @@ struct rfb_client_key_event_msg {
uint32_t key;
} RFB_PACKED;
struct rfb_client_qemu_key_event_msg {
uint8_t type;
uint8_t subtype;
uint16_t down_flag;
uint32_t keysym;
uint32_t keycode;
} RFB_PACKED;
struct rfb_client_pointer_event_msg {
uint8_t type;
uint8_t button_mask;
@ -138,11 +193,11 @@ struct rfb_client_pointer_event_msg {
uint16_t y;
} RFB_PACKED;
struct rfb_client_cut_text_msg {
struct rfb_cut_text_msg {
uint8_t type;
uint8_t padding[3];
uint32_t length;
char test[0];
char text[0];
} RFB_PACKED;
struct rfb_server_fb_rect {
@ -153,6 +208,25 @@ struct rfb_server_fb_rect {
int32_t encoding;
} RFB_PACKED;
struct rfb_screen {
uint32_t id;
uint16_t x;
uint16_t y;
uint16_t width;
uint16_t height;
uint32_t flags;
} RFB_PACKED;
struct rfb_client_set_desktop_size_event_msg {
uint8_t type;
uint8_t padding;
uint16_t width;
uint16_t height;
uint8_t number_of_screens;
uint8_t padding2;
struct rfb_screen screens[0];
} RFB_PACKED;
struct rfb_server_fb_update_msg {
uint8_t type;
uint8_t padding;
@ -174,3 +248,42 @@ struct rfb_vencrypt_plain_auth_msg {
uint32_t password_len;
char text[0];
} RFB_PACKED;
struct rfb_ntp_msg {
uint8_t type;
uint8_t padding[3];
uint32_t t0, t1, t2, t3;
} RFB_PACKED;
struct rfb_apple_dh_server_msg {
uint16_t generator;
uint16_t key_size;
uint8_t modulus_and_key[0];
} RFB_PACKED;
struct rfb_apple_dh_client_msg {
uint8_t encrypted_credentials[128];
uint8_t public_key[0];
} RFB_PACKED;
struct rfb_rsa_aes_pub_key_msg {
uint32_t length;
uint8_t modulus_and_exponent[0];
} RFB_PACKED;
struct rfb_rsa_aes_challenge_msg {
uint16_t length;
uint8_t challenge[0];
} RFB_PACKED;
struct rfb_colour_map_entry {
uint16_t r, g, b;
} RFB_PACKED;
struct rfb_set_colour_map_entries_msg {
uint8_t type;
uint8_t padding;
uint16_t first_colour;
uint16_t n_colours;
struct rfb_colour_map_entry colours[0];
} RFB_PACKED;

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2020 - 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "stream.h"
#include <aml.h>
static inline void stream__poll_r(struct stream* self)
{
aml_set_event_mask(self->handler, AML_EVENT_READ);
}
static inline void stream__poll_w(struct stream* self)
{
aml_set_event_mask(self->handler, AML_EVENT_WRITE);
}
static inline void stream__poll_rw(struct stream* self)
{
aml_set_event_mask(self->handler, AML_EVENT_READ | AML_EVENT_WRITE);
}
void stream_req__finish(struct stream_req* req, enum stream_req_status status);
void stream__remote_closed(struct stream* self);

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "stream.h"
#include <unistd.h>
struct stream;
int stream_tcp_init(struct stream* self, int fd, stream_event_fn on_event,
void* userdata);
int stream_tcp_close(struct stream* self);
void stream_tcp_destroy(struct stream* self);
ssize_t stream_tcp_read(struct stream* self, void* dst, size_t size);
int stream_tcp_send(struct stream* self, struct rcbuf* payload,
stream_req_fn on_done, void* userdata);
int stream_tcp_send_first(struct stream* self, struct rcbuf* payload);
void stream_tcp_exec_and_send(struct stream* self,
stream_exec_fn exec_fn, void* userdata);
int stream_tcp_install_cipher(struct stream* self,
struct crypto_cipher* cipher);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Andri Yngvason
* Copyright (c) 2020 - 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -14,16 +14,22 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <uv.h>
#pragma once
#include "config.h"
#include "sys/queue.h"
#include "rcbuf.h"
#include "vec.h"
#ifdef ENABLE_TLS
#include <gnutls/gnutls.h>
#ifdef HAVE_CRYPTO
#include "crypto.h"
#endif
#include <stdint.h>
#include <stdbool.h>
#define STREAM_ALLOC_SIZE 4096
enum stream_state {
STREAM_STATE_NORMAL = 0,
STREAM_STATE_CLOSED,
@ -33,11 +39,6 @@ enum stream_state {
#endif
};
enum stream_status {
STREAM_READY = 0,
STREAM_CLOSED,
};
enum stream_req_status {
STREAM_REQ_DONE = 0,
STREAM_REQ_FAILED,
@ -49,34 +50,57 @@ enum stream_event {
};
struct stream;
struct crypto_cipher;
typedef void (*stream_event_fn)(struct stream*, enum stream_event);
typedef void (*stream_req_fn)(void*, enum stream_req_status);
typedef struct rcbuf* (*stream_exec_fn)(struct stream*, void* userdata);
struct stream_req {
struct rcbuf* payload;
stream_req_fn on_done;
stream_exec_fn exec;
void* userdata;
TAILQ_ENTRY(stream_req) link;
};
TAILQ_HEAD(stream_send_queue, stream_req);
struct stream_impl {
int (*close)(struct stream*);
void (*destroy)(struct stream*);
ssize_t (*read)(struct stream*, void* dst, size_t size);
int (*send)(struct stream*, struct rcbuf* payload,
stream_req_fn on_done, void* userdata);
int (*send_first)(struct stream*, struct rcbuf* payload);
void (*exec_and_send)(struct stream*, stream_exec_fn, void* userdata);
};
struct stream {
struct stream_impl *impl;
enum stream_state state;
int fd;
uv_poll_t uv_poll;
struct aml_handler* handler;
stream_event_fn on_event;
void* userdata;
struct stream_send_queue send_queue;
#ifdef ENABLE_TLS
gnutls_session_t tls_session;
#endif
uint32_t bytes_sent;
uint32_t bytes_received;
bool cork;
struct crypto_cipher* cipher;
struct vec tmp_buf;
};
#ifdef ENABLE_WEBSOCKET
struct stream* stream_ws_new(int fd, stream_event_fn on_event, void* userdata);
#endif
struct stream* stream_new(int fd, stream_event_fn on_event, void* userdata);
int stream_close(struct stream* self);
void stream_destroy(struct stream* self);
@ -85,7 +109,17 @@ int stream_write(struct stream* self, const void* payload, size_t len,
stream_req_fn on_done, void* userdata);
int stream_send(struct stream* self, struct rcbuf* payload,
stream_req_fn on_done, void* userdata);
int stream_send_first(struct stream* self, struct rcbuf* payload);
// Queue a pure function to be executed when time comes to send it.
void stream_exec_and_send(struct stream* self, stream_exec_fn, void* userdata);
#ifdef ENABLE_TLS
int stream_upgrade_to_tls(struct stream* self, void* context);
#endif
#ifdef HAVE_CRYPTO
int stream_upgrade_to_rsa_eas(struct stream* base,
enum crypto_cipher_type cipher_type,
const uint8_t* enc_key, const uint8_t* dec_key);
#endif

View File

@ -35,8 +35,6 @@
#ifndef _SYS_QUEUE_H_
#define _SYS_QUEUE_H_
#include <sys/cdefs.h>
/*
* This file defines four types of data structures: singly-linked lists,
* singly-linked tail queues, lists and tail queues.

View File

@ -1,10 +0,0 @@
#pragma once
struct vec;
struct nvnc_client;
struct nvnc_fb;
struct pixman_region16;
int tight_encode_frame(struct vec* dst, struct nvnc_client* client,
const struct nvnc_fb* fb,
struct pixman_region16* region);

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2020 - 2021 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "neatvnc.h"
#include <pixman.h>
void nvnc_transform_to_pixman_transform(pixman_transform_t* dst,
enum nvnc_transform src, int width, int height);
void nvnc_transform_dimensions(enum nvnc_transform transform, uint32_t* width,
uint32_t* height);
void nvnc_transform_region(struct pixman_region16* dst,
struct pixman_region16* src, enum nvnc_transform transform,
int width, int height);

31
include/usdt.h 100644
View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "config.h"
#ifdef HAVE_USDT
#include <sys/sdt.h>
#else
#define DTRACE_PROBE(...)
#define DTRACE_PROBE1(...)
#define DTRACE_PROBE2(...)
#define DTRACE_PROBE3(...)
#define DTRACE_PROBE4(...)
#define DTRACE_PROBE5(...)
#define DTRACE_PROBE6(...)
#endif

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#define WS_HEADER_MIN_SIZE 14
enum ws_opcode {
WS_OPCODE_CONT = 0,
WS_OPCODE_TEXT,
WS_OPCODE_BIN,
WS_OPCODE_CLOSE = 8,
WS_OPCODE_PING,
WS_OPCODE_PONG,
};
struct ws_frame_header {
bool fin;
enum ws_opcode opcode;
bool mask;
uint64_t payload_length;
uint8_t masking_key[4];
size_t header_length;
};
ssize_t ws_handshake(char* output, size_t output_maxlen, const char* input);
const char *ws_opcode_name(enum ws_opcode op);
bool ws_parse_frame_header(struct ws_frame_header* header,
const uint8_t* payload, size_t length);
void ws_apply_mask(const struct ws_frame_header* header,
uint8_t* restrict payload);
void ws_copy_payload(const struct ws_frame_header* header,
uint8_t* restrict dst, const uint8_t* restrict src, size_t len);
int ws_write_frame_header(uint8_t* dst, const struct ws_frame_header* header);

7044
include/xxhash.h 100644

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,47 @@
project(
'neatvnc',
'c',
version: '0.0.0',
version: '0.9-dev',
license: 'ISC',
default_options: [
'c_std=gnu11',
'warning_level=2',
],
)
buildtype = get_option('buildtype')
host_system = host_machine.system()
c_args = [
'-D_GNU_SOURCE',
'-fvisibility=hidden',
'-DAML_UNSTABLE_API=1',
'-Wmissing-prototypes',
'-Wno-unused-parameter',
'-Wno-format-truncation',
]
if buildtype == 'release' or buildtype == 'plain'
if buildtype != 'debug' and buildtype != 'debugoptimized'
c_args += '-DNDEBUG'
endif
cpu = host_machine.cpu_family()
if cpu == 'x86_64'
c_args += '-mavx'
elif cpu == 'arm'
c_args += '-mfpu=neon'
version = '"@0@"'.format(meson.project_version())
git = find_program('git', native: true, required: false)
if git.found()
git_commit = run_command([git, 'rev-parse', '--short', 'HEAD'])
git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'])
if git_commit.returncode() == 0 and git_branch.returncode() == 0
version = '"v@0@-@1@ (@2@)"'.format(
meson.project_version(),
git_commit.stdout().strip(),
git_branch.stdout().strip(),
)
endif
endif
add_project_arguments('-DPROJECT_VERSION=@0@'.format(version), language: 'c')
libdrm_inc = dependency('libdrm').partial_dependency(compile_args: true)
add_project_arguments(c_args, language: 'c')
@ -34,11 +50,28 @@ cc = meson.get_compiler('c')
libm = cc.find_library('m', required: false)
pixman = dependency('pixman-1')
libuv = dependency('libuv')
libturbojpeg = dependency('libturbojpeg', required: get_option('tight-encoding'))
libturbojpeg = dependency('libturbojpeg', required: get_option('jpeg'))
gnutls = dependency('gnutls', required: get_option('tls'))
nettle = dependency('nettle', required: get_option('nettle'))
hogweed = dependency('hogweed', required: get_option('nettle'))
gmp = dependency('gmp', required: get_option('nettle'))
zlib = dependency('zlib')
gbm = dependency('gbm', required: get_option('gbm'))
libdrm = dependency('libdrm', required: get_option('h264'))
inc = include_directories('include', 'contrib/miniz')
libavcodec = dependency('libavcodec', required: get_option('h264'))
libavfilter = dependency('libavfilter', required: get_option('h264'))
libavutil = dependency('libavutil', required: get_option('h264'))
aml_version = ['>=0.3.0', '<0.4.0']
aml_project = subproject('aml', required: false, version: aml_version)
if aml_project.found()
aml = aml_project.get_variable('aml_dep')
else
aml = dependency('aml', version: aml_version)
endif
inc = include_directories('include')
sources = [
'src/server.c',
@ -46,38 +79,108 @@ sources = [
'src/zrle.c',
'src/raw-encoding.c',
'src/pixels.c',
'src/damage.c',
'src/fb.c',
'src/fb_pool.c',
'src/rcbuf.c',
'src/stream.c',
'contrib/miniz/miniz.c',
'src/stream-common.c',
'src/stream-tcp.c',
'src/desktop-layout.c',
'src/display.c',
'src/tight.c',
'src/enc-util.c',
'src/qnum-to-evdev.c',
'src/resampler.c',
'src/transform-util.c',
'src/damage-refinery.c',
'src/encoder.c',
'src/cursor.c',
'src/logging.c',
'src/base64.c',
]
dependencies = [
libm,
pixman,
libuv,
aml,
zlib,
libdrm_inc,
]
enable_websocket = false
config = configuration_data()
if libturbojpeg.found()
dependencies += libturbojpeg
sources += 'src/tight.c'
config.set('ENABLE_TIGHT', true)
config.set('HAVE_JPEG', true)
endif
if gnutls.found()
sources += 'src/stream-gnutls.c'
dependencies += gnutls
config.set('ENABLE_TLS', true)
endif
if nettle.found() and hogweed.found() and gmp.found()
dependencies += [ nettle, hogweed, gmp ]
enable_websocket = true
config.set('HAVE_CRYPTO', true)
sources += ['src/crypto-nettle.c', 'src/stream-rsa-aes.c']
endif
if host_system == 'linux' and get_option('systemtap') and cc.has_header('sys/sdt.h')
config.set('HAVE_USDT', true)
endif
if gbm.found()
dependencies += gbm
config.set('HAVE_GBM', true)
endif
have_ffmpeg = gbm.found() and libdrm.found() and libavcodec.found() and libavfilter.found() and libavutil.found()
have_v4l2 = gbm.found() and libdrm.found() and cc.check_header('linux/videodev2.h')
if have_ffmpeg
sources += [ 'src/h264-encoder-ffmpeg-impl.c' ]
dependencies += [libdrm, libavcodec, libavfilter, libavutil]
config.set('HAVE_FFMPEG', true)
config.set('HAVE_LIBAVUTIL', true)
endif
if have_v4l2
sources += [ 'src/h264-encoder-v4l2m2m-impl.c' ]
config.set('HAVE_V4L2', true)
endif
if have_ffmpeg or have_v4l2
sources += [ 'src/h264-encoder.c', 'src/open-h264.c' ]
config.set('ENABLE_OPEN_H264', true)
endif
if enable_websocket
sources += [
'src/ws-handshake.c',
'src/ws-framing.c',
'src/http.c',
'src/stream-ws.c',
]
config.set('ENABLE_WEBSOCKET', true)
endif
if get_option('experimental')
if buildtype == 'release'
warning('Experimental features enabled in release build')
endif
config.set('ENABLE_EXPERIMENTAL', true)
endif
configure_file(
output: 'config.h',
configuration: config,
)
neatvnc = shared_library(
neatvnc = library(
'neatvnc',
sources,
version: '0.0.0',
@ -91,6 +194,18 @@ neatvnc_dep = declare_dependency(
link_with: neatvnc,
)
if get_option('examples')
subdir('examples')
endif
if get_option('benchmarks')
subdir('bench')
endif
if get_option('tests')
subdir('test')
endif
install_headers('include/neatvnc.h')
pkgconfig = import('pkgconfig')

View File

@ -1,2 +1,10 @@
option('tight-encoding', type: 'feature', value: 'disabled', description: 'Enable Tight encoding (experimental)')
option('benchmarks', type: 'boolean', value: false, description: 'Build benchmarks')
option('examples', type: 'boolean', value: false, description: 'Build examples')
option('tests', type: 'boolean', value: false, description: 'Build unit tests')
option('jpeg', type: 'feature', value: 'auto', description: 'Enable JPEG compression')
option('tls', type: 'feature', value: 'auto', description: 'Enable encryption & authentication')
option('nettle', type: 'feature', value: 'auto', description: 'Enable nettle low level encryption library')
option('systemtap', type: 'boolean', value: false, description: 'Enable tracing using sdt')
option('gbm', type: 'feature', value: 'auto', description: 'Enable GBM integration')
option('h264', type: 'feature', value: 'auto', description: 'Enable open h264 encoding')
option('experimental', type: 'boolean', value: false, description: 'Enable experimental features')

155
src/base64.c 100644
View File

@ -0,0 +1,155 @@
/* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "base64.h"
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
static const char base64_enc_lut[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const uint8_t base64_validation_lut[256] = {
['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1,
['E'] = 1, ['F'] = 1, ['G'] = 1, ['H'] = 1,
['I'] = 1, ['J'] = 1, ['K'] = 1, ['L'] = 1,
['M'] = 1, ['N'] = 1, ['O'] = 1, ['P'] = 1,
['Q'] = 1, ['R'] = 1, ['S'] = 1, ['T'] = 1,
['U'] = 1, ['V'] = 1, ['W'] = 1, ['X'] = 1,
['Y'] = 1, ['Z'] = 1, ['a'] = 1, ['b'] = 1,
['c'] = 1, ['d'] = 1, ['e'] = 1, ['f'] = 1,
['g'] = 1, ['h'] = 1, ['i'] = 1, ['j'] = 1,
['k'] = 1, ['l'] = 1, ['m'] = 1, ['n'] = 1,
['o'] = 1, ['p'] = 1, ['q'] = 1, ['r'] = 1,
['s'] = 1, ['t'] = 1, ['u'] = 1, ['v'] = 1,
['w'] = 1, ['x'] = 1, ['y'] = 1, ['z'] = 1,
['0'] = 1, ['1'] = 1, ['2'] = 1, ['3'] = 1,
['4'] = 1, ['5'] = 1, ['6'] = 1, ['7'] = 1,
['8'] = 1, ['9'] = 1, ['+'] = 1, ['/'] = 1,
['-'] = 1, ['_'] = 1, ['='] = 1,
};
static const uint8_t base64_dec_lut[256] = {
['A'] = 0x00, ['B'] = 0x01, ['C'] = 0x02, ['D'] = 0x03,
['E'] = 0x04, ['F'] = 0x05, ['G'] = 0x06, ['H'] = 0x07,
['I'] = 0x08, ['J'] = 0x09, ['K'] = 0x0a, ['L'] = 0x0b,
['M'] = 0x0c, ['N'] = 0x0d, ['O'] = 0x0e, ['P'] = 0x0f,
['Q'] = 0x10, ['R'] = 0x11, ['S'] = 0x12, ['T'] = 0x13,
['U'] = 0x14, ['V'] = 0x15, ['W'] = 0x16, ['X'] = 0x17,
['Y'] = 0x18, ['Z'] = 0x19, ['a'] = 0x1a, ['b'] = 0x1b,
['c'] = 0x1c, ['d'] = 0x1d, ['e'] = 0x1e, ['f'] = 0x1f,
['g'] = 0x20, ['h'] = 0x21, ['i'] = 0x22, ['j'] = 0x23,
['k'] = 0x24, ['l'] = 0x25, ['m'] = 0x26, ['n'] = 0x27,
['o'] = 0x28, ['p'] = 0x29, ['q'] = 0x2a, ['r'] = 0x2b,
['s'] = 0x2c, ['t'] = 0x2d, ['u'] = 0x2e, ['v'] = 0x2f,
['w'] = 0x30, ['x'] = 0x31, ['y'] = 0x32, ['z'] = 0x33,
['0'] = 0x34, ['1'] = 0x35, ['2'] = 0x36, ['3'] = 0x37,
['4'] = 0x38, ['5'] = 0x39, ['6'] = 0x3a, ['7'] = 0x3b,
['8'] = 0x3c, ['9'] = 0x3d, ['+'] = 0x3e, ['/'] = 0x3f,
['-'] = 0x3e, ['_'] = 0x3f,
};
void base64_encode(char* dst, const uint8_t* src, size_t src_len)
{
size_t i = 0;
for (; i < src_len / 3; ++i) {
uint32_t tmp = 0;
tmp |= (uint32_t)src[i * 3 + 0] << 16;
tmp |= (uint32_t)src[i * 3 + 1] << 8;
tmp |= (uint32_t)src[i * 3 + 2];
dst[i * 4 + 0] = base64_enc_lut[tmp >> 18];
dst[i * 4 + 1] = base64_enc_lut[(tmp >> 12) & 0x3f];
dst[i * 4 + 2] = base64_enc_lut[(tmp >> 6) & 0x3f];
dst[i * 4 + 3] = base64_enc_lut[tmp & 0x3f];
}
size_t rem = src_len % 3;
if (rem == 0) {
dst[i * 4] = '\0';
return;
}
uint32_t tmp = 0;
for (size_t r = 0; r < rem; ++r) {
size_t s = (2 - r) * 8;
tmp |= (uint32_t)src[i * 3 + r] << s;
}
size_t di = 0;
for (; di < rem + 1; ++di) {
size_t s = (3 - di) * 6;
dst[i * 4 + di] = base64_enc_lut[(tmp >> s) & 0x3f];
}
for (; di < 4; ++di) {
dst[i * 4 + di] = '=';
}
dst[i * 4 + di] = '\0';
}
static bool base64_is_valid(const char* src)
{
for (int i = 0; src[i]; i++)
if (!base64_validation_lut[(uint8_t)src[i]])
return false;
return true;
}
ssize_t base64_decode(uint8_t* dst, const char* src)
{
if (!base64_is_valid(src))
return -1;
size_t src_len = strcspn(src, "=");
size_t i = 0;
for (; i < src_len / 4; ++i) {
uint32_t tmp = 0;
tmp |= (uint32_t)base64_dec_lut[(uint8_t)src[i * 4 + 0]] << 18;
tmp |= (uint32_t)base64_dec_lut[(uint8_t)src[i * 4 + 1]] << 12;
tmp |= (uint32_t)base64_dec_lut[(uint8_t)src[i * 4 + 2]] << 6;
tmp |= (uint32_t)base64_dec_lut[(uint8_t)src[i * 4 + 3]];
dst[i * 3 + 0] = tmp >> 16;
dst[i * 3 + 1] = (tmp >> 8) & 0xff;
dst[i * 3 + 2] = tmp & 0xff;
}
size_t rem = src_len % 4;
if (rem == 0)
return i * 3;
size_t di = 0;
uint32_t tmp = 0;
for (size_t r = 0; r < rem; ++r) {
size_t s = (3 - r) * 6;
tmp |= (uint32_t)base64_dec_lut[(uint8_t)src[i * 4 + r]] << s;
}
for (; di < (rem * 3) / 4; ++di) {
size_t s = (2 - di) * 8;
dst[i * 3 + di] = (tmp >> s) & 0xff;
}
return i * 3 + di;
}

739
src/crypto-nettle.c 100644
View File

@ -0,0 +1,739 @@
#include "crypto.h"
#include "neatvnc.h"
#include "vec.h"
#include "base64.h"
#include <gmp.h>
#include <nettle/base64.h>
#include <nettle/base16.h>
#include <nettle/aes.h>
#include <nettle/eax.h>
#include <nettle/md5.h>
#include <nettle/sha1.h>
#include <nettle/sha.h>
#include <nettle/rsa.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/param.h>
#include <arpa/inet.h>
// TODO: This is linux specific
#include <sys/random.h>
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
struct vec;
struct crypto_key {
int g;
mpz_t p;
mpz_t q;
};
struct crypto_aes_eax {
struct eax_aes128_ctx ctx;
uint64_t count[2];
};
struct crypto_aes256_eax {
struct EAX_CTX(struct aes256_ctx) ctx;
uint64_t count[2];
};
struct crypto_cipher {
union {
struct aes128_ctx aes128_ecb;
struct crypto_aes_eax aes_eax;
struct crypto_aes256_eax aes256_eax;
} enc_ctx;
union {
struct aes128_ctx aes128_ecb;
struct crypto_aes_eax aes_eax;
struct crypto_aes256_eax aes256_eax;
} dec_ctx;
bool (*encrypt)(struct crypto_cipher*, struct vec* dst, uint8_t* mac,
const uint8_t* src, size_t src_len, const uint8_t* ad,
size_t ad_len);
ssize_t (*decrypt)(struct crypto_cipher*, uint8_t* dst, uint8_t* mac,
const uint8_t* src, size_t src_len, const uint8_t* ad,
size_t ad_len);
};
struct crypto_hash {
union {
struct md5_ctx md5;
struct sha1_ctx sha1;
struct sha256_ctx sha256;
} ctx;
void (*update)(void* ctx, size_t len, const uint8_t* src);
void (*digest)(void* ctx, size_t len, uint8_t* dst);
};
struct crypto_rsa_pub_key {
struct rsa_public_key key;
};
struct crypto_rsa_priv_key {
struct rsa_private_key key;
};
void crypto_dump_base64(const char* msg, const uint8_t* bytes, size_t len)
{
struct base64_encode_ctx ctx = {};
size_t buflen = BASE64_ENCODE_LENGTH(len);
char* buffer = malloc(buflen + BASE64_ENCODE_FINAL_LENGTH + 1);
assert(buffer);
nettle_base64_encode_init(&ctx);
size_t count = nettle_base64_encode_update(&ctx, buffer, len, bytes);
count += nettle_base64_encode_final(&ctx, buffer + count);
buffer[count] = '\0';
nvnc_log(NVNC_LOG_DEBUG, "%s: %s", msg, buffer);
free(buffer);
}
void crypto_dump_base16(const char* msg, const uint8_t* bytes, size_t len)
{
size_t buflen = BASE16_ENCODE_LENGTH(len);
char* buffer = calloc(1, buflen + 1);
assert(buffer);
nettle_base16_encode_update(buffer, len, bytes);
nvnc_log(NVNC_LOG_DEBUG, "%s: %s", msg, buffer);
free(buffer);
}
void crypto_random(uint8_t* dst, size_t len)
{
getrandom(dst, len, 0);
}
static void crypto_import(mpz_t n, const uint8_t* src, size_t len)
{
int order = 1;
int unit_size = 1;
int endian = 1;
int skip_bits = 0;
mpz_import(n, len, order, unit_size, endian, skip_bits, src);
}
struct crypto_key *crypto_key_new(int g, const uint8_t* p, uint32_t p_len,
const uint8_t* q, uint32_t q_len)
{
struct crypto_key* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->g = g;
mpz_init(self->p);
crypto_import(self->p, p, p_len);
mpz_init(self->q);
crypto_import(self->q, q, q_len);
return self;
}
void crypto_key_del(struct crypto_key* key)
{
if (!key)
return;
mpz_clear(key->q);
mpz_clear(key->p);
free(key);
}
int crypto_key_g(const struct crypto_key* key)
{
return key->g;
}
static size_t crypto_export(uint8_t* dst, size_t dst_size, const mpz_t n)
{
int order = 1; // msb first
int unit_size = 1; // byte
int endian = 1; // msb first
int skip_bits = 0;
size_t bitsize = mpz_sizeinbase(n, 2);
size_t bytesize = (bitsize + 7) / 8;
assert(bytesize <= dst_size);
memset(dst, 0, dst_size);
mpz_export(dst + dst_size - bytesize, &bytesize, order, unit_size,
endian, skip_bits, n);
return bytesize;
}
uint32_t crypto_key_p(const struct crypto_key* key, uint8_t* dst,
uint32_t dst_size)
{
return crypto_export(dst, dst_size, key->p);
}
uint32_t crypto_key_q(const struct crypto_key* key, uint8_t* dst,
uint32_t dst_size)
{
return crypto_export(dst, dst_size, key->q);
}
static void initialise_p(mpz_t p)
{
// RFC 3526, section 3
static const char s[] =
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
"83655D23DCA3AD961C62F356208552BB9ED529077096966D"
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
"15728E5A8AACAA68FFFFFFFFFFFFFFFF";
char buf[256];
size_t len = 0;
struct base16_decode_ctx ctx;
nettle_base16_decode_init(&ctx);
nettle_base16_decode_update(&ctx, &len, (uint8_t*)buf, sizeof(s) - 1, s);
nettle_base16_decode_final(&ctx);
assert(len == sizeof(buf));
crypto_import(p, (const uint8_t*)buf, sizeof(buf));
}
static void generate_random(mpz_t n)
{
uint8_t buf[256];
getrandom(buf, sizeof(buf), 0);
crypto_import(n, buf, sizeof(buf));
}
struct crypto_key* crypto_keygen(void)
{
struct crypto_key* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->g = 2;
mpz_init(self->p);
initialise_p(self->p);
mpz_init(self->q);
generate_random(self->q);
return self;
}
struct crypto_key* crypto_derive_public_key(const struct crypto_key* priv)
{
struct crypto_key* pub = calloc(1, sizeof(*pub));
if (!pub)
return NULL;
pub->g = priv->g;
mpz_set(pub->p, priv->p);
mpz_init(pub->q);
mpz_t g;
mpz_init(g);
mpz_set_ui(g, priv->g);
mpz_powm_sec(pub->q, g, priv->q, priv->p);
mpz_clear(g);
return pub;
}
struct crypto_key* crypto_derive_shared_secret(
const struct crypto_key* own_secret,
const struct crypto_key* remote_public_key)
{
if (own_secret->g != remote_public_key->g) {
return NULL;
}
if (mpz_cmp(own_secret->p, remote_public_key->p) != 0) {
return NULL;
}
struct crypto_key* shared = calloc(1, sizeof(*shared));
if (!shared)
return NULL;
shared->g = own_secret->g;
mpz_set(shared->p, own_secret->p);
mpz_t g;
mpz_init(g);
mpz_set_ui(g, own_secret->g);
mpz_powm_sec(shared->q, remote_public_key->q, own_secret->q,
own_secret->p);
mpz_clear(g);
return shared;
}
static bool crypto_cipher_aes128_ecb_encrypt(struct crypto_cipher* self,
struct vec* dst, uint8_t* mac, const uint8_t* src,
size_t len, const uint8_t* ad, size_t ad_len)
{
vec_reserve(dst, dst->len + len);
aes128_encrypt(&self->enc_ctx.aes128_ecb, len, dst->data, src);
dst->len = len;
return true;
}
static ssize_t crypto_cipher_aes128_ecb_decrypt(struct crypto_cipher* self,
uint8_t* dst, uint8_t* mac, const uint8_t* src, size_t len,
const uint8_t* ad, size_t ad_len)
{
aes128_decrypt(&self->dec_ctx.aes128_ecb, len, dst, src);
return len;
}
static struct crypto_cipher* crypto_cipher_new_aes128_ecb(
const uint8_t* enc_key, const uint8_t* dec_key)
{
struct crypto_cipher* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
if (enc_key)
aes128_set_encrypt_key(&self->enc_ctx.aes128_ecb, enc_key);
if (dec_key)
aes128_set_decrypt_key(&self->dec_ctx.aes128_ecb, dec_key);
self->encrypt = crypto_cipher_aes128_ecb_encrypt;
self->decrypt = crypto_cipher_aes128_ecb_decrypt;
return self;
}
static void crypto_aes_eax_update_nonce(struct crypto_aes_eax* self)
{
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
nettle_eax_aes128_set_nonce(&self->ctx, 16, (const uint8_t*)self->count);
#else
uint64_t c[2];
c[0] = __builtin_bswap64(self->count[0]);
c[1] = __builtin_bswap64(self->count[1]);
nettle_eax_aes128_set_nonce(&self->ctx, 16, (const uint8_t*)c);
#endif
if (++self->count[0] == 0)
++self->count[1];
}
static bool crypto_cipher_aes_eax_encrypt(struct crypto_cipher* self,
struct vec* dst, uint8_t* mac, const uint8_t* src,
size_t src_len, const uint8_t* ad, size_t ad_len)
{
vec_reserve(dst, dst->len + src_len);
crypto_aes_eax_update_nonce(&self->enc_ctx.aes_eax);
nettle_eax_aes128_update(&self->enc_ctx.aes_eax.ctx, ad_len,
(uint8_t*)ad);
nettle_eax_aes128_encrypt(&self->enc_ctx.aes_eax.ctx, src_len,
(uint8_t*)dst->data + dst->len, src);
dst->len += src_len;
nettle_eax_aes128_digest(&self->enc_ctx.aes_eax.ctx, 16, mac);
return true;
}
static ssize_t crypto_cipher_aes_eax_decrypt(struct crypto_cipher* self,
uint8_t* dst, uint8_t* mac, const uint8_t* src, size_t len,
const uint8_t* ad, size_t ad_len)
{
crypto_aes_eax_update_nonce(&self->dec_ctx.aes_eax);
nettle_eax_aes128_update(&self->dec_ctx.aes_eax.ctx, ad_len, ad);
nettle_eax_aes128_decrypt(&self->dec_ctx.aes_eax.ctx, len, dst, src);
nettle_eax_aes128_digest(&self->dec_ctx.aes_eax.ctx, 16, mac);
return len;
}
static struct crypto_cipher* crypto_cipher_new_aes_eax(const uint8_t* enc_key,
const uint8_t* dec_key)
{
struct crypto_cipher* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
eax_aes128_set_key(&self->enc_ctx.aes_eax.ctx, enc_key);
eax_aes128_set_key(&self->dec_ctx.aes_eax.ctx, dec_key);
self->encrypt = crypto_cipher_aes_eax_encrypt;
self->decrypt = crypto_cipher_aes_eax_decrypt;
return self;
}
static void crypto_aes256_eax_update_nonce(struct crypto_aes256_eax* self)
{
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
EAX_SET_NONCE(&self->ctx, aes256_encrypt, 16, (const uint8_t*)self->count);
#else
uint64_t c[2];
c[0] = __builtin_bswap64(self->count[0]);
c[1] = __builtin_bswap64(self->count[1]);
EAX_SET_NONCE(&self->ctx, aes256_encrypt, 16, (const uint8_t*)c);
#endif
if (++self->count[0] == 0)
++self->count[1];
}
static bool crypto_cipher_aes256_eax_encrypt(struct crypto_cipher* self,
struct vec* dst, uint8_t* mac, const uint8_t* src,
size_t src_len, const uint8_t* ad, size_t ad_len)
{
vec_reserve(dst, dst->len + src_len);
crypto_aes256_eax_update_nonce(&self->enc_ctx.aes256_eax);
EAX_UPDATE(&self->enc_ctx.aes256_eax.ctx, aes256_encrypt, ad_len, ad);
EAX_ENCRYPT(&self->enc_ctx.aes256_eax.ctx, aes256_encrypt, src_len,
(uint8_t*)dst->data + dst->len, src);
dst->len += src_len;
EAX_DIGEST(&self->enc_ctx.aes256_eax.ctx, aes256_encrypt, 16, mac);
return true;
}
static ssize_t crypto_cipher_aes256_eax_decrypt(struct crypto_cipher* self,
uint8_t* dst, uint8_t* mac, const uint8_t* src, size_t len,
const uint8_t* ad, size_t ad_len)
{
crypto_aes256_eax_update_nonce(&self->dec_ctx.aes256_eax);
EAX_UPDATE(&self->dec_ctx.aes256_eax.ctx, aes256_encrypt, ad_len, ad);
EAX_DECRYPT(&self->dec_ctx.aes256_eax.ctx, aes256_encrypt, len, dst, src);
EAX_DIGEST(&self->dec_ctx.aes256_eax.ctx, aes256_encrypt, 16, mac);
return len;
}
static struct crypto_cipher* crypto_cipher_new_aes256_eax(const uint8_t* enc_key,
const uint8_t* dec_key)
{
struct crypto_cipher* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
EAX_SET_KEY(&self->enc_ctx.aes256_eax.ctx, aes256_set_encrypt_key,
aes256_encrypt, enc_key);
EAX_SET_KEY(&self->dec_ctx.aes256_eax.ctx, aes256_set_encrypt_key,
aes256_encrypt, dec_key);
self->encrypt = crypto_cipher_aes256_eax_encrypt;
self->decrypt = crypto_cipher_aes256_eax_decrypt;
return self;
}
struct crypto_cipher* crypto_cipher_new(const uint8_t* enc_key,
const uint8_t* dec_key, enum crypto_cipher_type type)
{
switch (type) {
case CRYPTO_CIPHER_AES128_ECB:
return crypto_cipher_new_aes128_ecb(enc_key, dec_key);
case CRYPTO_CIPHER_AES_EAX:
return crypto_cipher_new_aes_eax(enc_key, dec_key);
case CRYPTO_CIPHER_AES256_EAX:
return crypto_cipher_new_aes256_eax(enc_key, dec_key);
case CRYPTO_CIPHER_INVALID:
break;
}
nvnc_log(NVNC_LOG_PANIC, "Invalid type: %d", type);
return NULL;
}
void crypto_cipher_del(struct crypto_cipher* self)
{
free(self);
}
bool crypto_cipher_encrypt(struct crypto_cipher* self, struct vec* dst,
uint8_t* mac, const uint8_t* src, size_t src_len,
const uint8_t* ad, size_t ad_len)
{
return self->encrypt(self, dst, mac, src, src_len, ad, ad_len);
}
ssize_t crypto_cipher_decrypt(struct crypto_cipher* self, uint8_t* dst,
uint8_t* mac, const uint8_t* src, size_t src_len,
const uint8_t* ad, size_t ad_len)
{
return self->decrypt(self, dst, mac, src, src_len, ad, ad_len);
}
struct crypto_hash* crypto_hash_new(enum crypto_hash_type type)
{
struct crypto_hash* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
switch (type) {
case CRYPTO_HASH_INVALID:
nvnc_log(NVNC_LOG_PANIC, "Invalid hash type");
break;
case CRYPTO_HASH_MD5:
md5_init(&self->ctx.md5);
self->update = (void*)nettle_md5_update;
self->digest = (void*)nettle_md5_digest;
break;
case CRYPTO_HASH_SHA1:
sha1_init(&self->ctx.sha1);
self->update = (void*)nettle_sha1_update;
self->digest = (void*)nettle_sha1_digest;
break;
case CRYPTO_HASH_SHA256:
sha256_init(&self->ctx.sha256);
self->update = (void*)nettle_sha256_update;
self->digest = (void*)nettle_sha256_digest;
break;
}
return self;
}
void crypto_hash_del(struct crypto_hash* self)
{
free(self);
}
void crypto_hash_append(struct crypto_hash* self, const uint8_t* src,
size_t len)
{
self->update(&self->ctx, len, src);
}
void crypto_hash_digest(struct crypto_hash* self, uint8_t* dst, size_t len)
{
self->digest(&self->ctx, len, dst);
}
void crypto_hash_one(uint8_t* dst, size_t dst_len, enum crypto_hash_type type,
const uint8_t* src, size_t src_len)
{
struct crypto_hash *hash = crypto_hash_new(type);
crypto_hash_append(hash, src, src_len);
crypto_hash_digest(hash, dst, dst_len);
crypto_hash_del(hash);
}
void crypto_hash_many(uint8_t* dst, size_t dst_len, enum crypto_hash_type type,
const struct crypto_data_entry *src)
{
struct crypto_hash *hash = crypto_hash_new(type);
for (int i = 0; src[i].data && src[i].len; ++i)
crypto_hash_append(hash, src[i].data, src[i].len);
crypto_hash_digest(hash, dst, dst_len);
crypto_hash_del(hash);
}
struct crypto_rsa_pub_key *crypto_rsa_pub_key_new(void)
{
struct crypto_rsa_pub_key* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
rsa_public_key_init(&self->key);
return self;
}
void crypto_rsa_pub_key_del(struct crypto_rsa_pub_key* self)
{
if (!self)
return;
rsa_public_key_clear(&self->key);
free(self);
}
struct crypto_rsa_pub_key* crypto_rsa_pub_key_import(const uint8_t* modulus,
const uint8_t* exponent, size_t size)
{
struct crypto_rsa_pub_key* self = crypto_rsa_pub_key_new();
if (!self)
return NULL;
rsa_public_key_init(&self->key);
mpz_init(self->key.n);
crypto_import(self->key.n, modulus, size);
mpz_init(self->key.e);
crypto_import(self->key.e, exponent, size);
rsa_public_key_prepare(&self->key);
return self;
}
bool crypto_rsa_priv_key_import_pkcs1_der(struct crypto_rsa_priv_key* priv,
struct crypto_rsa_pub_key* pub, const uint8_t* key,
size_t size)
{
return rsa_keypair_from_der(&pub->key, &priv->key, 0, size, key);
}
bool crypto_rsa_priv_key_load(struct crypto_rsa_priv_key* priv,
struct crypto_rsa_pub_key* pub, const char* path)
{
FILE* stream = fopen(path, "r");
if (!stream) {
nvnc_log(NVNC_LOG_ERROR, "Could not open file: %m");
return false;
}
char* line = NULL;
size_t n = 0;
if (getline(&line, &n, stream) < 0) {
nvnc_log(NVNC_LOG_ERROR, "RSA private key file is not PEM");
return false;
}
char head[128];
strncpy(head, line, sizeof(head));
head[sizeof(head) - 1] = '\0';
char* end = strchr(head, '\n');
if (end)
*end = '\0';
nvnc_trace("Read PEM head: \"%s\"\n", head);
struct vec base64_der;
vec_init(&base64_der, 4096);
while (getline(&line, &n, stream) >= 0) {
if (strncmp(line, "-----END", 8) == 0)
break;
vec_append(&base64_der, line, strcspn(line, "\n"));
}
free(line);
fclose(stream);
vec_append_zero(&base64_der, 1);
uint8_t* der = malloc(BASE64_DECODED_MAX_SIZE(base64_der.len));
assert(der);
ssize_t der_len = base64_decode(der, base64_der.data);
vec_destroy(&base64_der);
if (der_len < 0) {
free(der);
return false;
}
bool ok = false;
if (strcmp(head, "-----BEGIN RSA PRIVATE KEY-----") == 0) {
ok = crypto_rsa_priv_key_import_pkcs1_der(priv, pub, der, der_len);
} else {
nvnc_log(NVNC_LOG_ERROR, "Unsupported RSA private key format");
}
nvnc_trace("Private key is %d bits long", priv->key.size * 8);
free(der);
return ok;
}
void crypto_rsa_pub_key_modulus(const struct crypto_rsa_pub_key* key,
uint8_t* dst, size_t dst_size)
{
crypto_export(dst, dst_size, key->key.n);
}
void crypto_rsa_pub_key_exponent(const struct crypto_rsa_pub_key* key,
uint8_t* dst, size_t dst_size)
{
char* str = mpz_get_str(NULL, 16, key->key.e);
free(str);
crypto_export(dst, dst_size, key->key.e);
}
struct crypto_rsa_priv_key *crypto_rsa_priv_key_new(void)
{
struct crypto_rsa_priv_key* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
rsa_private_key_init(&self->key);
return self;
}
void crypto_rsa_priv_key_del(struct crypto_rsa_priv_key* self)
{
if (!self)
return;
rsa_private_key_clear(&self->key);
free(self);
}
size_t crypto_rsa_pub_key_length(const struct crypto_rsa_pub_key* key)
{
return key->key.size;
}
static void generate_random_for_rsa(void* random_ctx, size_t len, uint8_t* dst)
{
getrandom(dst, len, 0);
}
bool crypto_rsa_keygen(struct crypto_rsa_pub_key* pub,
struct crypto_rsa_priv_key* priv)
{
void* random_ctx = NULL;
nettle_random_func* random_func = generate_random_for_rsa;
void* progress_ctx = NULL;
nettle_progress_func* progress = NULL;
int rc = rsa_generate_keypair(&pub->key, &priv->key, random_ctx,
random_func, progress_ctx, progress, 2048, 30);
return rc != 0;
}
ssize_t crypto_rsa_encrypt(struct crypto_rsa_pub_key* pub, uint8_t* dst,
size_t dst_size, const uint8_t* src, size_t src_size)
{
mpz_t ciphertext;
mpz_init(ciphertext);
int r = rsa_encrypt(&pub->key, NULL, generate_random_for_rsa,
src_size, src, ciphertext);
if (r == 0) {
mpz_clear(ciphertext);
return -1;
}
size_t len = crypto_export(dst, dst_size, ciphertext);
mpz_clear(ciphertext);
return len;
}
ssize_t crypto_rsa_decrypt(struct crypto_rsa_priv_key* priv, uint8_t* dst,
size_t dst_size, const uint8_t* src, size_t src_size)
{
mpz_t ciphertext;
mpz_init(ciphertext);
crypto_import(ciphertext, src, src_size);
int r = rsa_decrypt(&priv->key, &dst_size, dst, ciphertext);
mpz_clear(ciphertext);
return r != 0 ? (ssize_t)dst_size : -1;
}

126
src/cursor.c 100644
View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "cursor.h"
#include "fb.h"
#include "pixels.h"
#include "rfb-proto.h"
#include "vec.h"
#include "enc-util.h"
#include "resampler.h"
#include "transform-util.h"
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
static struct nvnc_fb* apply_transform(struct nvnc_fb* fb)
{
if (fb->transform == NVNC_TRANSFORM_NORMAL) {
nvnc_fb_ref(fb);
return fb;
}
uint32_t width = fb->width;
uint32_t height = fb->height;
nvnc_transform_dimensions(fb->transform, &width, &height);
struct nvnc_fb* dst = nvnc_fb_new(width, height, fb->fourcc_format,
width);
assert(dst);
// TODO: Don't assume bpp
memset(dst->addr, 0, width * height * 4);
resample_now(dst, fb, NULL);
return dst;
}
int cursor_encode(struct vec* dst, struct rfb_pixel_format* pixfmt,
struct nvnc_fb* image, uint32_t width, uint32_t height,
uint32_t hotspot_x, uint32_t hotspot_y)
{
int rc = -1;
// Empty cursor
if (!image)
return encode_rect_head(dst, RFB_ENCODING_CURSOR, 0, 0, 0, 0);
nvnc_transform_dimensions(image->transform, &width, &height);
nvnc_transform_dimensions(image->transform, &hotspot_x, &hotspot_y);
if (nvnc_fb_map(image) < 0)
goto failure;
image = apply_transform(image);
assert(width <= image->width);
assert(height <= image->height);
struct rfb_pixel_format srcfmt = { 0 };
rc = rfb_pixfmt_from_fourcc(&srcfmt, image->fourcc_format);
if (rc < 0)
goto failure;
rc = encode_rect_head(dst, RFB_ENCODING_CURSOR, hotspot_x, hotspot_y,
width, height);
if (rc < 0)
goto failure;
int bpp = pixfmt->bits_per_pixel / 8;
size_t size = width * height;
rc = vec_reserve(dst, dst->len + size * bpp + UDIV_UP(size, 8));
if (rc < 0)
goto failure;
uint8_t* dstdata = dst->data;
dstdata += dst->len;
int32_t src_byte_stride = image->stride * (srcfmt.bits_per_pixel / 8);
if((int32_t)width == image->stride) {
pixel_to_cpixel(dstdata, pixfmt, image->addr, &srcfmt, bpp, size);
} else {
for (uint32_t y = 0; y < height; ++y) {
pixel_to_cpixel(dstdata + y * bpp * width, pixfmt,
(uint8_t*)image->addr + y * src_byte_stride,
&srcfmt, bpp, width);
}
}
dst->len += size * bpp;
dstdata = dst->data;
dstdata += dst->len;
for (uint32_t y = 0; y < height; ++y) {
if (!extract_alpha_mask(dstdata + y * UDIV_UP(width, 8),
(uint32_t*)image->addr + y * image->stride,
image->fourcc_format, width))
goto failure;
dst->len += UDIV_UP(width, 8);
}
rc = 0;
failure:
nvnc_fb_unref(image);
return rc;
}

View File

@ -0,0 +1,161 @@
/*
* Copyright (c) 2020 - 2021 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <assert.h>
#include <pixman.h>
#include <sys/param.h>
#include "fb.h"
#include "pixels.h"
#include "damage-refinery.h"
#define XXH_STATIC_LINKING_ONLY
#define XXH_IMPLEMENTATION
#define XXH_VECTOR XXH_SCALAR
#include "xxhash.h"
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
#define HASH_SEED 0
int damage_refinery_init(struct damage_refinery* self, uint32_t width,
uint32_t height)
{
self->width = width;
self->height = height;
uint32_t twidth = UDIV_UP(width, 32);
uint32_t theight = UDIV_UP(height, 32);
self->state = XXH3_createState();
if (!self->state)
return -1;
self->hashes = calloc(twidth * theight, sizeof(*self->hashes));
if (!self->hashes) {
XXH3_freeState(self->state);
return -1;
}
return 0;
}
int damage_refinery_resize(struct damage_refinery* self, uint32_t width,
uint32_t height)
{
if (width == self->width && height == self->height)
return 0;
damage_refinery_destroy(self);
return damage_refinery_init(self, width, height);
}
void damage_refinery_destroy(struct damage_refinery* self)
{
XXH3_freeState(self->state);
free(self->hashes);
}
static uint32_t damage_hash_tile(struct damage_refinery* self, uint32_t tx,
uint32_t ty, const struct nvnc_fb* buffer)
{
uint8_t* pixels = buffer->addr;
int bpp = pixel_size_from_fourcc(buffer->fourcc_format);
int byte_stride = buffer->stride * bpp;
int x_start = tx * 32;
int x_stop = MIN((tx + 1) * 32, self->width);
int y_start = ty * 32;
int y_stop = MIN((ty + 1) * 32, self->height);
int32_t xoff = x_start * bpp;
XXH3_64bits_reset(self->state);
for (int y = y_start; y < y_stop; ++y) {
XXH3_64bits_update(self->state, pixels + xoff + y * byte_stride,
bpp * (x_stop - x_start));
}
return XXH3_64bits_digest(self->state);
}
static uint32_t* damage_tile_hash_ptr(struct damage_refinery* self,
uint32_t tx, uint32_t ty)
{
uint32_t twidth = UDIV_UP(self->width, 32);
return &self->hashes[tx + ty * twidth];
}
static void damage_refine_tile(struct damage_refinery* self,
struct pixman_region16* refined, uint32_t tx, uint32_t ty,
const struct nvnc_fb* buffer)
{
uint32_t hash = damage_hash_tile(self, tx, ty, buffer);
uint32_t* old_hash_ptr = damage_tile_hash_ptr(self, tx, ty);
int is_damaged = hash != *old_hash_ptr;
*old_hash_ptr = hash;
if (is_damaged)
pixman_region_union_rect(refined, refined, tx * 32, ty * 32, 32,
32);
}
static void tile_region_from_region(struct pixman_region16* dst,
struct pixman_region16* src)
{
int n_rects = 0;
struct pixman_box16* rects = pixman_region_rectangles(src, &n_rects);
for (int i = 0; i < n_rects; ++i) {
int x1 = rects[i].x1 / 32;
int y1 = rects[i].y1 / 32;
int x2 = UDIV_UP(rects[i].x2, 32);
int y2 = UDIV_UP(rects[i].y2, 32);
pixman_region_union_rect(dst, dst, x1, y1, x2 - x1, y2 - y1);
}
}
void damage_refine(struct damage_refinery* self,
struct pixman_region16* refined,
struct pixman_region16* hint,
struct nvnc_fb* buffer)
{
assert(self->width == (uint32_t)buffer->width &&
self->height == (uint32_t)buffer->height);
nvnc_fb_map(buffer);
struct pixman_region16 tile_region;
pixman_region_init(&tile_region);
tile_region_from_region(&tile_region, hint);
int n_rects = 0;
struct pixman_box16* rects = pixman_region_rectangles(&tile_region,
&n_rects);
for (int i = 0; i < n_rects; ++i)
for (int ty = rects[i].y1; ty < rects[i].y2; ++ty)
for (int tx = rects[i].x1; tx < rects[i].x2; ++tx)
damage_refine_tile(self, refined, tx, ty, buffer);
pixman_region_fini(&tile_region);
pixman_region_intersect_rect(refined, refined, 0, 0, self->width,
self->height);
}

View File

@ -1,155 +0,0 @@
#include "neatvnc.h"
#include "fb.h"
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <pixman.h>
#include <string.h>
#include <sys/param.h>
#include <libdrm/drm_fourcc.h>
#include <uv.h>
#include <assert.h>
#define EXPORT __attribute__((visibility("default")))
#define ALIGN_DOWN(a, b) (((a) / (b)) * (b))
struct damage_check {
uv_work_t work;
const struct nvnc_fb* fb0;
const struct nvnc_fb* fb1;
int x_hint;
int y_hint;
int width_hint;
int height_hint;
nvnc_damage_fn on_done;
struct pixman_region16 damage;
void* userdata;
};
static bool fbs_are_compatible(const struct nvnc_fb* fb0,
const struct nvnc_fb* fb1)
{
return fb0->fourcc_format == fb1->fourcc_format &&
fb0->width == fb1->width && fb0->height == fb1->height;
}
static inline bool are_tiles_equal(const uint32_t* a, const uint32_t* b,
int stride, int width, int height)
{
for (int y = 0; y < height; ++y)
if (memcmp(a + y * stride, b + y * stride, width * 4) != 0)
return false;
return true;
}
#define TILE_SIDE_LENGTH 32
int check_damage_linear(struct pixman_region16* damage,
const struct nvnc_fb* fb0, const struct nvnc_fb* fb1,
int x_hint, int y_hint, int width_hint, int height_hint)
{
uint32_t* b0 = fb0->addr;
uint32_t* b1 = fb1->addr;
int width = fb0->width;
int height = fb0->height;
assert(x_hint + width_hint <= width);
assert(y_hint + height_hint <= height);
int x_start = ALIGN_DOWN(x_hint, TILE_SIDE_LENGTH);
int y_start = ALIGN_DOWN(y_hint, TILE_SIDE_LENGTH);
width_hint += x_hint - x_start;
height_hint += y_hint - y_start;
for (int y = y_start; y < y_start + height_hint; y += TILE_SIDE_LENGTH) {
int tile_height = MIN(TILE_SIDE_LENGTH, height - y);
for (int x = x_start; x < x_start + width_hint;
x += TILE_SIDE_LENGTH) {
int tile_width = MIN(TILE_SIDE_LENGTH, width - x);
int offset = x + y * width;
if (are_tiles_equal(b0 + offset, b1 + offset, width,
tile_width, tile_height))
continue;
pixman_region_union_rect(damage, damage, x, y,
tile_width, tile_height);
}
}
return 0;
}
#undef TILE_SIDE_LENGTH
void do_damage_check_linear(uv_work_t* work)
{
struct damage_check* check = (void*)work;
check_damage_linear(&check->damage, check->fb0, check->fb1,
check->x_hint, check->y_hint, check->width_hint,
check->height_hint);
}
void on_damage_check_done_linear(uv_work_t* work, int status)
{
(void)status;
struct damage_check* check = (void*)work;
check->on_done(&check->damage, check->userdata);
pixman_region_fini(&check->damage);
free(check);
}
int check_damage_linear_threaded(const struct nvnc_fb* fb0,
const struct nvnc_fb* fb1, int x_hint,
int y_hint, int width_hint, int height_hint,
nvnc_damage_fn on_check_done, void* userdata)
{
struct damage_check* work = calloc(1, sizeof(*work));
if (!work)
return -1;
work->on_done = on_check_done;
work->userdata = userdata;
work->fb0 = fb0;
work->fb1 = fb1;
work->x_hint = x_hint;
work->y_hint = y_hint;
work->width_hint = width_hint;
work->height_hint = height_hint;
pixman_region_init(&work->damage);
/* TODO: Spread the work into more tasks */
int rc = uv_queue_work(uv_default_loop(), &work->work,
do_damage_check_linear,
on_damage_check_done_linear);
if (rc < 0)
free(work);
return rc;
}
EXPORT
int nvnc_check_damage(const struct nvnc_fb* fb0, const struct nvnc_fb* fb1,
int x_hint, int y_hint, int width_hint, int height_hint,
nvnc_damage_fn on_check_done, void* userdata)
{
if (!fbs_are_compatible(fb0, fb1))
return -1;
switch (fb0->fourcc_modifier) {
case DRM_FORMAT_MOD_LINEAR:
return check_damage_linear_threaded(fb0, fb1, x_hint, y_hint,
width_hint, height_hint,
on_check_done, userdata);
}
return -1;
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2023 Philipp Zabel
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "desktop-layout.h"
#include "neatvnc.h"
#include "rfb-proto.h"
#define EXPORT __attribute__((visibility("default")))
void nvnc_display_layout_init(
struct nvnc_display_layout* display, struct rfb_screen* screen)
{
display->display = NULL;
display->id = ntohl(screen->id);
display->x_pos = ntohs(screen->x);
display->y_pos = ntohs(screen->y);
display->width = ntohs(screen->width);
display->height = ntohs(screen->height);
}
EXPORT
uint16_t nvnc_desktop_layout_get_width(const struct nvnc_desktop_layout* layout)
{
return layout->width;
}
EXPORT
uint16_t nvnc_desktop_layout_get_height(const struct nvnc_desktop_layout* layout)
{
return layout->height;
}
EXPORT
uint8_t nvnc_desktop_layout_get_display_count(
const struct nvnc_desktop_layout* layout)
{
return layout->n_display_layouts;
}
EXPORT
uint16_t nvnc_desktop_layout_get_display_x_pos(
const struct nvnc_desktop_layout* layout, uint8_t display_index)
{
if (display_index >= layout->n_display_layouts)
return 0;
return layout->display_layouts[display_index].x_pos;
}
EXPORT
uint16_t nvnc_desktop_layout_get_display_y_pos(
const struct nvnc_desktop_layout* layout, uint8_t display_index)
{
if (display_index >= layout->n_display_layouts)
return 0;
return layout->display_layouts[display_index].y_pos;
}
EXPORT
uint16_t nvnc_desktop_layout_get_display_width(
const struct nvnc_desktop_layout* layout, uint8_t display_index)
{
if (display_index >= layout->n_display_layouts)
return 0;
return layout->display_layouts[display_index].width;
}
EXPORT
uint16_t nvnc_desktop_layout_get_display_height(
const struct nvnc_desktop_layout* layout, uint8_t display_index)
{
if (display_index >= layout->n_display_layouts)
return 0;
return layout->display_layouts[display_index].height;
}
EXPORT
struct nvnc_display* nvnc_desktop_layout_get_display(
const struct nvnc_desktop_layout* layout, uint8_t display_index)
{
if (display_index >= layout->n_display_layouts)
return NULL;
return layout->display_layouts[display_index].display;
}

146
src/display.c 100644
View File

@ -0,0 +1,146 @@
/*
* Copyright (c) 2020 - 2021 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "display.h"
#include "neatvnc.h"
#include "common.h"
#include "fb.h"
#include "resampler.h"
#include "transform-util.h"
#include "encoder.h"
#include "usdt.h"
#include <assert.h>
#include <stdlib.h>
#define EXPORT __attribute__((visibility("default")))
static void nvnc_display__on_resampler_done(struct nvnc_fb* fb,
struct pixman_region16* damage, void* userdata)
{
struct nvnc_display* self = userdata;
DTRACE_PROBE2(neatvnc, nvnc_display__on_resampler_done, self, fb->pts);
if (self->buffer) {
nvnc_fb_release(self->buffer);
nvnc_fb_unref(self->buffer);
}
self->buffer = fb;
nvnc_fb_ref(fb);
nvnc_fb_hold(fb);
assert(self->server);
// TODO: Shift according to display position
nvnc__damage_region(self->server, damage);
}
EXPORT
struct nvnc_display* nvnc_display_new(uint16_t x_pos, uint16_t y_pos)
{
struct nvnc_display* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->resampler = resampler_create();
if (!self->resampler)
goto resampler_failure;
if (damage_refinery_init(&self->damage_refinery, 0, 0) < 0)
goto refinery_failure;
self->ref = 1;
self->x_pos = x_pos;
self->y_pos = y_pos;
return self;
refinery_failure:
resampler_destroy(self->resampler);
resampler_failure:
free(self);
return NULL;
}
static void nvnc__display_free(struct nvnc_display* self)
{
if (self->buffer) {
nvnc_fb_release(self->buffer);
nvnc_fb_unref(self->buffer);
}
damage_refinery_destroy(&self->damage_refinery);
resampler_destroy(self->resampler);
free(self);
}
EXPORT
void nvnc_display_ref(struct nvnc_display* self)
{
self->ref++;
}
EXPORT
void nvnc_display_unref(struct nvnc_display* self)
{
if (--self->ref == 0)
nvnc__display_free(self);
}
EXPORT
struct nvnc* nvnc_display_get_server(const struct nvnc_display* self)
{
return self->server;
}
EXPORT
void nvnc_display_feed_buffer(struct nvnc_display* self, struct nvnc_fb* fb,
struct pixman_region16* damage)
{
DTRACE_PROBE2(neatvnc, nvnc_display_feed_buffer, self, fb->pts);
struct nvnc* server = self->server;
assert(server);
struct pixman_region16 refined_damage;
pixman_region_init(&refined_damage);
if (server->n_damage_clients != 0) {
damage_refinery_resize(&self->damage_refinery, fb->width,
fb->height);
// TODO: Run the refinery in a worker thread?
damage_refine(&self->damage_refinery, &refined_damage, damage, fb);
damage = &refined_damage;
} else {
// Resizing to zero causes the damage refinery to be reset when
// it's needed.
damage_refinery_resize(&self->damage_refinery, 0, 0);
}
struct pixman_region16 transformed_damage;
pixman_region_init(&transformed_damage);
nvnc_transform_region(&transformed_damage, damage, fb->transform,
fb->width, fb->height);
resampler_feed(self->resampler, fb, &transformed_damage,
nvnc_display__on_resampler_done, self);
pixman_region_fini(&transformed_damage);
pixman_region_fini(&refined_damage);
}

62
src/enc-util.c 100644
View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2019 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "enc-util.h"
#include "rfb-proto.h"
#include "vec.h"
#include <arpa/inet.h>
#include <stdint.h>
#include <pixman.h>
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
int encode_rect_head(struct vec* dst, enum rfb_encodings encoding,
uint32_t x, uint32_t y, uint32_t width, uint32_t height)
{
struct rfb_server_fb_rect head = {
.encoding = htonl(encoding),
.x = htons(x),
.y = htons(y),
.width = htons(width),
.height = htons(height),
};
return vec_append(dst, &head, sizeof(head));
}
uint32_t calc_bytes_per_cpixel(const struct rfb_pixel_format* fmt)
{
return fmt->bits_per_pixel == 32 ? UDIV_UP(fmt->depth, 8)
: UDIV_UP(fmt->bits_per_pixel, 8);
}
uint32_t calculate_region_area(struct pixman_region16* region)
{
uint32_t area = 0;
int n_rects = 0;
struct pixman_box16* rects = pixman_region_rectangles(region,
&n_rects);
for (int i = 0; i < n_rects; ++i) {
int width = rects[i].x2 - rects[i].x1;
int height = rects[i].y2 - rects[i].y1;
area += width * height;
}
return area;
}

132
src/encoder.c 100644
View File

@ -0,0 +1,132 @@
/*
* Copyright (c) 2021 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "encoder.h"
#include "config.h"
#include <stdlib.h>
#include <assert.h>
struct encoder* raw_encoder_new(void);
struct encoder* zrle_encoder_new(void);
struct encoder* tight_encoder_new(uint16_t width, uint16_t height);
#ifdef ENABLE_OPEN_H264
struct encoder* open_h264_new(void);
#endif
extern struct encoder_impl encoder_impl_raw;
extern struct encoder_impl encoder_impl_zrle;
extern struct encoder_impl encoder_impl_tight;
#ifdef ENABLE_OPEN_H264
extern struct encoder_impl encoder_impl_open_h264;
#endif
struct encoder* encoder_new(enum rfb_encodings type, uint16_t width,
uint16_t height)
{
switch (type) {
case RFB_ENCODING_RAW: return raw_encoder_new();
case RFB_ENCODING_ZRLE: return zrle_encoder_new();
case RFB_ENCODING_TIGHT: return tight_encoder_new(width, height);
#ifdef ENABLE_OPEN_H264
case RFB_ENCODING_OPEN_H264: return open_h264_new();
#endif
default: break;
}
return NULL;
}
void encoder_init(struct encoder* self, struct encoder_impl* impl)
{
self->ref = 1;
self->impl = impl;
}
enum rfb_encodings encoder_get_type(const struct encoder* self)
{
if (self->impl == &encoder_impl_raw)
return RFB_ENCODING_RAW;
if (self->impl == &encoder_impl_zrle)
return RFB_ENCODING_ZRLE;
if (self->impl == &encoder_impl_tight)
return RFB_ENCODING_TIGHT;
#ifdef ENABLE_OPEN_H264
if (self->impl == &encoder_impl_open_h264)
return RFB_ENCODING_OPEN_H264;
#endif
abort();
return 0;
}
void encoder_ref(struct encoder* self)
{
assert(self->ref > 0);
self->ref++;
}
void encoder_unref(struct encoder* self)
{
if (!self)
return;
if (--self->ref != 0)
return;
if (self->impl->destroy)
self->impl->destroy(self);
}
void encoder_set_output_format(struct encoder* self,
const struct rfb_pixel_format* pixfmt)
{
if (self->impl->set_output_format)
self->impl->set_output_format(self, pixfmt);
}
void encoder_set_quality(struct encoder* self, int value)
{
if (self->impl->set_quality)
self->impl->set_quality(self, value);
}
int encoder_resize(struct encoder* self, uint16_t width, uint16_t height)
{
if (self->impl->resize)
return self->impl->resize(self, width, height);
return 0;
}
int encoder_encode(struct encoder* self, struct nvnc_fb* fb,
struct pixman_region16* damage)
{
assert(self->impl->encode);
return self->impl->encode(self, fb, damage);
}
void encoder_request_key_frame(struct encoder* self)
{
if (self->impl->request_key_frame)
return self->impl->request_key_frame(self);
}
void encoder_finish_frame(struct encoder* self, struct rcbuf* result,
uint64_t pts)
{
if (self->on_done)
self->on_done(self, result, pts);
}

220
src/fb.c
View File

@ -1,30 +1,61 @@
/*
* Copyright (c) 2019 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "fb.h"
#include "pixels.h"
#include "neatvnc.h"
#include <stdlib.h>
#include <unistd.h>
#include <sys/param.h>
#include <stdatomic.h>
#include "config.h"
#ifdef HAVE_GBM
#include <gbm.h>
#endif
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
#define ALIGN_UP(n, a) (UDIV_UP(n, a) * a)
#define EXPORT __attribute__((visibility("default")))
EXPORT
struct nvnc_fb* nvnc_fb_new(uint16_t width, uint16_t height,
uint32_t fourcc_format)
uint32_t fourcc_format, uint16_t stride)
{
struct nvnc_fb* fb = calloc(1, sizeof(*fb));
if (!fb)
return NULL;
uint32_t bpp = pixel_size_from_fourcc(fourcc_format);
fb->type = NVNC_FB_SIMPLE;
fb->ref = 1;
fb->width = width;
fb->height = height;
fb->fourcc_format = fourcc_format;
fb->size = width * height * 4; /* Assume 4 byte format for now */
fb->stride = stride;
fb->pts = NVNC_NO_PTS;
/* fb could be allocated in single allocation, but I want to reserve
* the possiblity to create an fb with a pixel buffer passed from the
* user.
*/
fb->addr = malloc(fb->size);
size_t size = height * stride * bpp;
size_t alignment = MAX(4, sizeof(void*));
size_t aligned_size = ALIGN_UP(size, alignment);
fb->addr = aligned_alloc(alignment, aligned_size);
if (!fb->addr) {
free(fb);
fb = NULL;
@ -33,6 +64,51 @@ struct nvnc_fb* nvnc_fb_new(uint16_t width, uint16_t height,
return fb;
}
EXPORT
struct nvnc_fb* nvnc_fb_from_buffer(void* buffer, uint16_t width, uint16_t height,
uint32_t fourcc_format, int32_t stride)
{
struct nvnc_fb* fb = calloc(1, sizeof(*fb));
if (!fb)
return NULL;
fb->type = NVNC_FB_SIMPLE;
fb->ref = 1;
fb->addr = buffer;
fb->is_external = true;
fb->width = width;
fb->height = height;
fb->fourcc_format = fourcc_format;
fb->stride = stride;
fb->pts = NVNC_NO_PTS;
return fb;
}
EXPORT
struct nvnc_fb* nvnc_fb_from_gbm_bo(struct gbm_bo* bo)
{
#ifdef HAVE_GBM
struct nvnc_fb* fb = calloc(1, sizeof(*fb));
if (!fb)
return NULL;
fb->type = NVNC_FB_GBM_BO;
fb->ref = 1;
fb->is_external = true;
fb->width = gbm_bo_get_width(bo);
fb->height = gbm_bo_get_height(bo);
fb->fourcc_format = gbm_bo_get_format(bo);
fb->bo = bo;
fb->pts = NVNC_NO_PTS;
return fb;
#else
nvnc_log(NVNC_LOG_ERROR, "nvnc_fb_from_gbm_bo was not enabled during build time");
return NULL;
#endif
}
EXPORT
void* nvnc_fb_get_addr(const struct nvnc_fb* fb)
{
@ -57,9 +133,66 @@ uint32_t nvnc_fb_get_fourcc_format(const struct nvnc_fb* fb)
return fb->fourcc_format;
}
void nvnc__fb_free(struct nvnc_fb* fb)
EXPORT
int32_t nvnc_fb_get_stride(const struct nvnc_fb* fb)
{
return fb->stride;
}
EXPORT
int nvnc_fb_get_pixel_size(const struct nvnc_fb* fb)
{
return pixel_size_from_fourcc(fb->fourcc_format);
}
EXPORT
struct gbm_bo* nvnc_fb_get_gbm_bo(const struct nvnc_fb* fb)
{
return fb->bo;
}
EXPORT
enum nvnc_transform nvnc_fb_get_transform(const struct nvnc_fb* fb)
{
return fb->transform;
}
EXPORT
enum nvnc_fb_type nvnc_fb_get_type(const struct nvnc_fb* fb)
{
return fb->type;
}
EXPORT
uint64_t nvnc_fb_get_pts(const struct nvnc_fb* fb)
{
return fb->pts;
}
static void nvnc__fb_free(struct nvnc_fb* fb)
{
nvnc_cleanup_fn cleanup = fb->common.cleanup_fn;
if (cleanup)
cleanup(fb->common.userdata);
nvnc_fb_unmap(fb);
if (!fb->is_external)
switch (fb->type) {
case NVNC_FB_UNSPEC:
abort();
case NVNC_FB_SIMPLE:
free(fb->addr);
break;
case NVNC_FB_GBM_BO:
#ifdef HAVE_GBM
gbm_bo_destroy(fb->bo);
#else
abort();
#endif
break;
}
free(fb);
}
@ -75,3 +208,74 @@ void nvnc_fb_unref(struct nvnc_fb* fb)
if (--fb->ref == 0)
nvnc__fb_free(fb);
}
EXPORT
void nvnc_fb_set_release_fn(struct nvnc_fb* fb, nvnc_fb_release_fn fn, void* context)
{
fb->on_release = fn;
fb->release_context = context;
}
EXPORT
void nvnc_fb_set_transform(struct nvnc_fb* fb, enum nvnc_transform transform)
{
fb->transform = transform;
}
EXPORT
void nvnc_fb_set_pts(struct nvnc_fb* fb, uint64_t pts)
{
fb->pts = pts;
}
void nvnc_fb_hold(struct nvnc_fb* fb)
{
fb->hold_count++;
}
void nvnc_fb_release(struct nvnc_fb* fb)
{
if (--fb->hold_count != 0)
return;
nvnc_fb_unmap(fb);
fb->pts = NVNC_NO_PTS;
if (fb->on_release)
fb->on_release(fb, fb->release_context);
}
int nvnc_fb_map(struct nvnc_fb* fb)
{
#ifdef HAVE_GBM
if (fb->type != NVNC_FB_GBM_BO || fb->bo_map_handle)
return 0;
uint32_t stride = 0;
fb->addr = gbm_bo_map(fb->bo, 0, 0, fb->width, fb->height,
GBM_BO_TRANSFER_READ, &stride, &fb->bo_map_handle);
fb->stride = stride / nvnc_fb_get_pixel_size(fb);
if (fb->addr)
return 0;
fb->bo_map_handle = NULL;
return -1;
#else
return 0;
#endif
}
void nvnc_fb_unmap(struct nvnc_fb* fb)
{
#ifdef HAVE_GBM
if (fb->type != NVNC_FB_GBM_BO)
return;
if (fb->bo_map_handle)
gbm_bo_unmap(fb->bo, fb->bo_map_handle);
fb->bo_map_handle = NULL;
fb->addr = NULL;
fb->stride = 0;
#endif
}

179
src/fb_pool.c 100644
View File

@ -0,0 +1,179 @@
/*
* Copyright (c) 2021 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "fb.h"
#include "neatvnc.h"
#include "sys/queue.h"
#include <stdlib.h>
#include <assert.h>
#define EXPORT __attribute__((visibility("default")))
struct fbq_item {
struct nvnc_fb* fb;
TAILQ_ENTRY(fbq_item) link;
};
TAILQ_HEAD(fbq, fbq_item);
struct nvnc_fb_pool {
int ref;
struct fbq fbs;
uint16_t width;
uint16_t height;
int32_t stride;
uint32_t fourcc_format;
nvnc_fb_alloc_fn alloc_fn;
};
EXPORT
struct nvnc_fb_pool* nvnc_fb_pool_new(uint16_t width, uint16_t height,
uint32_t fourcc_format, uint16_t stride)
{
struct nvnc_fb_pool* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->ref = 1;
TAILQ_INIT(&self->fbs);
self->width = width;
self->height = height;
self->stride = stride;
self->fourcc_format = fourcc_format;
self->alloc_fn = nvnc_fb_new;
return self;
}
static void nvnc_fb_pool__destroy_fbs(struct nvnc_fb_pool* self)
{
while (!TAILQ_EMPTY(&self->fbs)) {
struct fbq_item* item = TAILQ_FIRST(&self->fbs);
TAILQ_REMOVE(&self->fbs, item, link);
nvnc_fb_unref(item->fb);
free(item);
}
}
static void nvnc_fb_pool__destroy(struct nvnc_fb_pool* self)
{
nvnc_fb_pool__destroy_fbs(self);
free(self);
}
EXPORT
bool nvnc_fb_pool_resize(struct nvnc_fb_pool* self, uint16_t width,
uint16_t height, uint32_t fourcc_format, uint16_t stride)
{
if (width == self->width && height == self->height &&
fourcc_format == self->fourcc_format &&
stride == self->stride)
return false;
nvnc_fb_pool__destroy_fbs(self);
self->width = width;
self->height = height;
self->stride = stride;
self->fourcc_format = fourcc_format;
return true;
}
EXPORT
void nvnc_fb_pool_ref(struct nvnc_fb_pool* self)
{
self->ref++;
}
EXPORT
void nvnc_fb_pool_unref(struct nvnc_fb_pool* self)
{
if (--self->ref == 0)
nvnc_fb_pool__destroy(self);
}
static void nvnc_fb_pool__on_fb_release(struct nvnc_fb* fb, void* userdata)
{
struct nvnc_fb_pool* pool = userdata;
nvnc_fb_pool_release(pool, fb);
nvnc_fb_pool_unref(pool);
}
static struct nvnc_fb* nvnc_fb_pool__acquire_new(struct nvnc_fb_pool* self)
{
struct nvnc_fb* fb = self->alloc_fn(self->width, self->height,
self->fourcc_format, self->stride);
if (!fb)
return NULL;
nvnc_fb_set_release_fn(fb, nvnc_fb_pool__on_fb_release, self);
nvnc_fb_pool_ref(self);
return fb;
}
static struct nvnc_fb* nvnc_fb_pool__acquire_from_list(struct nvnc_fb_pool* self)
{
struct fbq_item* item = TAILQ_FIRST(&self->fbs);
struct nvnc_fb* fb = item->fb;
assert(item && fb);
TAILQ_REMOVE(&self->fbs, item, link);
free(item);
nvnc_fb_pool_ref(self);
return fb;
}
EXPORT
struct nvnc_fb* nvnc_fb_pool_acquire(struct nvnc_fb_pool* self)
{
return TAILQ_EMPTY(&self->fbs) ?
nvnc_fb_pool__acquire_new(self) :
nvnc_fb_pool__acquire_from_list(self);
}
EXPORT
void nvnc_fb_pool_release(struct nvnc_fb_pool* self, struct nvnc_fb* fb)
{
if (fb->width != self->width || fb->height != self->height ||
fb->fourcc_format != self->fourcc_format ||
fb->stride != self->stride) {
return;
}
nvnc_fb_ref(fb);
struct fbq_item* item = calloc(1, sizeof(*item));
assert(item);
item->fb = fb;
TAILQ_INSERT_TAIL(&self->fbs, item, link);
}
EXPORT
void nvnc_fb_pool_set_alloc_fn(struct nvnc_fb_pool* self, nvnc_fb_alloc_fn fn)
{
self->alloc_fn = fn;
}

View File

@ -0,0 +1,627 @@
/*
* Copyright (c) 2021 - 2024 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "h264-encoder.h"
#include "neatvnc.h"
#include "fb.h"
#include "sys/queue.h"
#include "vec.h"
#include "usdt.h"
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <assert.h>
#include <gbm.h>
#include <xf86drm.h>
#include <aml.h>
#include <libavcodec/avcodec.h>
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_drm.h>
#include <libavutil/pixdesc.h>
#include <libavutil/dict.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libdrm/drm_fourcc.h>
struct h264_encoder;
struct fb_queue_entry {
struct nvnc_fb* fb;
TAILQ_ENTRY(fb_queue_entry) link;
};
TAILQ_HEAD(fb_queue, fb_queue_entry);
struct h264_encoder_ffmpeg {
struct h264_encoder base;
uint32_t width;
uint32_t height;
uint32_t format;
AVRational timebase;
AVRational sample_aspect_ratio;
enum AVPixelFormat av_pixel_format;
/* type: AVHWDeviceContext */
AVBufferRef* hw_device_ctx;
/* type: AVHWFramesContext */
AVBufferRef* hw_frames_ctx;
AVCodecContext* codec_ctx;
AVFilterGraph* filter_graph;
AVFilterContext* filter_in;
AVFilterContext* filter_out;
struct fb_queue fb_queue;
struct aml_work* work;
struct nvnc_fb* current_fb;
struct vec current_packet;
bool current_frame_is_keyframe;
bool please_destroy;
};
struct h264_encoder_impl h264_encoder_ffmpeg_impl;
static enum AVPixelFormat drm_to_av_pixel_format(uint32_t format)
{
switch (format) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
return AV_PIX_FMT_BGR0;
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_ABGR8888:
return AV_PIX_FMT_RGB0;
case DRM_FORMAT_RGBX8888:
case DRM_FORMAT_RGBA8888:
return AV_PIX_FMT_0BGR;
case DRM_FORMAT_BGRX8888:
case DRM_FORMAT_BGRA8888:
return AV_PIX_FMT_0RGB;
}
return AV_PIX_FMT_NONE;
}
static void hw_frame_desc_free(void* opaque, uint8_t* data)
{
struct AVDRMFrameDescriptor* desc = (void*)data;
assert(desc);
for (int i = 0; i < desc->nb_objects; ++i)
close(desc->objects[i].fd);
free(desc);
}
// TODO: Maybe do this once per frame inside nvnc_fb?
static AVFrame* fb_to_avframe(struct nvnc_fb* fb)
{
struct gbm_bo* bo = fb->bo;
int n_planes = gbm_bo_get_plane_count(bo);
AVDRMFrameDescriptor* desc = calloc(1, sizeof(*desc));
desc->nb_objects = n_planes;
desc->nb_layers = 1;
desc->layers[0].format = gbm_bo_get_format(bo);
desc->layers[0].nb_planes = n_planes;
for (int i = 0; i < n_planes; ++i) {
uint32_t stride = gbm_bo_get_stride_for_plane(bo, i);
desc->objects[i].fd = gbm_bo_get_fd_for_plane(bo, i);
desc->objects[i].size = stride * fb->height;
desc->objects[i].format_modifier = gbm_bo_get_modifier(bo);
desc->layers[0].format = gbm_bo_get_format(bo);
desc->layers[0].planes[i].object_index = i;
desc->layers[0].planes[i].offset = gbm_bo_get_offset(bo, i);
desc->layers[0].planes[i].pitch = stride;
}
AVFrame* frame = av_frame_alloc();
if (!frame) {
hw_frame_desc_free(NULL, (void*)desc);
return NULL;
}
frame->opaque = fb;
frame->width = fb->width;
frame->height = fb->height;
frame->format = AV_PIX_FMT_DRM_PRIME;
frame->sample_aspect_ratio = (AVRational){1, 1};
AVBufferRef* desc_ref = av_buffer_create((void*)desc, sizeof(*desc),
hw_frame_desc_free, NULL, 0);
if (!desc_ref) {
hw_frame_desc_free(NULL, (void*)desc);
av_frame_free(&frame);
return NULL;
}
frame->buf[0] = desc_ref;
frame->data[0] = (void*)desc_ref->data;
// TODO: Set colorspace?
return frame;
}
static struct nvnc_fb* fb_queue_dequeue(struct fb_queue* queue)
{
if (TAILQ_EMPTY(queue))
return NULL;
struct fb_queue_entry* entry = TAILQ_FIRST(queue);
TAILQ_REMOVE(queue, entry, link);
struct nvnc_fb* fb = entry->fb;
free(entry);
return fb;
}
static int fb_queue_enqueue(struct fb_queue* queue, struct nvnc_fb* fb)
{
struct fb_queue_entry* entry = calloc(1, sizeof(*entry));
if (!entry)
return -1;
entry->fb = fb;
nvnc_fb_ref(fb);
TAILQ_INSERT_TAIL(queue, entry, link);
return 0;
}
static int h264_encoder__init_buffersrc(struct h264_encoder_ffmpeg* self)
{
int rc;
/* Placeholder values are used to pacify input checking and the real
* values are set below.
*/
rc = avfilter_graph_create_filter(&self->filter_in,
avfilter_get_by_name("buffer"), "in",
"width=1:height=1:pix_fmt=drm_prime:time_base=1/1", NULL,
self->filter_graph);
if (rc != 0)
return -1;
AVBufferSrcParameters *params = av_buffersrc_parameters_alloc();
if (!params)
return -1;
params->format = AV_PIX_FMT_DRM_PRIME;
params->width = self->width;
params->height = self->height;
params->sample_aspect_ratio = self->sample_aspect_ratio;
params->time_base = self->timebase;
params->hw_frames_ctx = self->hw_frames_ctx;
rc = av_buffersrc_parameters_set(self->filter_in, params);
assert(rc == 0);
av_free(params);
return 0;
}
static int h264_encoder__init_filters(struct h264_encoder_ffmpeg* self)
{
int rc;
self->filter_graph = avfilter_graph_alloc();
if (!self->filter_graph)
return -1;
rc = h264_encoder__init_buffersrc(self);
if (rc != 0)
goto failure;
rc = avfilter_graph_create_filter(&self->filter_out,
avfilter_get_by_name("buffersink"), "out", NULL,
NULL, self->filter_graph);
if (rc != 0)
goto failure;
AVFilterInOut* inputs = avfilter_inout_alloc();
if (!inputs)
goto failure;
inputs->name = av_strdup("in");
inputs->filter_ctx = self->filter_in;
inputs->pad_idx = 0;
inputs->next = NULL;
AVFilterInOut* outputs = avfilter_inout_alloc();
if (!outputs) {
avfilter_inout_free(&inputs);
goto failure;
}
outputs->name = av_strdup("out");
outputs->filter_ctx = self->filter_out;
outputs->pad_idx = 0;
outputs->next = NULL;
rc = avfilter_graph_parse(self->filter_graph,
"hwmap=mode=direct:derive_device=vaapi"
",scale_vaapi=format=nv12:mode=fast",
outputs, inputs, NULL);
if (rc != 0)
goto failure;
assert(self->hw_device_ctx);
for (unsigned int i = 0; i < self->filter_graph->nb_filters; ++i) {
self->filter_graph->filters[i]->hw_device_ctx =
av_buffer_ref(self->hw_device_ctx);
}
rc = avfilter_graph_config(self->filter_graph, NULL);
if (rc != 0)
goto failure;
return 0;
failure:
avfilter_graph_free(&self->filter_graph);
return -1;
}
static int h264_encoder__init_codec_context(struct h264_encoder_ffmpeg* self,
const AVCodec* codec, int quality)
{
self->codec_ctx = avcodec_alloc_context3(codec);
if (!self->codec_ctx)
return -1;
struct AVCodecContext* c = self->codec_ctx;
c->width = self->width;
c->height = self->height;
c->time_base = self->timebase;
c->sample_aspect_ratio = self->sample_aspect_ratio;
c->pix_fmt = AV_PIX_FMT_VAAPI;
c->gop_size = INT32_MAX; /* We'll select key frames manually */
c->max_b_frames = 0; /* B-frames are bad for latency */
c->global_quality = quality;
/* open-h264 requires baseline profile, so we use constrained
* baseline: AV_PROFILE_H264_BASELINE.
* But that is not supported by many clients. So we use a "DEFAULT" profile.
*
*/
c->profile = AV_PROFILE_H264_MAIN;
return 0;
}
static int h264_encoder__init_hw_frames_context(struct h264_encoder_ffmpeg* self)
{
self->hw_frames_ctx = av_hwframe_ctx_alloc(self->hw_device_ctx);
if (!self->hw_frames_ctx)
return -1;
AVHWFramesContext* c = (AVHWFramesContext*)self->hw_frames_ctx->data;
c->format = AV_PIX_FMT_DRM_PRIME;
c->sw_format = drm_to_av_pixel_format(self->format);
c->width = self->width;
c->height = self->height;
if (av_hwframe_ctx_init(self->hw_frames_ctx) < 0)
av_buffer_unref(&self->hw_frames_ctx);
return 0;
}
static int h264_encoder__schedule_work(struct h264_encoder_ffmpeg* self)
{
if (self->current_fb)
return 0;
self->current_fb = fb_queue_dequeue(&self->fb_queue);
if (!self->current_fb)
return 0;
DTRACE_PROBE1(neatvnc, h264_encode_frame_begin, self->current_fb->pts);
self->current_frame_is_keyframe = self->base.next_frame_should_be_keyframe;
self->base.next_frame_should_be_keyframe = false;
return aml_start(aml_get_default(), self->work);
}
static int h264_encoder__encode(struct h264_encoder_ffmpeg* self,
AVFrame* frame_in)
{
int rc;
rc = av_buffersrc_add_frame_flags(self->filter_in, frame_in,
AV_BUFFERSRC_FLAG_KEEP_REF);
if (rc != 0)
return -1;
AVFrame* filtered_frame = av_frame_alloc();
if (!filtered_frame)
return -1;
rc = av_buffersink_get_frame(self->filter_out, filtered_frame);
if (rc != 0)
goto get_frame_failure;
rc = avcodec_send_frame(self->codec_ctx, filtered_frame);
if (rc != 0)
goto send_frame_failure;
AVPacket* packet = av_packet_alloc();
assert(packet); // TODO
while (1) {
rc = avcodec_receive_packet(self->codec_ctx, packet);
if (rc != 0)
break;
vec_append(&self->current_packet, packet->data, packet->size);
packet->stream_index = 0;
av_packet_unref(packet);
}
// Frame should always start with a zero:
assert(self->current_packet.len == 0 ||
((char*)self->current_packet.data)[0] == 0);
av_packet_free(&packet);
send_frame_failure:
av_frame_unref(filtered_frame);
get_frame_failure:
av_frame_free(&filtered_frame);
return rc == AVERROR(EAGAIN) ? 0 : rc;
}
static void h264_encoder__do_work(void* handle)
{
struct h264_encoder_ffmpeg* self = aml_get_userdata(handle);
AVFrame* frame = fb_to_avframe(self->current_fb);
assert(frame); // TODO
frame->hw_frames_ctx = av_buffer_ref(self->hw_frames_ctx);
if (self->current_frame_is_keyframe) {
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
frame->flags |= AV_FRAME_FLAG_KEY;
#else
frame->key_frame = 1;
#endif
frame->pict_type = AV_PICTURE_TYPE_I;
} else {
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
frame->flags &= ~AV_FRAME_FLAG_KEY;
#else
frame->key_frame = 0;
#endif
frame->pict_type = AV_PICTURE_TYPE_P;
}
int rc = h264_encoder__encode(self, frame);
if (rc != 0) {
char err[256];
av_strerror(rc, err, sizeof(err));
nvnc_log(NVNC_LOG_ERROR, "Failed to encode packet: %s", err);
goto failure;
}
failure:
av_frame_unref(frame);
av_frame_free(&frame);
}
static void h264_encoder__on_work_done(void* handle)
{
struct h264_encoder_ffmpeg* self = aml_get_userdata(handle);
uint64_t pts = nvnc_fb_get_pts(self->current_fb);
nvnc_fb_release(self->current_fb);
nvnc_fb_unref(self->current_fb);
self->current_fb = NULL;
DTRACE_PROBE1(neatvnc, h264_encode_frame_end, pts);
if (self->please_destroy) {
vec_destroy(&self->current_packet);
h264_encoder_destroy(&self->base);
return;
}
if (self->current_packet.len == 0) {
nvnc_log(NVNC_LOG_WARNING, "Whoops, encoded packet length is 0");
return;
}
void* userdata = self->base.userdata;
// Must make a copy of packet because the callback might destroy the
// encoder object.
struct vec packet;
vec_init(&packet, self->current_packet.len);
vec_append(&packet, self->current_packet.data,
self->current_packet.len);
vec_clear(&self->current_packet);
h264_encoder__schedule_work(self);
self->base.on_packet_ready(packet.data, packet.len, pts, userdata);
vec_destroy(&packet);
}
static int find_render_node(char *node, size_t maxlen) {
bool r = -1;
drmDevice *devices[64];
int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0]));
for (int i = 0; i < n; ++i) {
drmDevice *dev = devices[i];
if (!(dev->available_nodes & (1 << DRM_NODE_RENDER)))
continue;
strncpy(node, dev->nodes[DRM_NODE_RENDER], maxlen);
node[maxlen - 1] = '\0';
r = 0;
break;
}
drmFreeDevices(devices, n);
return r;
}
static struct h264_encoder* h264_encoder_ffmpeg_create(uint32_t width,
uint32_t height, uint32_t format, int quality)
{
int rc;
struct h264_encoder_ffmpeg* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->base.impl = &h264_encoder_ffmpeg_impl;
if (vec_init(&self->current_packet, 65536) < 0)
goto packet_failure;
self->work = aml_work_new(h264_encoder__do_work,
h264_encoder__on_work_done, self, NULL);
if (!self->work)
goto worker_failure;
char render_node[64];
if (find_render_node(render_node, sizeof(render_node)) < 0)
goto render_node_failure;
rc = av_hwdevice_ctx_create(&self->hw_device_ctx,
AV_HWDEVICE_TYPE_DRM, render_node, NULL, 0);
if (rc != 0)
goto hwdevice_ctx_failure;
self->base.next_frame_should_be_keyframe = true;
TAILQ_INIT(&self->fb_queue);
self->width = width;
self->height = height;
self->format = format;
self->timebase = (AVRational){1, 1000000};
self->sample_aspect_ratio = (AVRational){1, 1};
self->av_pixel_format = drm_to_av_pixel_format(format);
if (self->av_pixel_format == AV_PIX_FMT_NONE)
goto pix_fmt_failure;
const AVCodec* codec = avcodec_find_encoder_by_name("h264_vaapi");
if (!codec)
goto codec_failure;
if (h264_encoder__init_hw_frames_context(self) < 0)
goto hw_frames_context_failure;
if (h264_encoder__init_filters(self) < 0)
goto filter_failure;
if (h264_encoder__init_codec_context(self, codec, quality) < 0)
goto codec_context_failure;
self->codec_ctx->hw_frames_ctx =
av_buffer_ref(self->filter_out->inputs[0]->hw_frames_ctx);
AVDictionary *opts = NULL;
av_dict_set_int(&opts, "async_depth", 1, 0);
rc = avcodec_open2(self->codec_ctx, codec, &opts);
av_dict_free(&opts);
if (rc != 0)
goto avcodec_open_failure;
return &self->base;
avcodec_open_failure:
avcodec_free_context(&self->codec_ctx);
codec_context_failure:
filter_failure:
av_buffer_unref(&self->hw_frames_ctx);
hw_frames_context_failure:
codec_failure:
pix_fmt_failure:
av_buffer_unref(&self->hw_device_ctx);
hwdevice_ctx_failure:
render_node_failure:
aml_unref(self->work);
worker_failure:
vec_destroy(&self->current_packet);
packet_failure:
free(self);
return NULL;
}
static void h264_encoder_ffmpeg_destroy(struct h264_encoder* base)
{
struct h264_encoder_ffmpeg* self = (struct h264_encoder_ffmpeg*)base;
if (self->current_fb) {
self->please_destroy = true;
return;
}
vec_destroy(&self->current_packet);
av_buffer_unref(&self->hw_frames_ctx);
avcodec_free_context(&self->codec_ctx);
av_buffer_unref(&self->hw_device_ctx);
avfilter_graph_free(&self->filter_graph);
aml_unref(self->work);
free(self);
}
static void h264_encoder_ffmpeg_feed(struct h264_encoder* base,
struct nvnc_fb* fb)
{
struct h264_encoder_ffmpeg* self = (struct h264_encoder_ffmpeg*)base;
assert(fb->type == NVNC_FB_GBM_BO);
// TODO: Add transform filter
assert(fb->transform == NVNC_TRANSFORM_NORMAL);
int rc = fb_queue_enqueue(&self->fb_queue, fb);
assert(rc == 0); // TODO
nvnc_fb_hold(fb);
rc = h264_encoder__schedule_work(self);
assert(rc == 0); // TODO
}
struct h264_encoder_impl h264_encoder_ffmpeg_impl = {
.create = h264_encoder_ffmpeg_create,
.destroy = h264_encoder_ffmpeg_destroy,
.feed = h264_encoder_ffmpeg_feed,
};

View File

@ -0,0 +1,741 @@
/*
* Copyright (c) 2024 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "h264-encoder.h"
#include "neatvnc.h"
#include "fb.h"
#include "pixels.h"
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <inttypes.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <drm_fourcc.h>
#include <gbm.h>
#include <aml.h>
#include <dirent.h>
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
#define ALIGN_UP(a, b) ((b) * UDIV_UP((a), (b)))
#define ARRAY_LENGTH(a) (sizeof(a) / sizeof((a)[0]))
#define N_SRC_BUFS 3
#define N_DST_BUFS 3
struct h264_encoder_v4l2m2m_dst_buf {
struct v4l2_buffer buffer;
struct v4l2_plane plane;
void* payload;
};
struct h264_encoder_v4l2m2m_src_buf {
struct v4l2_buffer buffer;
struct v4l2_plane planes[4];
int fd;
bool is_taken;
struct nvnc_fb* fb;
};
struct h264_encoder_v4l2m2m {
struct h264_encoder base;
uint32_t width;
uint32_t height;
uint32_t format;
int quality; // TODO: Can we affect the quality?
char driver[16];
int fd;
struct aml_handler* handler;
struct h264_encoder_v4l2m2m_src_buf src_bufs[N_SRC_BUFS];
int src_buf_index;
struct h264_encoder_v4l2m2m_dst_buf dst_bufs[N_DST_BUFS];
};
struct h264_encoder_impl h264_encoder_v4l2m2m_impl;
static int v4l2_qbuf(int fd, const struct v4l2_buffer* inbuf)
{
assert(inbuf->length <= 4);
struct v4l2_plane planes[4];
struct v4l2_buffer outbuf;
outbuf = *inbuf;
memcpy(&planes, inbuf->m.planes, inbuf->length * sizeof(planes[0]));
outbuf.m.planes = planes;
return ioctl(fd, VIDIOC_QBUF, &outbuf);
}
static inline int v4l2_dqbuf(int fd, struct v4l2_buffer* buf)
{
return ioctl(fd, VIDIOC_DQBUF, buf);
}
static struct h264_encoder_v4l2m2m_src_buf* take_src_buffer(
struct h264_encoder_v4l2m2m* self)
{
unsigned int count = 0;
int i = self->src_buf_index;
struct h264_encoder_v4l2m2m_src_buf* buffer;
do {
buffer = &self->src_bufs[i++];
i %= ARRAY_LENGTH(self->src_bufs);
} while (++count < ARRAY_LENGTH(self->src_bufs) && buffer->is_taken);
if (buffer->is_taken)
return NULL;
self->src_buf_index = i;
buffer->is_taken = true;
return buffer;
}
static bool any_src_buf_is_taken(struct h264_encoder_v4l2m2m* self)
{
bool result = false;
for (unsigned int i = 0; i < ARRAY_LENGTH(self->src_bufs); ++i)
if (self->src_bufs[i].is_taken)
result = true;
return result;
}
static int u32_cmp(const void* pa, const void* pb)
{
const uint32_t *a = pa;
const uint32_t *b = pb;
return *a < *b ? -1 : *a > *b;
}
static size_t get_supported_formats(struct h264_encoder_v4l2m2m* self,
uint32_t* formats, size_t max_len)
{
size_t i = 0;
for (;; ++i) {
struct v4l2_fmtdesc desc = {
.index = i,
.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
};
int rc = ioctl(self->fd, VIDIOC_ENUM_FMT, &desc);
if (rc < 0)
break;
nvnc_trace("Got pixel format: %s", desc.description);
formats[i] = desc.pixelformat;
}
qsort(formats, i, sizeof(*formats), u32_cmp);
return i;
}
static bool have_v4l2_format(const uint32_t* formats, size_t n_formats,
uint32_t format)
{
return bsearch(&format, formats, n_formats, sizeof(format), u32_cmp);
}
static uint32_t v4l2_format_from_drm(const uint32_t* formats,
size_t n_formats, uint32_t drm_format)
{
#define TRY_FORMAT(f) \
if (have_v4l2_format(formats, n_formats, f)) \
return f
switch (drm_format) {
case DRM_FORMAT_RGBX8888:
case DRM_FORMAT_RGBA8888:
TRY_FORMAT(V4L2_PIX_FMT_RGBX32);
TRY_FORMAT(V4L2_PIX_FMT_RGBA32);
break;
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
TRY_FORMAT(V4L2_PIX_FMT_XRGB32);
TRY_FORMAT(V4L2_PIX_FMT_ARGB32);
TRY_FORMAT(V4L2_PIX_FMT_RGB32);
break;
case DRM_FORMAT_BGRX8888:
case DRM_FORMAT_BGRA8888:
TRY_FORMAT(V4L2_PIX_FMT_XBGR32);
TRY_FORMAT(V4L2_PIX_FMT_ABGR32);
TRY_FORMAT(V4L2_PIX_FMT_BGR32);
break;
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_ABGR8888:
TRY_FORMAT(V4L2_PIX_FMT_BGRX32);
TRY_FORMAT(V4L2_PIX_FMT_BGRA32);
break;
// TODO: More formats
}
return 0;
#undef TRY_FORMAT
}
// This driver mixes up pixel formats...
static uint32_t v4l2_format_from_drm_bcm2835(const uint32_t* formats,
size_t n_formats, uint32_t drm_format)
{
switch (drm_format) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
return V4L2_PIX_FMT_RGBA32;
case DRM_FORMAT_BGRX8888:
case DRM_FORMAT_BGRA8888:
// TODO: This could also be ABGR, based on how this driver
// behaves
return V4L2_PIX_FMT_BGR32;
}
return 0;
}
static int set_src_fmt(struct h264_encoder_v4l2m2m* self)
{
int rc;
uint32_t supported_formats[256];
size_t n_formats = get_supported_formats(self, supported_formats,
ARRAY_LENGTH(supported_formats));
uint32_t format;
if (strcmp(self->driver, "bcm2835-codec") == 0)
format = v4l2_format_from_drm_bcm2835(supported_formats,
n_formats, self->format);
else
format = v4l2_format_from_drm(supported_formats, n_formats,
self->format);
if (!format) {
nvnc_log(NVNC_LOG_DEBUG, "Failed to find a proper pixel format");
return -1;
}
struct v4l2_format fmt = {
.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
};
rc = ioctl(self->fd, VIDIOC_G_FMT, &fmt);
if (rc < 0) {
return -1;
}
struct v4l2_pix_format_mplane* pix_fmt = &fmt.fmt.pix_mp;
pix_fmt->pixelformat = format;
pix_fmt->width = ALIGN_UP(self->width, 16);
pix_fmt->height = ALIGN_UP(self->height, 16);
rc = ioctl(self->fd, VIDIOC_S_FMT, &fmt);
if (rc < 0) {
return -1;
}
return 0;
}
static int set_dst_fmt(struct h264_encoder_v4l2m2m* self)
{
int rc;
struct v4l2_format fmt = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
};
rc = ioctl(self->fd, VIDIOC_G_FMT, &fmt);
if (rc < 0) {
return -1;
}
struct v4l2_pix_format_mplane* pix_fmt = &fmt.fmt.pix_mp;
pix_fmt->pixelformat = V4L2_PIX_FMT_H264;
pix_fmt->width = self->width;
pix_fmt->height = self->height;
rc = ioctl(self->fd, VIDIOC_S_FMT, &fmt);
if (rc < 0) {
return -1;
}
return 0;
}
static int alloc_dst_buffers(struct h264_encoder_v4l2m2m* self)
{
int n_bufs = ARRAY_LENGTH(self->dst_bufs);
int rc;
struct v4l2_requestbuffers req = {
.memory = V4L2_MEMORY_MMAP,
.count = n_bufs,
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
};
rc = ioctl(self->fd, VIDIOC_REQBUFS, &req);
if (rc < 0)
return -1;
for (unsigned int i = 0; i < req.count; ++i) {
struct h264_encoder_v4l2m2m_dst_buf* buffer = &self->dst_bufs[i];
struct v4l2_buffer* buf = &buffer->buffer;
buf->index = i;
buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf->memory = V4L2_MEMORY_MMAP;
buf->length = 1;
buf->m.planes = &buffer->plane;
rc = ioctl(self->fd, VIDIOC_QUERYBUF, buf);
if (rc < 0)
return -1;
buffer->payload = mmap(0, buffer->plane.length,
PROT_READ | PROT_WRITE, MAP_SHARED, self->fd,
buffer->plane.m.mem_offset);
if (buffer->payload == MAP_FAILED) {
nvnc_log(NVNC_LOG_ERROR, "Whoops, mapping failed: %m");
return -1;
}
}
return 0;
}
static void enqueue_dst_buffers(struct h264_encoder_v4l2m2m* self)
{
for (unsigned int i = 0; i < ARRAY_LENGTH(self->dst_bufs); ++i) {
int rc = v4l2_qbuf(self->fd, &self->dst_bufs[i].buffer);
assert(rc >= 0);
}
}
static void process_dst_bufs(struct h264_encoder_v4l2m2m* self)
{
int rc;
struct v4l2_plane plane = { 0 };
struct v4l2_buffer buf = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
.memory = V4L2_MEMORY_MMAP,
.length = 1,
.m.planes = &plane,
};
while (true) {
rc = v4l2_dqbuf(self->fd, &buf);
if (rc < 0)
break;
uint64_t pts = buf.timestamp.tv_sec * UINT64_C(1000000) +
buf.timestamp.tv_usec;
struct h264_encoder_v4l2m2m_dst_buf* dstbuf =
&self->dst_bufs[buf.index];
size_t size = buf.m.planes[0].bytesused;
static uint64_t last_pts;
if (last_pts && last_pts > pts) {
nvnc_log(NVNC_LOG_ERROR, "pts - last_pts = %"PRIi64,
(int64_t)pts - (int64_t)last_pts);
}
last_pts = pts;
nvnc_trace("Encoded frame (index %d) at %"PRIu64" µs with size: %zu",
buf.index, pts, size);
self->base.on_packet_ready(dstbuf->payload, size, pts,
self->base.userdata);
v4l2_qbuf(self->fd, &buf);
}
}
static void process_src_bufs(struct h264_encoder_v4l2m2m* self)
{
int rc;
struct v4l2_plane planes[4] = { 0 };
struct v4l2_buffer buf = {
.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
.memory = V4L2_MEMORY_DMABUF,
.length = 1,
.m.planes = planes,
};
while (true) {
rc = v4l2_dqbuf(self->fd, &buf);
if (rc < 0)
break;
struct h264_encoder_v4l2m2m_src_buf* srcbuf =
&self->src_bufs[buf.index];
srcbuf->is_taken = false;
// TODO: This assumes that there's only one fd
close(srcbuf->planes[0].m.fd);
nvnc_fb_unmap(srcbuf->fb);
nvnc_fb_release(srcbuf->fb);
nvnc_fb_unref(srcbuf->fb);
srcbuf->fb = NULL;
}
}
static void stream_off(struct h264_encoder_v4l2m2m* self)
{
int type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
ioctl(self->fd, VIDIOC_STREAMOFF, &type);
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
ioctl(self->fd, VIDIOC_STREAMOFF, &type);
}
static void free_dst_buffers(struct h264_encoder_v4l2m2m* self)
{
for (unsigned int i = 0; i < ARRAY_LENGTH(self->dst_bufs); ++i) {
struct h264_encoder_v4l2m2m_dst_buf* buf = &self->dst_bufs[i];
munmap(buf->payload, buf->plane.length);
}
}
static int stream_on(struct h264_encoder_v4l2m2m* self)
{
int type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
ioctl(self->fd, VIDIOC_STREAMON, &type);
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
return ioctl(self->fd, VIDIOC_STREAMON, &type);
}
static int alloc_src_buffers(struct h264_encoder_v4l2m2m* self)
{
int rc;
struct v4l2_requestbuffers req = {
.memory = V4L2_MEMORY_DMABUF,
.count = N_SRC_BUFS,
.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
};
rc = ioctl(self->fd, VIDIOC_REQBUFS, &req);
if (rc < 0)
return -1;
for (int i = 0; i < N_SRC_BUFS; ++i) {
struct h264_encoder_v4l2m2m_src_buf* buffer = &self->src_bufs[i];
struct v4l2_buffer* buf = &buffer->buffer;
buf->index = i;
buf->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
buf->memory = V4L2_MEMORY_DMABUF;
buf->length = 1;
buf->m.planes = buffer->planes;
rc = ioctl(self->fd, VIDIOC_QUERYBUF, buf);
if (rc < 0)
return -1;
}
return 0;
}
static void force_key_frame(struct h264_encoder_v4l2m2m* self)
{
struct v4l2_control ctrl = { 0 };
ctrl.id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME;
ctrl.value = 0;
ioctl(self->fd, VIDIOC_S_CTRL, &ctrl);
}
static void encode_buffer(struct h264_encoder_v4l2m2m* self,
struct nvnc_fb* fb)
{
struct h264_encoder_v4l2m2m_src_buf* srcbuf = take_src_buffer(self);
if (!srcbuf) {
nvnc_log(NVNC_LOG_ERROR, "Out of source buffers. Dropping frame...");
return;
}
assert(!srcbuf->fb);
nvnc_fb_ref(fb);
nvnc_fb_hold(fb);
/* For some reason the v4l2m2m h264 encoder in the Rapberry Pi 4 gets
* really glitchy unless the buffer is mapped first.
* This should probably be handled by the driver, but it's not.
*/
nvnc_fb_map(fb);
srcbuf->fb = fb;
struct gbm_bo* bo = nvnc_fb_get_gbm_bo(fb);
int n_planes = gbm_bo_get_plane_count(bo);
int fd = gbm_bo_get_fd(bo);
uint32_t height = ALIGN_UP(gbm_bo_get_height(bo), 16);
for (int i = 0; i < n_planes; ++i) {
uint32_t stride = gbm_bo_get_stride_for_plane(bo, i);
uint32_t offset = gbm_bo_get_offset(bo, i);
uint32_t size = stride * height;
srcbuf->buffer.m.planes[i].m.fd = fd;
srcbuf->buffer.m.planes[i].bytesused = size;
srcbuf->buffer.m.planes[i].length = size;
srcbuf->buffer.m.planes[i].data_offset = offset;
}
srcbuf->buffer.timestamp.tv_sec = fb->pts / UINT64_C(1000000);
srcbuf->buffer.timestamp.tv_usec = fb->pts % UINT64_C(1000000);
if (self->base.next_frame_should_be_keyframe)
force_key_frame(self);
self->base.next_frame_should_be_keyframe = false;
int rc = v4l2_qbuf(self->fd, &srcbuf->buffer);
if (rc < 0) {
nvnc_log(NVNC_LOG_PANIC, "Failed to enqueue buffer: %m");
}
}
static void process_fd_events(void* handle)
{
struct h264_encoder_v4l2m2m* self = aml_get_userdata(handle);
process_dst_bufs(self);
}
static void h264_encoder_v4l2m2m_configure(struct h264_encoder_v4l2m2m* self)
{
struct v4l2_control ctrl = { 0 };
ctrl.id = V4L2_CID_MPEG_VIDEO_H264_PROFILE;
ctrl.value = V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE;
ioctl(self->fd, VIDIOC_S_CTRL, &ctrl);
ctrl.id = V4L2_CID_MPEG_VIDEO_H264_I_PERIOD;
ctrl.value = INT_MAX;
ioctl(self->fd, VIDIOC_S_CTRL, &ctrl);
ctrl.id = V4L2_CID_MPEG_VIDEO_BITRATE_MODE;
ctrl.value = V4L2_MPEG_VIDEO_BITRATE_MODE_CQ;
ioctl(self->fd, VIDIOC_S_CTRL, &ctrl);
ctrl.id = V4L2_CID_MPEG_VIDEO_CONSTANT_QUALITY;
ctrl.value = self->quality;
ioctl(self->fd, VIDIOC_S_CTRL, &ctrl);
}
static bool can_encode_to_h264(int fd)
{
size_t i = 0;
for (;; ++i) {
struct v4l2_fmtdesc desc = {
.index = i,
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
};
int rc = ioctl(fd, VIDIOC_ENUM_FMT, &desc);
if (rc < 0)
break;
if (desc.pixelformat == V4L2_PIX_FMT_H264)
return true;
}
return false;
}
static bool can_handle_frame_size(int fd, uint32_t width, uint32_t height)
{
size_t i = 0;
for (;; ++i) {
struct v4l2_frmsizeenum size = {
.index = i,
.pixel_format = V4L2_PIX_FMT_H264,
};
int rc = ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &size);
if (rc < 0)
break;
switch (size.type) {
case V4L2_FRMSIZE_TYPE_DISCRETE:
if (size.discrete.width == width &&
size.discrete.height == height)
return true;
break;
case V4L2_FRMSIZE_TYPE_CONTINUOUS:
case V4L2_FRMSIZE_TYPE_STEPWISE:
if (size.stepwise.min_width <= width &&
width <= size.stepwise.max_width &&
size.stepwise.min_height <= height &&
height <= size.stepwise.max_height &&
(16 % size.stepwise.step_width) == 0 &&
(16 % size.stepwise.step_height) == 0)
return true;
break;
}
}
return false;
}
static bool is_device_capable(int fd, uint32_t width, uint32_t height)
{
struct v4l2_capability cap = { 0 };
int rc = ioctl(fd, VIDIOC_QUERYCAP, &cap);
if (rc < 0)
return false;
uint32_t required_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
if ((cap.capabilities & required_caps) != required_caps)
return false;
if (!can_encode_to_h264(fd))
return false;
if (!can_handle_frame_size(fd, width, height))
return false;
return true;
}
static int find_capable_device(uint32_t width, uint32_t height)
{
int fd = -1;
DIR *dir = opendir("/dev");
assert(dir);
for (;;) {
struct dirent* entry = readdir(dir);
if (!entry)
break;
if (strncmp(entry->d_name, "video", 5) != 0)
continue;
char path[256];
snprintf(path, sizeof(path), "/dev/%s", entry->d_name);
fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
continue;
}
if (is_device_capable(fd, width, height)) {
nvnc_log(NVNC_LOG_DEBUG, "Using v4l2m2m device: %s",
path);
break;
}
close(fd);
fd = -1;
}
closedir(dir);
return fd;
}
static struct h264_encoder* h264_encoder_v4l2m2m_create(uint32_t width,
uint32_t height, uint32_t format, int quality)
{
struct h264_encoder_v4l2m2m* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->base.impl = &h264_encoder_v4l2m2m_impl;
self->fd = -1;
self->width = width;
self->height = height;
self->format = format;
self->quality = quality;
self->fd = find_capable_device(width, height);
if (self->fd < 0)
goto failure;
struct v4l2_capability cap = { 0 };
ioctl(self->fd, VIDIOC_QUERYCAP, &cap);
strncpy(self->driver, (const char*)cap.driver, sizeof(self->driver));
if (set_src_fmt(self) < 0)
goto failure;
if (set_dst_fmt(self) < 0)
goto failure;
h264_encoder_v4l2m2m_configure(self);
if (alloc_dst_buffers(self) < 0)
goto failure;
if (alloc_src_buffers(self) < 0)
goto failure;
enqueue_dst_buffers(self);
if (stream_on(self) < 0)
goto failure;
int flags = fcntl(self->fd, F_GETFL);
fcntl(self->fd, F_SETFL, flags | O_NONBLOCK);
self->handler = aml_handler_new(self->fd, process_fd_events, self, NULL);
aml_set_event_mask(self->handler, AML_EVENT_READ);
if (aml_start(aml_get_default(), self->handler) < 0) {
aml_unref(self->handler);
goto failure;
}
return &self->base;
failure:
if (self->fd >= 0)
close(self->fd);
return NULL;
}
static void claim_all_src_bufs(
struct h264_encoder_v4l2m2m* self)
{
for (;;) {
process_src_bufs(self);
if (!any_src_buf_is_taken(self))
break;
usleep(10000);
}
}
static void h264_encoder_v4l2m2m_destroy(struct h264_encoder* base)
{
struct h264_encoder_v4l2m2m* self = (struct h264_encoder_v4l2m2m*)base;
claim_all_src_bufs(self);
aml_stop(aml_get_default(), self->handler);
aml_unref(self->handler);
stream_off(self);
free_dst_buffers(self);
if (self->fd >= 0)
close(self->fd);
free(self);
}
static void h264_encoder_v4l2m2m_feed(struct h264_encoder* base,
struct nvnc_fb* fb)
{
struct h264_encoder_v4l2m2m* self = (struct h264_encoder_v4l2m2m*)base;
process_src_bufs(self);
encode_buffer(self, fb);
}
struct h264_encoder_impl h264_encoder_v4l2m2m_impl = {
.create = h264_encoder_v4l2m2m_create,
.destroy = h264_encoder_v4l2m2m_destroy,
.feed = h264_encoder_v4l2m2m_feed,
};

74
src/h264-encoder.c 100644
View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2024 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "h264-encoder.h"
#include "config.h"
#ifdef HAVE_FFMPEG
extern struct h264_encoder_impl h264_encoder_ffmpeg_impl;
#endif
#ifdef HAVE_V4L2
extern struct h264_encoder_impl h264_encoder_v4l2m2m_impl;
#endif
struct h264_encoder* h264_encoder_create(uint32_t width, uint32_t height,
uint32_t format, int quality)
{
struct h264_encoder* encoder = NULL;
#ifdef HAVE_V4L2
encoder = h264_encoder_v4l2m2m_impl.create(width, height, format, quality);
if (encoder) {
return encoder;
}
#endif
#ifdef HAVE_FFMPEG
encoder = h264_encoder_ffmpeg_impl.create(width, height, format, quality);
if (encoder) {
return encoder;
}
#endif
return encoder;
}
void h264_encoder_destroy(struct h264_encoder* self)
{
self->impl->destroy(self);
}
void h264_encoder_set_packet_handler_fn(struct h264_encoder* self,
h264_encoder_packet_handler_fn fn)
{
self->on_packet_ready = fn;
}
void h264_encoder_set_userdata(struct h264_encoder* self, void* userdata)
{
self->userdata = userdata;
}
void h264_encoder_feed(struct h264_encoder* self, struct nvnc_fb* fb)
{
self->impl->feed(self, fb);
}
void h264_encoder_request_keyframe(struct h264_encoder* self)
{
self->next_frame_should_be_keyframe = true;
}

483
src/http.c 100644
View File

@ -0,0 +1,483 @@
/* Copyright (c) 2014-2016, Marel
* Copyright (c) 2023, Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "vec.h"
#include "http.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
enum httplex_token_type {
HTTPLEX_SOLIDUS,
HTTPLEX_CR,
HTTPLEX_LF,
HTTPLEX_WS,
HTTPLEX_LITERAL,
HTTPLEX_KEY,
HTTPLEX_VALUE,
HTTPLEX_QUERY,
HTTPLEX_AMPERSAND,
HTTPLEX_EQ,
HTTPLEX_END,
};
struct httplex_token {
enum httplex_token_type type;
const char* value;
};
enum httplex_state {
HTTPLEX_STATE_REQUEST = 0,
HTTPLEX_STATE_KEY,
HTTPLEX_STATE_VALUE,
};
struct httplex {
enum httplex_state state;
struct httplex_token current_token;
const char* input;
const char* pos;
const char* next_pos;
struct vec buffer;
int accepted;
int errno_;
};
static int httplex_init(struct httplex* self, const char* input)
{
memset(self, 0, sizeof(*self));
self->input = input;
self->pos = input;
self->accepted = 1;
if (vec_reserve(&self->buffer, 256) < 0)
return -1;
return 0;
}
static void httplex_destroy(struct httplex* self)
{
vec_destroy(&self->buffer);
}
static inline int httplex__is_literal(char c)
{
switch (c) {
case '/': case '\r': case '\n': case ' ': case '\t':
case '?': case '&': case '=':
return 0;
}
return isprint(c);
}
static inline size_t httplex__literal_length(const char* str)
{
size_t len = 0;
while (httplex__is_literal(*str++))
++len;
return len;
}
static int httplex__classify_request_token(struct httplex* self)
{
switch (*self->pos) {
case '/':
self->current_token.type = HTTPLEX_SOLIDUS;
self->next_pos = self->pos + strspn(self->pos, "/");
return 0;
case '\r':
self->current_token.type = HTTPLEX_CR;
self->next_pos = self->pos + 1;
return 0;
case '\n':
self->current_token.type = HTTPLEX_LF;
self->next_pos = self->pos + 1;
return 0;
case '?':
self->current_token.type = HTTPLEX_QUERY;
self->next_pos = self->pos + 1;
return 0;
case '&':
self->current_token.type = HTTPLEX_AMPERSAND;
self->next_pos = self->pos + 1;
return 0;
case '=':
self->current_token.type = HTTPLEX_EQ;
self->next_pos = self->pos + 1;
return 0;
case ' ':
case '\t':
self->current_token.type = HTTPLEX_WS;
self->next_pos = self->pos + strspn(self->pos, " \t");
return 0;
}
if (httplex__is_literal(*self->pos)) {
self->current_token.type = HTTPLEX_LITERAL;
size_t len = httplex__literal_length(self->pos);
self->next_pos = self->pos + len;
vec_assign(&self->buffer, self->pos, len);
vec_append(&self->buffer, "", 1);
self->current_token.value = self->buffer.data;
return 0;
}
return -1;
}
static inline int httplex__is_key_char(char c)
{
return isalnum(c) || c == '-';
}
static inline size_t httplex__key_length(const char* str)
{
size_t len = 0;
while (httplex__is_key_char(*str++))
++len;
return len;
}
static int httplex__classify_key_token(struct httplex* self)
{
switch (*self->pos) {
case '\r':
self->current_token.type = HTTPLEX_CR;
self->next_pos = self->pos + 1;
return 0;
case '\n':
self->current_token.type = HTTPLEX_LF;
self->next_pos = self->pos + 1;
return 0;
}
if (!httplex__is_key_char(*self->pos))
return -1;
size_t len = httplex__key_length(self->pos);
if (self->pos[len] != ':')
return -1;
len += 1;
self->next_pos = self->pos + len;
self->next_pos += strspn(self->next_pos, " \t");
vec_assign(&self->buffer, self->pos, len - 1);
vec_append(&self->buffer, "", 1);
self->current_token.type = HTTPLEX_KEY;
self->current_token.value = self->buffer.data;
return 0;
}
static int httplex__classify_value_token(struct httplex* self)
{
size_t len = strcspn(self->pos, "\r");
if (strncmp(&self->pos[len], "\r\n", 2) != 0)
return -1;
self->next_pos = self->pos + len + 2;
vec_assign(&self->buffer, self->pos, len);
vec_append(&self->buffer, "", 1);
self->current_token.type = HTTPLEX_VALUE;
self->current_token.value = self->buffer.data;
return 0;
}
static int httplex__classify_token(struct httplex* self)
{
switch (self->state) {
case HTTPLEX_STATE_REQUEST:
return httplex__classify_request_token(self);
case HTTPLEX_STATE_KEY:
return httplex__classify_key_token(self);
case HTTPLEX_STATE_VALUE:
return httplex__classify_value_token(self);
};
abort();
return -1;
}
static struct httplex_token* httplex_next_token(struct httplex* self)
{
if (self->current_token.type == HTTPLEX_END)
return &self->current_token;
if (!self->accepted)
return &self->current_token;
if (self->next_pos)
self->pos = self->next_pos;
if (httplex__classify_token(self) < 0)
return NULL;
self->accepted = 0;
return &self->current_token;
}
static inline int httplex_accept_token(struct httplex* self)
{
self->accepted = 1;
return 1;
}
static int http__literal(struct httplex* lex, const char* str)
{
struct httplex_token* tok = httplex_next_token(lex);
if (!tok)
return 0;
if (tok->type != HTTPLEX_LITERAL)
return 0;
if (strcasecmp(str, tok->value) != 0)
return 0;
return httplex_accept_token(lex);
}
static int http__get(struct http_req* req, struct httplex* lex)
{
return http__literal(lex, "GET");
}
static int http__method(struct http_req* req, struct httplex* lex)
{
return http__get(req, lex);
}
static int http__peek(struct httplex* lex, enum httplex_token_type type)
{
struct httplex_token* tok = httplex_next_token(lex);
if (!tok)
return 0;
if (tok->type != type)
return 0;
return 1;
}
static int http__expect(struct httplex* lex, enum httplex_token_type type)
{
return http__peek(lex, type) && httplex_accept_token(lex);
}
static int http__version(struct httplex* lex)
{
return http__literal(lex, "HTTP")
&& http__expect(lex, HTTPLEX_SOLIDUS)
&& http__literal(lex, "1.1");
}
static int http__url_path(struct http_req* req, struct httplex* lex)
{
if (!http__expect(lex, HTTPLEX_SOLIDUS))
return 0;
struct httplex_token* tok = httplex_next_token(lex);
if (!tok)
return 0;
if (tok->type != HTTPLEX_LITERAL)
return tok->type == HTTPLEX_WS;
httplex_accept_token(lex);
return http__peek(lex, HTTPLEX_SOLIDUS)
? http__url_path(req, lex) : 1;
}
static int http__url_query(struct http_req* req, struct httplex* lex)
{
return http__expect(lex, HTTPLEX_LITERAL)
&& http__expect(lex, HTTPLEX_EQ)
&& http__expect(lex, HTTPLEX_LITERAL)
&& http__expect(lex, HTTPLEX_AMPERSAND)
? http__url_query(req, lex) : 1;
}
static int http__url(struct http_req* req, struct httplex* lex)
{
return http__url_path(req, lex)
&& http__expect(lex, HTTPLEX_QUERY) ? http__url_query(req, lex) : 1;
}
static int http__request(struct http_req* req, struct httplex* lex)
{
return http__method(req, lex)
&& http__expect(lex, HTTPLEX_WS)
&& http__url(req, lex)
&& http__expect(lex, HTTPLEX_WS)
&& http__version(lex)
&& http__expect(lex, HTTPLEX_CR)
&& http__expect(lex, HTTPLEX_LF);
}
static int http__expect_key(struct httplex* lex, const char* key)
{
struct httplex_token* tok = httplex_next_token(lex);
if (!tok)
return 0;
if (tok->type != HTTPLEX_KEY)
return 0;
if (key && strcasecmp(tok->value, key) != 0)
return 0;
return httplex_accept_token(lex);
}
static int http__content_length(struct http_req* req, struct httplex* lex)
{
lex->state = HTTPLEX_STATE_KEY;
if (!http__expect_key(lex, "Content-Length"))
return 0;
lex->state = HTTPLEX_STATE_VALUE;
struct httplex_token* tok = httplex_next_token(lex);
if (!tok)
return 0;
if (tok->type != HTTPLEX_VALUE)
return 0;
req->content_length = atoi(tok->value);
return httplex_accept_token(lex);
}
static int http__content_type(struct http_req* req, struct httplex* lex)
{
lex->state = HTTPLEX_STATE_KEY;
if (!http__expect_key(lex, "Content-Type"))
return 0;
lex->state = HTTPLEX_STATE_VALUE;
struct httplex_token* tok = httplex_next_token(lex);
if (!tok)
return 0;
if (tok->type != HTTPLEX_VALUE)
return 0;
req->content_type = strdup(tok->value);
return httplex_accept_token(lex);
}
static int http__field_key(struct http_req* req, struct httplex* lex)
{
lex->state = HTTPLEX_STATE_KEY;
struct httplex_token* tok = httplex_next_token(lex);
if (!tok)
return 0;
if (tok->type != HTTPLEX_KEY)
return 0;
req->field[req->field_index].key = strdup(tok->value);
return httplex_accept_token(lex);
}
static int http__field_value(struct http_req* req, struct httplex* lex)
{
lex->state = HTTPLEX_STATE_VALUE;
struct httplex_token* tok = httplex_next_token(lex);
if (!tok)
return 0;
if (tok->type != HTTPLEX_VALUE)
return 0;
req->field[req->field_index++].value = strdup(tok->value);
return httplex_accept_token(lex);
}
static int http__field_kv(struct http_req* req, struct httplex* lex)
{
return http__field_key(req, lex)
&& http__field_value(req, lex);
}
static int http__header_kv(struct http_req* req, struct httplex* lex)
{
return http__content_length(req, lex)
|| http__content_type(req, lex)
|| http__field_kv(req, lex);
}
static int http__header(struct http_req* req, struct httplex* lex)
{
while (http__header_kv(req, lex));
lex->state = HTTPLEX_STATE_KEY;
if (http__expect(lex, HTTPLEX_CR))
return http__expect(lex, HTTPLEX_LF);
return 1;
}
int http_req_parse(struct http_req* req, const char* input)
{
memset(req, 0, sizeof(*req));
struct httplex lex;
if (httplex_init(&lex, input) < 0)
return -1;
if (!http__request(req, &lex))
goto failure;
if (!http__header(req, &lex))
goto failure;
req->header_length = lex.next_pos - input;
httplex_destroy(&lex);
return 0;
failure:
httplex_destroy(&lex);
http_req_free(req);
return -1;
}
void http_req_free(struct http_req* req)
{
free(req->content_type);
for (size_t i = 0; i < HTTP_FIELD_INDEX_MAX && req->field[i].key; ++i) {
free(req->field[i].key);
free(req->field[i].value);
}
}

215
src/logging.c 100644
View File

@ -0,0 +1,215 @@
/*
* Copyright (c) 2019 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "neatvnc.h"
#include "common.h"
#include "logging.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <threads.h>
#ifdef HAVE_LIBAVUTIL
#include <libavutil/avutil.h>
#endif
#define EXPORT __attribute__((visibility("default")))
static nvnc_log_fn log_fn = nvnc_default_logger;
static thread_local nvnc_log_fn thread_local_log_fn = NULL;
#ifndef NDEBUG
static enum nvnc_log_level log_level = NVNC_LOG_DEBUG;
#else
static enum nvnc_log_level log_level = NVNC_LOG_WARNING;
#endif
static bool is_initialised = false;
static nvnc_log_fn get_log_fn(void)
{
return thread_local_log_fn ? thread_local_log_fn : log_fn;
}
static char* trim_left(char* str)
{
while (isspace(*str))
++str;
return str;
}
static char* trim_right(char* str)
{
char* end = str + strlen(str) - 1;
while (str < end && isspace(*end))
*end-- = '\0';
return str;
}
static inline char* trim(char* str)
{
return trim_right(trim_left(str));
}
static const char* log_level_to_string(enum nvnc_log_level level)
{
switch (level) {
case NVNC_LOG_PANIC: return "PANIC";
case NVNC_LOG_ERROR: return "ERROR";
case NVNC_LOG_WARNING: return "Warning";
case NVNC_LOG_INFO: return "Info";
case NVNC_LOG_DEBUG: return "DEBUG";
case NVNC_LOG_TRACE: return "TRACE";
}
return "UNKNOWN";
}
static FILE* stream_for_log_level(enum nvnc_log_level level)
{
switch (level) {
case NVNC_LOG_PANIC: return stderr;
case NVNC_LOG_ERROR: return stderr;
case NVNC_LOG_WARNING: return stderr;
case NVNC_LOG_INFO: return stdout;
case NVNC_LOG_DEBUG: return stdout;
case NVNC_LOG_TRACE: return stdout;
}
return stderr;
}
static void nvnc__vlog(const struct nvnc_log_data* meta, const char* fmt,
va_list args)
{
char message[1024];
if (meta->level <= log_level) {
vsnprintf(message, sizeof(message), fmt, args);
get_log_fn()(meta, trim(message));
}
if (meta->level == NVNC_LOG_PANIC)
abort();
}
EXPORT
void nvnc_default_logger(const struct nvnc_log_data* meta,
const char* message)
{
const char* level = log_level_to_string(meta->level);
FILE* stream = stream_for_log_level(meta->level);
if (meta->level == NVNC_LOG_INFO)
fprintf(stream, "Info: %s\n", message);
else
fprintf(stream, "%s: %s: %d: %s\n", level, meta->file,
meta->line, message);
fflush(stream);
}
#ifdef HAVE_LIBAVUTIL
static enum nvnc_log_level nvnc__log_level_from_av(int level)
{
switch (level) {
case AV_LOG_PANIC: return NVNC_LOG_PANIC;
case AV_LOG_FATAL: return NVNC_LOG_ERROR;
case AV_LOG_ERROR: return NVNC_LOG_ERROR;
case AV_LOG_WARNING: return NVNC_LOG_WARNING;
case AV_LOG_INFO: return NVNC_LOG_INFO;
case AV_LOG_VERBOSE: return NVNC_LOG_INFO;
case AV_LOG_DEBUG: return NVNC_LOG_DEBUG;
case AV_LOG_TRACE: return NVNC_LOG_TRACE;
}
return NVNC_LOG_TRACE;
}
static int nvnc__log_level_to_av(enum nvnc_log_level level)
{
switch (level) {
case NVNC_LOG_PANIC: return AV_LOG_PANIC;
case NVNC_LOG_ERROR: return AV_LOG_ERROR;
case NVNC_LOG_WARNING: return AV_LOG_WARNING;
case NVNC_LOG_INFO: return AV_LOG_INFO;
case NVNC_LOG_DEBUG: return AV_LOG_DEBUG;
case NVNC_LOG_TRACE: return AV_LOG_TRACE;
}
return AV_LOG_TRACE;
}
static void nvnc__av_log_callback(void* ptr, int level, const char* fmt,
va_list va)
{
struct nvnc_log_data meta = {
.level = nvnc__log_level_from_av(level),
.file = "libav",
.line = 0,
};
nvnc__vlog(&meta, fmt, va);
}
#endif
EXPORT
void nvnc_set_log_level(enum nvnc_log_level level)
{
log_level = level;
#ifdef HAVE_LIBAVUTIL
av_log_set_level(nvnc__log_level_to_av(level));
#endif
}
EXPORT
void nvnc_set_log_fn(nvnc_log_fn fn)
{
log_fn = fn ? fn : nvnc_default_logger;
}
EXPORT
void nvnc_set_log_fn_thread_local(nvnc_log_fn fn)
{
thread_local_log_fn = fn;
}
EXPORT
void nvnc__log(const struct nvnc_log_data* meta,
const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
nvnc__vlog(meta, fmt, ap);
va_end(ap);
}
void nvnc__log_init(void)
{
if (is_initialised)
return;
#ifdef HAVE_LIBAVUTIL
av_log_set_callback(nvnc__av_log_callback);
#endif
is_initialised = true;
}

241
src/open-h264.c 100644
View File

@ -0,0 +1,241 @@
/*
* Copyright (c) 2021 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "h264-encoder.h"
#include "rfb-proto.h"
#include "enc-util.h"
#include "vec.h"
#include "fb.h"
#include "rcbuf.h"
#include "encoder.h"
#include "usdt.h"
#include <stdlib.h>
#include <math.h>
typedef void (*open_h264_ready_fn)(void*);
struct open_h264_header {
uint32_t length;
uint32_t flags;
} RFB_PACKED;
struct open_h264 {
struct encoder parent;
struct h264_encoder* encoder;
struct vec pending;
uint64_t pts;
uint32_t width;
uint32_t height;
uint32_t format;
bool needs_reset;
int quality;
bool quality_changed;
};
enum open_h264_flags {
OPEN_H264_FLAG_RESET_CONTEXT = 0,
OPEN_H264_FLAG_RESET_ALL_CONTEXTS = 1,
};
struct encoder* open_h264_new(void);
static struct rcbuf* open_h264_pull(struct encoder* enc, uint64_t* pts);
struct encoder_impl encoder_impl_open_h264;
static inline struct open_h264* open_h264(struct encoder* enc)
{
return (struct open_h264*)enc;
}
static void open_h264_handle_packet(const void* data, size_t size, uint64_t pts,
void* userdata)
{
struct open_h264* self = userdata;
// Let's not deplete the RAM if the client isn't pulling
if (self->pending.len > 100000000) {
// TODO: Drop buffer and request a keyframe?
nvnc_log(NVNC_LOG_WARNING, "Pending buffer grew too large. Dropping packet...");
return;
}
vec_append(&self->pending, data, size);
self->pts = pts;
uint64_t rpts = NVNC_NO_PTS;
struct rcbuf* result = open_h264_pull(&self->parent, &rpts);
DTRACE_PROBE1(neatvnc, open_h264_finish_frame, rpts);
encoder_finish_frame(&self->parent, result, rpts);
rcbuf_unref(result);
}
static int open_h264_init_pending(struct open_h264* self)
{
if (vec_init(&self->pending, 4096) < 0)
return -1;
vec_append_zero(&self->pending, sizeof(struct rfb_server_fb_rect));
vec_append_zero(&self->pending, sizeof(struct open_h264_header));
return 0;
}
struct encoder* open_h264_new(void)
{
struct open_h264* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
encoder_init(&self->parent, &encoder_impl_open_h264);
if (open_h264_init_pending(self) < 0) {
free(self);
return NULL;
}
self->pts = NVNC_NO_PTS;
self->quality = 6;
return (struct encoder*)self;
}
static void open_h264_destroy(struct encoder* enc)
{
struct open_h264* self = open_h264(enc);
if (self->encoder)
h264_encoder_destroy(self->encoder);
vec_destroy(&self->pending);
free(self);
}
static int open_h264_resize(struct open_h264* self, struct nvnc_fb* fb)
{
int quality = 51 - round((50.0 / 9.0) * (float)self->quality);
struct h264_encoder* encoder = h264_encoder_create(fb->width,
fb->height, fb->fourcc_format, quality);
if (!encoder)
return -1;
if (self->encoder)
h264_encoder_destroy(self->encoder);
h264_encoder_set_userdata(encoder, self);
h264_encoder_set_packet_handler_fn(encoder, open_h264_handle_packet);
self->encoder = encoder;
self->width = fb->width;
self->height = fb->height;
self->format = fb->fourcc_format;
self->needs_reset = true;
self->quality_changed = false;
return 0;
}
static int open_h264_encode(struct encoder* enc, struct nvnc_fb* fb,
struct pixman_region16* damage)
{
DTRACE_PROBE1(neatvnc, open_h264_encode, fb->pts);
struct open_h264* self = open_h264(enc);
(void)damage;
if (fb->width != self->width || fb->height != self->height ||
fb->fourcc_format != self->format ||
self->quality_changed) {
if (open_h264_resize(self, fb) < 0)
return -1;
}
assert(self->width && self->height);
// TODO: encoder_feed should return an error code
h264_encoder_feed(self->encoder, fb);
return 0;
}
static struct rcbuf* open_h264_pull(struct encoder* enc, uint64_t* pts)
{
struct open_h264* self = open_h264(enc);
size_t payload_size = self->pending.len
- sizeof(struct rfb_server_fb_rect)
- sizeof(struct open_h264_header);
if (payload_size == 0)
return NULL;
if (pts)
*pts = self->pts;
self->pts = NVNC_NO_PTS;
uint32_t flags = self->needs_reset ? OPEN_H264_FLAG_RESET_CONTEXT : 0;
self->needs_reset = false;
struct rfb_server_fb_rect* rect = self->pending.data;
rect->encoding = htonl(RFB_ENCODING_OPEN_H264);
rect->width = htons(self->width);
rect->height = htons(self->height);
rect->x = htons(self->parent.x_pos);
rect->y = htons(self->parent.y_pos);
struct open_h264_header* header =
(void*)(((uint8_t*)self->pending.data) + sizeof(*rect));
header->length = htonl(payload_size);
header->flags = htonl(flags);
enc->n_rects = 1;
struct rcbuf* payload = rcbuf_new(self->pending.data, self->pending.len);
open_h264_init_pending(self);
return payload;
}
static void open_h264_request_keyframe(struct encoder* enc)
{
struct open_h264* self = open_h264(enc);
h264_encoder_request_keyframe(self->encoder);
}
static void open_h264_set_quality(struct encoder* enc, int value)
{
struct open_h264* self = open_h264(enc);
if (value == 10)
value = 6;
self->quality_changed |= self->quality != value;
self->quality = value;
}
struct encoder_impl encoder_impl_open_h264 = {
.flags = ENCODER_IMPL_FLAG_IGNORES_DAMAGE,
.destroy = open_h264_destroy,
.encode = open_h264_encode,
.request_key_frame = open_h264_request_keyframe,
.set_quality = open_h264_set_quality,
};

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 - 2020 Andri Yngvason
* Copyright (c) 2019 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -15,24 +15,28 @@
*/
#include "rfb-proto.h"
#include "pixels.h"
#include <stdlib.h>
#include <assert.h>
#include <libdrm/drm_fourcc.h>
#include <math.h>
#define POPCOUNT(x) __builtin_popcount(x)
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
#define XSTR(s) STR(s)
#define STR(s) #s
void pixel32_to_cpixel(uint8_t* restrict dst,
static void pixel32_to_cpixel(uint8_t* restrict dst,
const struct rfb_pixel_format* dst_fmt,
const uint32_t* restrict src,
const struct rfb_pixel_format* src_fmt,
size_t bytes_per_cpixel, size_t len)
{
assert(src_fmt->true_colour_flag);
assert(src_fmt->bits_per_pixel == 32);
assert(src_fmt->depth <= 32);
assert(dst_fmt->true_colour_flag);
assert(dst_fmt->bits_per_pixel <= 32);
assert(dst_fmt->depth <= 24);
assert(dst_fmt->depth <= 32);
assert(bytes_per_cpixel <= 4 && bytes_per_cpixel >= 1);
uint32_t src_red_shift = src_fmt->red_shift;
@ -148,9 +152,181 @@ void pixel32_to_cpixel(uint8_t* restrict dst,
#undef CONVERT_PIXELS
}
void pixel_to_cpixel(uint8_t* restrict dst,
const struct rfb_pixel_format* dst_fmt,
const uint8_t* restrict src,
const struct rfb_pixel_format* src_fmt,
size_t bytes_per_cpixel, size_t len)
{
if (src_fmt->bits_per_pixel == 32) {
pixel32_to_cpixel(dst, dst_fmt, (uint32_t*)src, src_fmt, bytes_per_cpixel, len);
return;
}
assert(src_fmt->true_colour_flag);
assert(src_fmt->depth <= 32);
assert(dst_fmt->true_colour_flag);
assert(dst_fmt->bits_per_pixel <= 32);
assert(dst_fmt->depth <= 32);
assert(bytes_per_cpixel <= 4 && bytes_per_cpixel >= 1);
uint32_t src_bpp = src_fmt->bits_per_pixel / 8;
uint32_t src_red_shift = src_fmt->red_shift;
uint32_t src_green_shift = src_fmt->green_shift;
uint32_t src_blue_shift = src_fmt->blue_shift;
uint32_t dst_red_shift = dst_fmt->red_shift;
uint32_t dst_green_shift = dst_fmt->green_shift;
uint32_t dst_blue_shift = dst_fmt->blue_shift;
uint32_t src_red_max = src_fmt->red_max;
uint32_t src_green_max = src_fmt->green_max;
uint32_t src_blue_max = src_fmt->blue_max;
uint32_t src_red_bits = POPCOUNT(src_fmt->red_max);
uint32_t src_green_bits = POPCOUNT(src_fmt->green_max);
uint32_t src_blue_bits = POPCOUNT(src_fmt->blue_max);
uint32_t dst_red_bits = POPCOUNT(dst_fmt->red_max);
uint32_t dst_green_bits = POPCOUNT(dst_fmt->green_max);
uint32_t dst_blue_bits = POPCOUNT(dst_fmt->blue_max);
uint32_t dst_endian_correction;
#define CONVERT_PIXELS(cpx, px) \
{ \
uint32_t r, g, b; \
r = ((px >> src_red_shift) & src_red_max) << dst_red_bits \
>> src_red_bits << dst_red_shift; \
g = ((px >> src_green_shift) & src_green_max) << dst_green_bits\
>> src_green_bits << dst_green_shift; \
b = ((px >> src_blue_shift) & src_blue_max) << dst_blue_bits \
>> src_blue_bits << dst_blue_shift; \
cpx = r | g | b; \
}
switch (bytes_per_cpixel) {
case 4:
if (dst_fmt->big_endian_flag) {
while (len--) {
uint32_t cpx, px = 0;
memcpy(&px, src, src_bpp);
src += src_bpp;
CONVERT_PIXELS(cpx, px)
*dst++ = (cpx >> 24) & 0xff;
*dst++ = (cpx >> 16) & 0xff;
*dst++ = (cpx >> 8) & 0xff;
*dst++ = (cpx >> 0) & 0xff;
}
} else {
while (len--) {
uint32_t cpx, px = 0;
memcpy(&px, src, src_bpp);
src += src_bpp;
CONVERT_PIXELS(cpx, px)
*dst++ = (cpx >> 0) & 0xff;
*dst++ = (cpx >> 8) & 0xff;
*dst++ = (cpx >> 16) & 0xff;
*dst++ = (cpx >> 24) & 0xff;
}
}
break;
case 3:
if (dst_fmt->bits_per_pixel == 32 && dst_fmt->depth <= 24) {
uint32_t min_dst_shift = dst_red_shift;
if (min_dst_shift > dst_green_shift)
min_dst_shift = dst_green_shift;
if (min_dst_shift > dst_blue_shift)
min_dst_shift = dst_blue_shift;
dst_red_shift -= min_dst_shift;
dst_green_shift -= min_dst_shift;
dst_blue_shift -= min_dst_shift;
}
dst_endian_correction = dst_fmt->big_endian_flag ? 16 : 0;
while (len--) {
uint32_t cpx, px = 0;
memcpy(&px, src, src_bpp);
src += src_bpp;
CONVERT_PIXELS(cpx, px)
*dst++ = (cpx >> (0 ^ dst_endian_correction)) & 0xff;
*dst++ = (cpx >> 8) & 0xff;
*dst++ = (cpx >> (16 ^ dst_endian_correction)) & 0xff;
}
break;
case 2:
dst_endian_correction = dst_fmt->big_endian_flag ? 8 : 0;
while (len--) {
uint32_t cpx, px = 0;
memcpy(&px, src, src_bpp);
src += src_bpp;
CONVERT_PIXELS(cpx, px)
*dst++ = (cpx >> (0 ^ dst_endian_correction)) & 0xff;
*dst++ = (cpx >> (8 ^ dst_endian_correction)) & 0xff;
}
break;
case 1:
while (len--) {
uint32_t cpx, px = 0;
memcpy(&px, src, src_bpp);
src += src_bpp;
CONVERT_PIXELS(cpx, px)
*dst++ = cpx & 0xff;
}
break;
default:
abort();
}
#undef CONVERT_PIXELS
}
/* clang-format off */
int rfb_pixfmt_from_fourcc(struct rfb_pixel_format *dst, uint32_t src) {
switch (src & ~DRM_FORMAT_BIG_ENDIAN) {
case DRM_FORMAT_RGBA1010102:
case DRM_FORMAT_RGBX1010102:
dst->red_shift = 22;
dst->green_shift = 12;
dst->blue_shift = 2;
goto bpp_32_10bit;
case DRM_FORMAT_BGRA1010102:
case DRM_FORMAT_BGRX1010102:
dst->red_shift = 2;
dst->green_shift = 12;
dst->blue_shift = 22;
goto bpp_32_10bit;
case DRM_FORMAT_ARGB2101010:
case DRM_FORMAT_XRGB2101010:
dst->red_shift = 20;
dst->green_shift = 10;
dst->blue_shift = 0;
goto bpp_32_10bit;
case DRM_FORMAT_ABGR2101010:
case DRM_FORMAT_XBGR2101010:
dst->red_shift = 0;
dst->green_shift = 10;
dst->blue_shift = 20;
bpp_32_10bit:
dst->bits_per_pixel = 32;
dst->depth = 30;
dst->red_max = 0x3ff;
dst->green_max = 0x3ff;
dst->blue_max = 0x3ff;
break;
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_RGBX8888:
dst->red_shift = 24;
@ -181,6 +357,22 @@ bpp_32:
dst->green_max = 0xff;
dst->blue_max = 0xff;
break;
case DRM_FORMAT_BGR888:
dst->red_shift = 0;
dst->green_shift = 8;
dst->blue_shift = 16;
goto bpp_24;
case DRM_FORMAT_RGB888:
dst->red_shift = 16;
dst->green_shift = 8;
dst->blue_shift = 0;
bpp_24:
dst->bits_per_pixel = 24;
dst->depth = 24;
dst->red_max = 0xff;
dst->green_max = 0xff;
dst->blue_max = 0xff;
break;
case DRM_FORMAT_RGBA4444:
case DRM_FORMAT_RGBX4444:
dst->red_shift = 12;
@ -219,4 +411,269 @@ bpp_16:
dst->true_colour_flag = 1;
return 0;
};
}
int pixel_size_from_fourcc(uint32_t fourcc)
{
switch (fourcc & ~DRM_FORMAT_BIG_ENDIAN) {
case DRM_FORMAT_RGBA1010102:
case DRM_FORMAT_RGBX1010102:
case DRM_FORMAT_BGRA1010102:
case DRM_FORMAT_BGRX1010102:
case DRM_FORMAT_ARGB2101010:
case DRM_FORMAT_XRGB2101010:
case DRM_FORMAT_ABGR2101010:
case DRM_FORMAT_XBGR2101010:
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_RGBX8888:
case DRM_FORMAT_BGRA8888:
case DRM_FORMAT_BGRX8888:
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_XBGR8888:
return 4;
case DRM_FORMAT_BGR888:
case DRM_FORMAT_RGB888:
return 3;
case DRM_FORMAT_RGBA4444:
case DRM_FORMAT_RGBX4444:
case DRM_FORMAT_BGRA4444:
case DRM_FORMAT_BGRX4444:
case DRM_FORMAT_ARGB4444:
case DRM_FORMAT_XRGB4444:
case DRM_FORMAT_ABGR4444:
case DRM_FORMAT_XBGR4444:
return 2;
}
return 0;
}
bool fourcc_to_pixman_fmt(pixman_format_code_t* dst, uint32_t src)
{
assert(!(src & DRM_FORMAT_BIG_ENDIAN));
#define LOWER_R r
#define LOWER_G g
#define LOWER_B b
#define LOWER_A a
#define LOWER_X x
#define LOWER_
#define LOWER(x) LOWER_##x
#define CONCAT_(a, b) a ## b
#define CONCAT(a, b) CONCAT_(a, b)
#define FMT_DRM(x, y, z, v, a, b, c, d) DRM_FORMAT_##x##y##z##v##a##b##c##d
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define FMT_PIXMAN(x, y, z, v, a, b, c, d) \
CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(\
PIXMAN_, LOWER(x)), a), LOWER(y)), b), LOWER(z)), c), LOWER(v)), d)
#else
#define FMT_PIXMAN(x, y, z, v, a, b, c, d) \
CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(\
PIXMAN_, LOWER(v)), d), LOWER(z)), c), LOWER(y)), b), LOWER(x)), a)
#endif
switch (src) {
#define X(...) \
case FMT_DRM(__VA_ARGS__): *dst = FMT_PIXMAN(__VA_ARGS__); break
/* 32 bits */
X(A,R,G,B,8,8,8,8);
X(A,B,G,R,8,8,8,8);
X(X,R,G,B,8,8,8,8);
X(X,B,G,R,8,8,8,8);
X(R,G,B,A,8,8,8,8);
X(B,G,R,A,8,8,8,8);
X(R,G,B,X,8,8,8,8);
X(B,G,R,X,8,8,8,8);
/* 24 bits */
X(R,G,B,,8,8,8,);
X(B,G,R,,8,8,8,);
/* 16 bits */
X(R,G,B,,5,6,5,);
X(B,G,R,,5,6,5,);
/* These are incompatible on big endian */
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
X(A,R,G,B,2,10,10,10);
X(X,R,G,B,2,10,10,10);
X(A,B,G,R,2,10,10,10);
X(X,B,G,R,2,10,10,10);
X(A,R,G,B,1,5,5,5);
X(A,B,G,R,1,5,5,5);
X(X,R,G,B,1,5,5,5);
X(X,B,G,R,1,5,5,5);
X(A,R,G,B,4,4,4,4);
X(A,B,G,R,4,4,4,4);
X(X,R,G,B,4,4,4,4);
X(X,B,G,R,4,4,4,4);
#endif
#undef X
default: return false;
}
return true;
}
static bool extract_alpha_mask_rgba32(uint8_t* dst, const uint32_t* src,
size_t len, int alpha_shift, uint32_t alpha_max)
{
for (size_t i = 0; i < len; i++) {
uint8_t alpha = (src[i] >> alpha_shift) & alpha_max;
uint8_t binary = !!(alpha > alpha_max / 2);
dst[i / 8] |= binary << (7 - (i % 8));
}
return true;
}
static bool extract_alpha_mask_rgba16(uint8_t* dst, const uint16_t* src,
size_t len, int alpha_shift)
{
for (size_t i = 0; i < len; i++) {
uint8_t alpha = (src[i] >> alpha_shift) & 0xf;
uint8_t binary = !!(alpha > 0xf / 2);
dst[i / 8] |= binary << (7 - (i % 8));
}
return true;
}
// Note: The destination buffer must be at least UDIV_UP(len, 8) long.
bool extract_alpha_mask(uint8_t* dst, const void* src, uint32_t format,
size_t len)
{
memset(dst, 0, UDIV_UP(len, 8));
switch (format & ~DRM_FORMAT_BIG_ENDIAN) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
case DRM_FORMAT_RGBA1010102:
case DRM_FORMAT_BGRA1010102:
return extract_alpha_mask_rgba32(dst, src, len, 0, 3);
case DRM_FORMAT_ARGB2101010:
case DRM_FORMAT_ABGR2101010:
return extract_alpha_mask_rgba32(dst, src, len, 30, 3);
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_BGRA8888:
return extract_alpha_mask_rgba32(dst, src, len, 0, 0xff);
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_ABGR8888:
return extract_alpha_mask_rgba32(dst, src, len, 24, 0xff);
case DRM_FORMAT_RGBA4444:
case DRM_FORMAT_BGRA4444:
return extract_alpha_mask_rgba16(dst, src, len, 0);
case DRM_FORMAT_ARGB4444:
case DRM_FORMAT_ABGR4444:
return extract_alpha_mask_rgba16(dst, src, len, 12);
#else
case DRM_FORMAT_RGBA1010102:
case DRM_FORMAT_BGRA1010102:
return extract_alpha_mask_rgba32(dst, src, len, 30, 3);
case DRM_FORMAT_ARGB2101010:
case DRM_FORMAT_ABGR2101010:
return extract_alpha_mask_rgba32(dst, src, len, 0, 3);
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_BGRA8888:
return extract_alpha_mask_rgba32(dst, src, len, 24, 0xff);
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_ABGR8888:
return extract_alpha_mask_rgba32(dst, src, len, 0, 0xff);
case DRM_FORMAT_RGBA4444:
case DRM_FORMAT_BGRA4444:
return extract_alpha_mask_rgba16(dst, src, len, 12);
case DRM_FORMAT_ARGB4444:
case DRM_FORMAT_ABGR4444:
return extract_alpha_mask_rgba16(dst, src, len, 0);
#endif
}
return false;
}
const char* drm_format_to_string(uint32_t fmt)
{
switch (fmt) {
#define X(x) case DRM_FORMAT_ ## x: return XSTR(x);
X(RGBA1010102) \
X(RGBX1010102) \
X(BGRA1010102) \
X(BGRX1010102) \
X(ARGB2101010) \
X(XRGB2101010) \
X(ABGR2101010) \
X(XBGR2101010) \
X(RGBA8888) \
X(RGBX8888) \
X(BGRA8888) \
X(BGRX8888) \
X(ARGB8888) \
X(XRGB8888) \
X(ABGR8888) \
X(XBGR8888) \
X(RGB888) \
X(BGR888) \
X(RGBA4444) \
X(RGBX4444) \
X(BGRA4444) \
X(BGRX4444) \
X(ARGB4444) \
X(XRGB4444) \
X(ABGR4444) \
X(XBGR4444) \
X(RGB565)
#undef X
}
return "UNKNOWN";
}
// Not exact, but close enough for debugging
const char* rfb_pixfmt_to_string(const struct rfb_pixel_format* fmt)
{
uint32_t profile = (fmt->red_shift << 16) | (fmt->green_shift << 8)
| (fmt->blue_shift);
switch (profile) {
#define CASE(r, g, b) case ((r << 16) | (g << 8) | b)
CASE(22, 10, 2): return "RGBX1010102";
CASE(2, 12, 22): return "BGRX1010102";
CASE(20, 10, 0): return "XRGB2101010";
CASE(0, 10, 20): return "XBGR2101010";
CASE(24, 16, 8): return "RGBX8888";
CASE(8, 16, 24): return "BGRX8888";
CASE(16, 8, 0): return "XRGB8888";
CASE(0, 8, 16): return "XBGR8888";
CASE(12, 8, 4): return "RGBX4444";
CASE(4, 8, 12): return "BGRX4444";
CASE(8, 4, 0): return "XRGB4444";
CASE(0, 4, 8): return "XBGR4444";
CASE(11, 5, 0): return "RGB565";
CASE(5, 2, 0): return "RGB332";
CASE(0, 2, 5): return "RGB332";
CASE(4, 2, 0): return "RGB222";
CASE(0, 2, 4): return "BGR222";
#undef CASE
}
return "UNKNOWN";
}
void make_rgb332_pal8_map(struct rfb_set_colour_map_entries_msg* msg)
{
msg->type = RFB_SERVER_TO_CLIENT_SET_COLOUR_MAP_ENTRIES;
msg->padding = 0;
msg->first_colour = htons(0);
msg->n_colours = htons(256);
for (unsigned int i = 0; i < 256; ++i) {
msg->colours[i].r = htons(round(65535.0 / 7.0 * ((i >> 5) & 7)));
msg->colours[i].g = htons(round(65535.0 / 7.0 * ((i >> 2) & 7)));
msg->colours[i].b = htons(round(65535.0 / 3.0 * (i & 3)));
}
}

View File

@ -3,7 +3,6 @@
* benchmarks.
*/
#include "util.h"
#include "neatvnc.h"
#include <stdlib.h>
@ -13,6 +12,8 @@
#include <assert.h>
#include <libdrm/drm_fourcc.h>
struct nvnc_fb* read_png_file(const char* filename);
struct nvnc_fb* read_png_file(const char* filename)
{
int width, height;
@ -72,8 +73,8 @@ struct nvnc_fb* read_png_file(const char* filename)
png_read_update_info(png, info);
size_t row_bytes = png_get_rowbytes(png, info);
assert(row_bytes == width * 4);
struct nvnc_fb* fb = nvnc_fb_new(width, height, DRM_FORMAT_ABGR8888);
struct nvnc_fb* fb = nvnc_fb_new(width, height, DRM_FORMAT_ABGR8888,
row_bytes / 4);
assert(fb);
uint8_t* addr = nvnc_fb_get_addr(fb);

245
src/qnum-to-evdev.c 100644
View File

@ -0,0 +1,245 @@
/*
* This file is auto-generated from keymaps.csv
* Database checksum sha256(76d68c10e97d37fe2ea459e210125ae41796253fb217e900bf2983ade13a7920)
* To re-generate, run:
* keymap-gen code-map --lang=stdc keymaps.csv qnum linux
*/
const unsigned short code_map_qnum_to_linux[254] = {
[0x1] = 0x1, /* qnum:1 -> linux:1 (KEY_ESC) -> linux:1 (KEY_ESC) */
[0x2] = 0x2, /* qnum:2 -> linux:2 (KEY_1) -> linux:2 (KEY_1) */
[0x3] = 0x3, /* qnum:3 -> linux:3 (KEY_2) -> linux:3 (KEY_2) */
[0x4] = 0x4, /* qnum:4 -> linux:4 (KEY_3) -> linux:4 (KEY_3) */
[0x5] = 0x5, /* qnum:5 -> linux:5 (KEY_4) -> linux:5 (KEY_4) */
[0x6] = 0x6, /* qnum:6 -> linux:6 (KEY_5) -> linux:6 (KEY_5) */
[0x7] = 0x7, /* qnum:7 -> linux:7 (KEY_6) -> linux:7 (KEY_6) */
[0x8] = 0x8, /* qnum:8 -> linux:8 (KEY_7) -> linux:8 (KEY_7) */
[0x9] = 0x9, /* qnum:9 -> linux:9 (KEY_8) -> linux:9 (KEY_8) */
[0xa] = 0xa, /* qnum:10 -> linux:10 (KEY_9) -> linux:10 (KEY_9) */
[0xb] = 0xb, /* qnum:11 -> linux:11 (KEY_0) -> linux:11 (KEY_0) */
[0xc] = 0xc, /* qnum:12 -> linux:12 (KEY_MINUS) -> linux:12 (KEY_MINUS) */
[0xd] = 0xd, /* qnum:13 -> linux:13 (KEY_EQUAL) -> linux:13 (KEY_EQUAL) */
[0xe] = 0xe, /* qnum:14 -> linux:14 (KEY_BACKSPACE) -> linux:14 (KEY_BACKSPACE) */
[0xf] = 0xf, /* qnum:15 -> linux:15 (KEY_TAB) -> linux:15 (KEY_TAB) */
[0x10] = 0x10, /* qnum:16 -> linux:16 (KEY_Q) -> linux:16 (KEY_Q) */
[0x11] = 0x11, /* qnum:17 -> linux:17 (KEY_W) -> linux:17 (KEY_W) */
[0x12] = 0x12, /* qnum:18 -> linux:18 (KEY_E) -> linux:18 (KEY_E) */
[0x13] = 0x13, /* qnum:19 -> linux:19 (KEY_R) -> linux:19 (KEY_R) */
[0x14] = 0x14, /* qnum:20 -> linux:20 (KEY_T) -> linux:20 (KEY_T) */
[0x15] = 0x15, /* qnum:21 -> linux:21 (KEY_Y) -> linux:21 (KEY_Y) */
[0x16] = 0x16, /* qnum:22 -> linux:22 (KEY_U) -> linux:22 (KEY_U) */
[0x17] = 0x17, /* qnum:23 -> linux:23 (KEY_I) -> linux:23 (KEY_I) */
[0x18] = 0x18, /* qnum:24 -> linux:24 (KEY_O) -> linux:24 (KEY_O) */
[0x19] = 0x19, /* qnum:25 -> linux:25 (KEY_P) -> linux:25 (KEY_P) */
[0x1a] = 0x1a, /* qnum:26 -> linux:26 (KEY_LEFTBRACE) -> linux:26 (KEY_LEFTBRACE) */
[0x1b] = 0x1b, /* qnum:27 -> linux:27 (KEY_RIGHTBRACE) -> linux:27 (KEY_RIGHTBRACE) */
[0x1c] = 0x1c, /* qnum:28 -> linux:28 (KEY_ENTER) -> linux:28 (KEY_ENTER) */
[0x1d] = 0x1d, /* qnum:29 -> linux:29 (KEY_LEFTCTRL) -> linux:29 (KEY_LEFTCTRL) */
[0x1e] = 0x1e, /* qnum:30 -> linux:30 (KEY_A) -> linux:30 (KEY_A) */
[0x1f] = 0x1f, /* qnum:31 -> linux:31 (KEY_S) -> linux:31 (KEY_S) */
[0x20] = 0x20, /* qnum:32 -> linux:32 (KEY_D) -> linux:32 (KEY_D) */
[0x21] = 0x21, /* qnum:33 -> linux:33 (KEY_F) -> linux:33 (KEY_F) */
[0x22] = 0x22, /* qnum:34 -> linux:34 (KEY_G) -> linux:34 (KEY_G) */
[0x23] = 0x23, /* qnum:35 -> linux:35 (KEY_H) -> linux:35 (KEY_H) */
[0x24] = 0x24, /* qnum:36 -> linux:36 (KEY_J) -> linux:36 (KEY_J) */
[0x25] = 0x25, /* qnum:37 -> linux:37 (KEY_K) -> linux:37 (KEY_K) */
[0x26] = 0x26, /* qnum:38 -> linux:38 (KEY_L) -> linux:38 (KEY_L) */
[0x27] = 0x27, /* qnum:39 -> linux:39 (KEY_SEMICOLON) -> linux:39 (KEY_SEMICOLON) */
[0x28] = 0x28, /* qnum:40 -> linux:40 (KEY_APOSTROPHE) -> linux:40 (KEY_APOSTROPHE) */
[0x29] = 0x29, /* qnum:41 -> linux:41 (KEY_GRAVE) -> linux:41 (KEY_GRAVE) */
[0x2a] = 0x2a, /* qnum:42 -> linux:42 (KEY_LEFTSHIFT) -> linux:42 (KEY_LEFTSHIFT) */
[0x2b] = 0x2b, /* qnum:43 -> linux:43 (KEY_BACKSLASH) -> linux:43 (KEY_BACKSLASH) */
[0x2c] = 0x2c, /* qnum:44 -> linux:44 (KEY_Z) -> linux:44 (KEY_Z) */
[0x2d] = 0x2d, /* qnum:45 -> linux:45 (KEY_X) -> linux:45 (KEY_X) */
[0x2e] = 0x2e, /* qnum:46 -> linux:46 (KEY_C) -> linux:46 (KEY_C) */
[0x2f] = 0x2f, /* qnum:47 -> linux:47 (KEY_V) -> linux:47 (KEY_V) */
[0x30] = 0x30, /* qnum:48 -> linux:48 (KEY_B) -> linux:48 (KEY_B) */
[0x31] = 0x31, /* qnum:49 -> linux:49 (KEY_N) -> linux:49 (KEY_N) */
[0x32] = 0x32, /* qnum:50 -> linux:50 (KEY_M) -> linux:50 (KEY_M) */
[0x33] = 0x33, /* qnum:51 -> linux:51 (KEY_COMMA) -> linux:51 (KEY_COMMA) */
[0x34] = 0x34, /* qnum:52 -> linux:52 (KEY_DOT) -> linux:52 (KEY_DOT) */
[0x35] = 0x35, /* qnum:53 -> linux:53 (KEY_SLASH) -> linux:53 (KEY_SLASH) */
[0x36] = 0x36, /* qnum:54 -> linux:54 (KEY_RIGHTSHIFT) -> linux:54 (KEY_RIGHTSHIFT) */
[0x37] = 0x37, /* qnum:55 -> linux:55 (KEY_KPASTERISK) -> linux:55 (KEY_KPASTERISK) */
[0x38] = 0x38, /* qnum:56 -> linux:56 (KEY_LEFTALT) -> linux:56 (KEY_LEFTALT) */
[0x39] = 0x39, /* qnum:57 -> linux:57 (KEY_SPACE) -> linux:57 (KEY_SPACE) */
[0x3a] = 0x3a, /* qnum:58 -> linux:58 (KEY_CAPSLOCK) -> linux:58 (KEY_CAPSLOCK) */
[0x3b] = 0x3b, /* qnum:59 -> linux:59 (KEY_F1) -> linux:59 (KEY_F1) */
[0x3c] = 0x3c, /* qnum:60 -> linux:60 (KEY_F2) -> linux:60 (KEY_F2) */
[0x3d] = 0x3d, /* qnum:61 -> linux:61 (KEY_F3) -> linux:61 (KEY_F3) */
[0x3e] = 0x3e, /* qnum:62 -> linux:62 (KEY_F4) -> linux:62 (KEY_F4) */
[0x3f] = 0x3f, /* qnum:63 -> linux:63 (KEY_F5) -> linux:63 (KEY_F5) */
[0x40] = 0x40, /* qnum:64 -> linux:64 (KEY_F6) -> linux:64 (KEY_F6) */
[0x41] = 0x41, /* qnum:65 -> linux:65 (KEY_F7) -> linux:65 (KEY_F7) */
[0x42] = 0x42, /* qnum:66 -> linux:66 (KEY_F8) -> linux:66 (KEY_F8) */
[0x43] = 0x43, /* qnum:67 -> linux:67 (KEY_F9) -> linux:67 (KEY_F9) */
[0x44] = 0x44, /* qnum:68 -> linux:68 (KEY_F10) -> linux:68 (KEY_F10) */
[0x45] = 0x45, /* qnum:69 -> linux:69 (KEY_NUMLOCK) -> linux:69 (KEY_NUMLOCK) */
[0x46] = 0x46, /* qnum:70 -> linux:70 (KEY_SCROLLLOCK) -> linux:70 (KEY_SCROLLLOCK) */
[0x47] = 0x47, /* qnum:71 -> linux:71 (KEY_KP7) -> linux:71 (KEY_KP7) */
[0x48] = 0x48, /* qnum:72 -> linux:72 (KEY_KP8) -> linux:72 (KEY_KP8) */
[0x49] = 0x49, /* qnum:73 -> linux:73 (KEY_KP9) -> linux:73 (KEY_KP9) */
[0x4a] = 0x4a, /* qnum:74 -> linux:74 (KEY_KPMINUS) -> linux:74 (KEY_KPMINUS) */
[0x4b] = 0x4b, /* qnum:75 -> linux:75 (KEY_KP4) -> linux:75 (KEY_KP4) */
[0x4c] = 0x4c, /* qnum:76 -> linux:76 (KEY_KP5) -> linux:76 (KEY_KP5) */
[0x4d] = 0x4d, /* qnum:77 -> linux:77 (KEY_KP6) -> linux:77 (KEY_KP6) */
[0x4e] = 0x4e, /* qnum:78 -> linux:78 (KEY_KPPLUS) -> linux:78 (KEY_KPPLUS) */
[0x4f] = 0x4f, /* qnum:79 -> linux:79 (KEY_KP1) -> linux:79 (KEY_KP1) */
[0x50] = 0x50, /* qnum:80 -> linux:80 (KEY_KP2) -> linux:80 (KEY_KP2) */
[0x51] = 0x51, /* qnum:81 -> linux:81 (KEY_KP3) -> linux:81 (KEY_KP3) */
[0x52] = 0x52, /* qnum:82 -> linux:82 (KEY_KP0) -> linux:82 (KEY_KP0) */
[0x53] = 0x53, /* qnum:83 -> linux:83 (KEY_KPDOT) -> linux:83 (KEY_KPDOT) */
[0x54] = 0x63, /* qnum:84 -> linux:99 (KEY_SYSRQ) -> linux:99 (KEY_SYSRQ) */
[0x55] = 0xba, /* qnum:85 -> linux:186 (KEY_F16) -> linux:186 (KEY_F16) */
[0x56] = 0x56, /* qnum:86 -> linux:86 (KEY_102ND) -> linux:86 (KEY_102ND) */
[0x57] = 0x57, /* qnum:87 -> linux:87 (KEY_F11) -> linux:87 (KEY_F11) */
[0x58] = 0x58, /* qnum:88 -> linux:88 (KEY_F12) -> linux:88 (KEY_F12) */
[0x59] = 0x75, /* qnum:89 -> linux:117 (KEY_KPEQUAL) -> linux:117 (KEY_KPEQUAL) */
[0x5a] = 0xbe, /* qnum:90 -> linux:190 (KEY_F20) -> linux:190 (KEY_F20) */
[0x5b] = 0x65, /* qnum:91 -> linux:101 (KEY_LINEFEED) -> linux:101 (KEY_LINEFEED) */
[0x5c] = 0x5f, /* qnum:92 -> linux:95 (KEY_KPJPCOMMA) -> linux:95 (KEY_KPJPCOMMA) */
[0x5d] = 0xb7, /* qnum:93 -> linux:183 (KEY_F13) -> linux:183 (KEY_F13) */
[0x5e] = 0xb8, /* qnum:94 -> linux:184 (KEY_F14) -> linux:184 (KEY_F14) */
[0x5f] = 0xb9, /* qnum:95 -> linux:185 (KEY_F15) -> linux:185 (KEY_F15) */
[0x63] = 0xa9, /* qnum:99 -> linux:169 (KEY_PHONE) -> linux:169 (KEY_PHONE) */
[0x64] = 0x86, /* qnum:100 -> linux:134 (KEY_OPEN) -> linux:134 (KEY_OPEN) */
[0x65] = 0x87, /* qnum:101 -> linux:135 (KEY_PASTE) -> linux:135 (KEY_PASTE) */
[0x66] = 0x8d, /* qnum:102 -> linux:141 (KEY_SETUP) -> linux:141 (KEY_SETUP) */
[0x67] = 0x90, /* qnum:103 -> linux:144 (KEY_FILE) -> linux:144 (KEY_FILE) */
[0x68] = 0x91, /* qnum:104 -> linux:145 (KEY_SENDFILE) -> linux:145 (KEY_SENDFILE) */
[0x69] = 0x92, /* qnum:105 -> linux:146 (KEY_DELETEFILE) -> linux:146 (KEY_DELETEFILE) */
[0x6a] = 0x97, /* qnum:106 -> linux:151 (KEY_MSDOS) -> linux:151 (KEY_MSDOS) */
[0x6b] = 0x99, /* qnum:107 -> linux:153 (KEY_DIRECTION) -> linux:153 (KEY_DIRECTION) */
[0x6c] = 0xa1, /* qnum:108 -> linux:161 (KEY_EJECTCD) -> linux:161 (KEY_EJECTCD) */
[0x6d] = 0xc1, /* qnum:109 -> linux:193 (KEY_F23) -> linux:193 (KEY_F23) */
[0x6f] = 0xc2, /* qnum:111 -> linux:194 (KEY_F24) -> linux:194 (KEY_F24) */
[0x70] = 0x5d, /* qnum:112 -> linux:93 (KEY_KATAKANAHIRAGANA) -> linux:93 (KEY_KATAKANAHIRAGANA) */
[0x71] = 0x7b, /* qnum:113 -> linux:123 (KEY_HANJA) -> linux:123 (KEY_HANJA) */
[0x72] = 0x7a, /* qnum:114 -> linux:122 (KEY_HANGEUL) -> linux:122 (KEY_HANGEUL) */
[0x73] = 0x59, /* qnum:115 -> linux:89 (KEY_RO) -> linux:89 (KEY_RO) */
[0x74] = 0xbf, /* qnum:116 -> linux:191 (KEY_F21) -> linux:191 (KEY_F21) */
[0x75] = 0xb1, /* qnum:117 -> linux:177 (KEY_SCROLLUP) -> linux:177 (KEY_SCROLLUP) */
[0x76] = 0x55, /* qnum:118 -> linux:85 (KEY_ZENKAKUHANKAKU) -> linux:85 (KEY_ZENKAKUHANKAKU) */
[0x77] = 0x5b, /* qnum:119 -> linux:91 (KEY_HIRAGANA) -> linux:91 (KEY_HIRAGANA) */
[0x78] = 0x5a, /* qnum:120 -> linux:90 (KEY_KATAKANA) -> linux:90 (KEY_KATAKANA) */
[0x79] = 0x5c, /* qnum:121 -> linux:92 (KEY_HENKAN) -> linux:92 (KEY_HENKAN) */
[0x7b] = 0x5e, /* qnum:123 -> linux:94 (KEY_MUHENKAN) -> linux:94 (KEY_MUHENKAN) */
[0x7d] = 0x7c, /* qnum:125 -> linux:124 (KEY_YEN) -> linux:124 (KEY_YEN) */
[0x7e] = 0x79, /* qnum:126 -> linux:121 (KEY_KPCOMMA) -> linux:121 (KEY_KPCOMMA) */
[0x81] = 0xab, /* qnum:129 -> linux:171 (KEY_CONFIG) -> linux:171 (KEY_CONFIG) */
[0x82] = 0x96, /* qnum:130 -> linux:150 (KEY_WWW) -> linux:150 (KEY_WWW) */
[0x83] = 0xbb, /* qnum:131 -> linux:187 (KEY_F17) -> linux:187 (KEY_F17) */
[0x84] = 0xbd, /* qnum:132 -> linux:189 (KEY_F19) -> linux:189 (KEY_F19) */
[0x85] = 0x81, /* qnum:133 -> linux:129 (KEY_AGAIN) -> linux:129 (KEY_AGAIN) */
[0x86] = 0x82, /* qnum:134 -> linux:130 (KEY_PROPS) -> linux:130 (KEY_PROPS) */
[0x87] = 0x83, /* qnum:135 -> linux:131 (KEY_UNDO) -> linux:131 (KEY_UNDO) */
[0x88] = 0xb0, /* qnum:136 -> linux:176 (KEY_EDIT) -> linux:176 (KEY_EDIT) */
[0x89] = 0xb5, /* qnum:137 -> linux:181 (KEY_NEW) -> linux:181 (KEY_NEW) */
[0x8a] = 0xb6, /* qnum:138 -> linux:182 (KEY_REDO) -> linux:182 (KEY_REDO) */
[0x8b] = 0x78, /* qnum:139 -> linux:120 (KEY_SCALE) -> linux:120 (KEY_SCALE) */
[0x8c] = 0x84, /* qnum:140 -> linux:132 (KEY_FRONT) -> linux:132 (KEY_FRONT) */
[0x8e] = 0xe9, /* qnum:142 -> linux:233 (KEY_FORWARDMAIL) -> linux:233 (KEY_FORWARDMAIL) */
[0x8f] = 0xb2, /* qnum:143 -> linux:178 (KEY_SCROLLDOWN) -> linux:178 (KEY_SCROLLDOWN) */
[0x90] = 0xa5, /* qnum:144 -> linux:165 (KEY_PREVIOUSSONG) -> linux:165 (KEY_PREVIOUSSONG) */
[0x92] = 0x98, /* qnum:146 -> linux:152 (KEY_SCREENLOCK) -> linux:152 (KEY_SCREENLOCK) */
[0x93] = 0x93, /* qnum:147 -> linux:147 (KEY_XFER) -> linux:147 (KEY_XFER) */
[0x94] = 0xde, /* qnum:148 -> linux:222 (KEY_ALTERASE) -> linux:222 (KEY_ALTERASE) */
[0x95] = 0xc3, /* qnum:149 -> linux:195 (unnamed) -> linux:195 (unnamed) */
[0x96] = 0xc4, /* qnum:150 -> linux:196 (unnamed) -> linux:196 (unnamed) */
[0x97] = 0x95, /* qnum:151 -> linux:149 (KEY_PROG2) -> linux:149 (KEY_PROG2) */
[0x98] = 0xa8, /* qnum:152 -> linux:168 (KEY_REWIND) -> linux:168 (KEY_REWIND) */
[0x99] = 0xa3, /* qnum:153 -> linux:163 (KEY_NEXTSONG) -> linux:163 (KEY_NEXTSONG) */
[0x9a] = 0xc5, /* qnum:154 -> linux:197 (unnamed) -> linux:197 (unnamed) */
[0x9b] = 0xc6, /* qnum:155 -> linux:198 (unnamed) -> linux:198 (unnamed) */
[0x9c] = 0x60, /* qnum:156 -> linux:96 (KEY_KPENTER) -> linux:96 (KEY_KPENTER) */
[0x9d] = 0x61, /* qnum:157 -> linux:97 (KEY_RIGHTCTRL) -> linux:97 (KEY_RIGHTCTRL) */
[0x9e] = 0x8b, /* qnum:158 -> linux:139 (KEY_MENU) -> linux:139 (KEY_MENU) */
[0x9f] = 0x94, /* qnum:159 -> linux:148 (KEY_PROG1) -> linux:148 (KEY_PROG1) */
[0xa0] = 0x71, /* qnum:160 -> linux:113 (KEY_MUTE) -> linux:113 (KEY_MUTE) */
[0xa1] = 0x8c, /* qnum:161 -> linux:140 (KEY_CALC) -> linux:140 (KEY_CALC) */
[0xa2] = 0xa4, /* qnum:162 -> linux:164 (KEY_PLAYPAUSE) -> linux:164 (KEY_PLAYPAUSE) */
[0xa3] = 0xa0, /* qnum:163 -> linux:160 (KEY_CLOSECD) -> linux:160 (KEY_CLOSECD) */
[0xa4] = 0xa6, /* qnum:164 -> linux:166 (KEY_STOPCD) -> linux:166 (KEY_STOPCD) */
[0xa5] = 0xcd, /* qnum:165 -> linux:205 (KEY_SUSPEND) -> linux:205 (KEY_SUSPEND) */
[0xa6] = 0x9a, /* qnum:166 -> linux:154 (KEY_CYCLEWINDOWS) -> linux:154 (KEY_CYCLEWINDOWS) */
[0xa7] = 0xc7, /* qnum:167 -> linux:199 (unnamed) -> linux:199 (unnamed) */
[0xa8] = 0xc8, /* qnum:168 -> linux:200 (KEY_PLAYCD) -> linux:200 (KEY_PLAYCD) */
[0xa9] = 0xc9, /* qnum:169 -> linux:201 (KEY_PAUSECD) -> linux:201 (KEY_PAUSECD) */
[0xab] = 0xca, /* qnum:171 -> linux:202 (KEY_PROG3) -> linux:202 (KEY_PROG3) */
[0xac] = 0xcb, /* qnum:172 -> linux:203 (KEY_PROG4) -> linux:203 (KEY_PROG4) */
[0xad] = 0xcc, /* qnum:173 -> linux:204 (KEY_DASHBOARD) -> linux:204 (KEY_DASHBOARD) */
[0xae] = 0x72, /* qnum:174 -> linux:114 (KEY_VOLUMEDOWN) -> linux:114 (KEY_VOLUMEDOWN) */
[0xaf] = 0xce, /* qnum:175 -> linux:206 (KEY_CLOSE) -> linux:206 (KEY_CLOSE) */
[0xb0] = 0x73, /* qnum:176 -> linux:115 (KEY_VOLUMEUP) -> linux:115 (KEY_VOLUMEUP) */
[0xb1] = 0xa7, /* qnum:177 -> linux:167 (KEY_RECORD) -> linux:167 (KEY_RECORD) */
[0xb2] = 0xac, /* qnum:178 -> linux:172 (KEY_HOMEPAGE) -> linux:172 (KEY_HOMEPAGE) */
[0xb3] = 0xcf, /* qnum:179 -> linux:207 (KEY_PLAY) -> linux:207 (KEY_PLAY) */
[0xb4] = 0xd0, /* qnum:180 -> linux:208 (KEY_FASTFORWARD) -> linux:208 (KEY_FASTFORWARD) */
[0xb5] = 0x62, /* qnum:181 -> linux:98 (KEY_KPSLASH) -> linux:98 (KEY_KPSLASH) */
[0xb6] = 0xd1, /* qnum:182 -> linux:209 (KEY_BASSBOOST) -> linux:209 (KEY_BASSBOOST) */
[0xb7] = 0x63, /* qnum:183 -> linux:99 (KEY_SYSRQ) -> linux:99 (KEY_SYSRQ) */
[0xb8] = 0x64, /* qnum:184 -> linux:100 (KEY_RIGHTALT) -> linux:100 (KEY_RIGHTALT) */
[0xb9] = 0xd2, /* qnum:185 -> linux:210 (KEY_PRINT) -> linux:210 (KEY_PRINT) */
[0xba] = 0xd3, /* qnum:186 -> linux:211 (KEY_HP) -> linux:211 (KEY_HP) */
[0xbb] = 0xd4, /* qnum:187 -> linux:212 (KEY_CAMERA) -> linux:212 (KEY_CAMERA) */
[0xbc] = 0x89, /* qnum:188 -> linux:137 (KEY_CUT) -> linux:137 (KEY_CUT) */
[0xbd] = 0xd5, /* qnum:189 -> linux:213 (KEY_SOUND) -> linux:213 (KEY_SOUND) */
[0xbe] = 0xd6, /* qnum:190 -> linux:214 (KEY_QUESTION) -> linux:214 (KEY_QUESTION) */
[0xbf] = 0xd7, /* qnum:191 -> linux:215 (KEY_EMAIL) -> linux:215 (KEY_EMAIL) */
[0xc0] = 0xd8, /* qnum:192 -> linux:216 (KEY_CHAT) -> linux:216 (KEY_CHAT) */
[0xc1] = 0x88, /* qnum:193 -> linux:136 (KEY_FIND) -> linux:136 (KEY_FIND) */
[0xc2] = 0xda, /* qnum:194 -> linux:218 (KEY_CONNECT) -> linux:218 (KEY_CONNECT) */
[0xc3] = 0xdb, /* qnum:195 -> linux:219 (KEY_FINANCE) -> linux:219 (KEY_FINANCE) */
[0xc4] = 0xdc, /* qnum:196 -> linux:220 (KEY_SPORT) -> linux:220 (KEY_SPORT) */
[0xc5] = 0xdd, /* qnum:197 -> linux:221 (KEY_SHOP) -> linux:221 (KEY_SHOP) */
[0xc6] = 0x77, /* qnum:198 -> linux:119 (KEY_PAUSE) -> linux:119 (KEY_PAUSE) */
[0xc7] = 0x66, /* qnum:199 -> linux:102 (KEY_HOME) -> linux:102 (KEY_HOME) */
[0xc8] = 0x67, /* qnum:200 -> linux:103 (KEY_UP) -> linux:103 (KEY_UP) */
[0xc9] = 0x68, /* qnum:201 -> linux:104 (KEY_PAGEUP) -> linux:104 (KEY_PAGEUP) */
[0xca] = 0xdf, /* qnum:202 -> linux:223 (KEY_CANCEL) -> linux:223 (KEY_CANCEL) */
[0xcb] = 0x69, /* qnum:203 -> linux:105 (KEY_LEFT) -> linux:105 (KEY_LEFT) */
[0xcc] = 0xe0, /* qnum:204 -> linux:224 (KEY_BRIGHTNESSDOWN) -> linux:224 (KEY_BRIGHTNESSDOWN) */
[0xcd] = 0x6a, /* qnum:205 -> linux:106 (KEY_RIGHT) -> linux:106 (KEY_RIGHT) */
[0xce] = 0x76, /* qnum:206 -> linux:118 (KEY_KPPLUSMINUS) -> linux:118 (KEY_KPPLUSMINUS) */
[0xcf] = 0x6b, /* qnum:207 -> linux:107 (KEY_END) -> linux:107 (KEY_END) */
[0xd0] = 0x6c, /* qnum:208 -> linux:108 (KEY_DOWN) -> linux:108 (KEY_DOWN) */
[0xd1] = 0x6d, /* qnum:209 -> linux:109 (KEY_PAGEDOWN) -> linux:109 (KEY_PAGEDOWN) */
[0xd2] = 0x6e, /* qnum:210 -> linux:110 (KEY_INSERT) -> linux:110 (KEY_INSERT) */
[0xd3] = 0x6f, /* qnum:211 -> linux:111 (KEY_DELETE) -> linux:111 (KEY_DELETE) */
[0xd4] = 0xe1, /* qnum:212 -> linux:225 (KEY_BRIGHTNESSUP) -> linux:225 (KEY_BRIGHTNESSUP) */
[0xd5] = 0xea, /* qnum:213 -> linux:234 (KEY_SAVE) -> linux:234 (KEY_SAVE) */
[0xd6] = 0xe3, /* qnum:214 -> linux:227 (KEY_SWITCHVIDEOMODE) -> linux:227 (KEY_SWITCHVIDEOMODE) */
[0xd7] = 0xe4, /* qnum:215 -> linux:228 (KEY_KBDILLUMTOGGLE) -> linux:228 (KEY_KBDILLUMTOGGLE) */
[0xd8] = 0xe5, /* qnum:216 -> linux:229 (KEY_KBDILLUMDOWN) -> linux:229 (KEY_KBDILLUMDOWN) */
[0xd9] = 0xe6, /* qnum:217 -> linux:230 (KEY_KBDILLUMUP) -> linux:230 (KEY_KBDILLUMUP) */
[0xda] = 0xe7, /* qnum:218 -> linux:231 (KEY_SEND) -> linux:231 (KEY_SEND) */
[0xdb] = 0x7d, /* qnum:219 -> linux:125 (KEY_LEFTMETA) -> linux:125 (KEY_LEFTMETA) */
[0xdc] = 0x7e, /* qnum:220 -> linux:126 (KEY_RIGHTMETA) -> linux:126 (KEY_RIGHTMETA) */
[0xdd] = 0x7f, /* qnum:221 -> linux:127 (KEY_COMPOSE) -> linux:127 (KEY_COMPOSE) */
[0xde] = 0x74, /* qnum:222 -> linux:116 (KEY_POWER) -> linux:116 (KEY_POWER) */
[0xdf] = 0x8e, /* qnum:223 -> linux:142 (KEY_SLEEP) -> linux:142 (KEY_SLEEP) */
[0xe3] = 0x8f, /* qnum:227 -> linux:143 (KEY_WAKEUP) -> linux:143 (KEY_WAKEUP) */
[0xe4] = 0xe8, /* qnum:228 -> linux:232 (KEY_REPLY) -> linux:232 (KEY_REPLY) */
[0xe5] = 0xd9, /* qnum:229 -> linux:217 (KEY_SEARCH) -> linux:217 (KEY_SEARCH) */
[0xe6] = 0x9c, /* qnum:230 -> linux:156 (KEY_BOOKMARKS) -> linux:156 (KEY_BOOKMARKS) */
[0xe7] = 0xad, /* qnum:231 -> linux:173 (KEY_REFRESH) -> linux:173 (KEY_REFRESH) */
[0xe8] = 0x80, /* qnum:232 -> linux:128 (KEY_STOP) -> linux:128 (KEY_STOP) */
[0xe9] = 0x9f, /* qnum:233 -> linux:159 (KEY_FORWARD) -> linux:159 (KEY_FORWARD) */
[0xea] = 0x9e, /* qnum:234 -> linux:158 (KEY_BACK) -> linux:158 (KEY_BACK) */
[0xeb] = 0x9d, /* qnum:235 -> linux:157 (KEY_COMPUTER) -> linux:157 (KEY_COMPUTER) */
[0xec] = 0x9b, /* qnum:236 -> linux:155 (KEY_MAIL) -> linux:155 (KEY_MAIL) */
[0xed] = 0xe2, /* qnum:237 -> linux:226 (KEY_MEDIA) -> linux:226 (KEY_MEDIA) */
[0xef] = 0x70, /* qnum:239 -> linux:112 (KEY_MACRO) -> linux:112 (KEY_MACRO) */
[0xf0] = 0xeb, /* qnum:240 -> linux:235 (KEY_DOCUMENTS) -> linux:235 (KEY_DOCUMENTS) */
[0xf1] = 0xec, /* qnum:241 -> linux:236 (KEY_BATTERY) -> linux:236 (KEY_BATTERY) */
[0xf2] = 0xed, /* qnum:242 -> linux:237 (KEY_BLUETOOTH) -> linux:237 (KEY_BLUETOOTH) */
[0xf3] = 0xee, /* qnum:243 -> linux:238 (KEY_WLAN) -> linux:238 (KEY_WLAN) */
[0xf4] = 0xef, /* qnum:244 -> linux:239 (KEY_UWB) -> linux:239 (KEY_UWB) */
[0xf5] = 0x8a, /* qnum:245 -> linux:138 (KEY_HELP) -> linux:138 (KEY_HELP) */
[0xf6] = 0xb3, /* qnum:246 -> linux:179 (KEY_KPLEFTPAREN) -> linux:179 (KEY_KPLEFTPAREN) */
[0xf7] = 0xbc, /* qnum:247 -> linux:188 (KEY_F18) -> linux:188 (KEY_F18) */
[0xf8] = 0x85, /* qnum:248 -> linux:133 (KEY_COPY) -> linux:133 (KEY_COPY) */
[0xf9] = 0xc0, /* qnum:249 -> linux:192 (KEY_F22) -> linux:192 (KEY_F22) */
[0xfb] = 0xb4, /* qnum:251 -> linux:180 (KEY_KPRIGHTPAREN) -> linux:180 (KEY_KPRIGHTPAREN) */
[0xfd] = 0xa2, /* qnum:253 -> linux:162 (KEY_EJECTCLOSECD) -> linux:162 (KEY_EJECTCLOSECD) */
};
const unsigned int code_map_qnum_to_linux_len = sizeof(code_map_qnum_to_linux)/sizeof(code_map_qnum_to_linux[0]);

View File

@ -1,31 +1,78 @@
/*
* Copyright (c) 2019 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "neatvnc.h"
#include "rfb-proto.h"
#include "vec.h"
#include "fb.h"
#include "pixels.h"
#include "enc-util.h"
#include "encoder.h"
#include "rcbuf.h"
#include <stdlib.h>
#include <pixman.h>
#include <aml.h>
int raw_encode_box(struct vec* dst, const struct rfb_pixel_format* dst_fmt,
struct encoder* raw_encoder_new(void);
struct raw_encoder {
struct encoder encoder;
struct rfb_pixel_format output_format;
struct aml_work* work;
};
struct raw_encoder_work {
struct raw_encoder* parent;
struct rfb_pixel_format output_format;
struct nvnc_fb* fb;
struct pixman_region16 damage;
int n_rects;
uint16_t x_pos, y_pos;
struct rcbuf *result;
};
struct encoder_impl encoder_impl_raw;
static inline struct raw_encoder* raw_encoder(struct encoder* encoder)
{
assert(encoder->impl == &encoder_impl_raw);
return (struct raw_encoder*)encoder;
}
static int raw_encode_box(struct raw_encoder_work* ctx, struct vec* dst,
const struct rfb_pixel_format* dst_fmt,
const struct nvnc_fb* fb,
const struct rfb_pixel_format* src_fmt, int x_start,
int y_start, int stride, int width, int height)
{
uint16_t x_pos = ctx->x_pos;
uint16_t y_pos = ctx->y_pos;
int rc = -1;
struct rfb_server_fb_rect rect = {
.encoding = htonl(RFB_ENCODING_RAW),
.x = htons(x_start),
.y = htons(y_start),
.width = htons(width),
.height = htons(height),
};
rc = vec_append(dst, &rect, sizeof(rect));
rc = encode_rect_head(dst, RFB_ENCODING_RAW, x_pos + x_start,
y_pos + y_start, width, height);
if (rc < 0)
return -1;
uint32_t* b = fb->addr;
uint8_t* b = fb->addr;
int32_t src_bpp = src_fmt->bits_per_pixel / 8;
int32_t xoff = x_start * src_bpp;
int32_t src_stride = fb->stride * src_bpp;
int bpp = dst_fmt->bits_per_pixel / 8;
@ -36,8 +83,8 @@ int raw_encode_box(struct vec* dst, const struct rfb_pixel_format* dst_fmt,
uint8_t* d = dst->data;
for (int y = y_start; y < y_start + height; ++y) {
pixel32_to_cpixel(d + dst->len, dst_fmt,
b + x_start + y * stride, src_fmt,
pixel_to_cpixel(d + dst->len, dst_fmt,
b + xoff + y * src_stride, src_fmt,
bpp, width);
dst->len += width * bpp;
}
@ -45,8 +92,8 @@ int raw_encode_box(struct vec* dst, const struct rfb_pixel_format* dst_fmt,
return 0;
}
int raw_encode_frame(struct vec* dst, const struct rfb_pixel_format* dst_fmt,
const struct nvnc_fb* src,
static int raw_encode_frame(struct raw_encoder_work* ctx, struct vec* dst,
const struct rfb_pixel_format* dst_fmt, struct nvnc_fb* src,
const struct rfb_pixel_format* src_fmt,
struct pixman_region16* region)
{
@ -59,16 +106,7 @@ int raw_encode_frame(struct vec* dst, const struct rfb_pixel_format* dst_fmt,
n_rects = 1;
}
struct rfb_server_fb_update_msg head = {
.type = RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE,
.n_rects = htons(n_rects),
};
rc = vec_reserve(dst, src->width * src->height * 4);
if (rc < 0)
return -1;
rc = vec_append(dst, &head, sizeof(head));
rc = nvnc_fb_map(src);
if (rc < 0)
return -1;
@ -78,11 +116,137 @@ int raw_encode_frame(struct vec* dst, const struct rfb_pixel_format* dst_fmt,
int box_width = box[i].x2 - x;
int box_height = box[i].y2 - y;
rc = raw_encode_box(dst, dst_fmt, src, src_fmt, x, y,
src->width, box_width, box_height);
rc = raw_encode_box(ctx, dst, dst_fmt, src, src_fmt, x, y,
src->stride, box_width, box_height);
if (rc < 0)
return -1;
}
ctx->n_rects = n_rects;
return 0;
}
static void raw_encoder_do_work(void* obj)
{
struct raw_encoder_work* ctx = aml_get_userdata(obj);
int rc;
struct nvnc_fb* fb = ctx->fb;
assert(fb);
size_t bpp = ctx->output_format.bits_per_pixel / 8;
size_t n_rects = pixman_region_n_rects(&ctx->damage);
if (n_rects > UINT16_MAX)
n_rects = 1;
size_t buffer_size = calculate_region_area(&ctx->damage) * bpp
+ n_rects * sizeof(struct rfb_server_fb_rect);
struct vec dst;
rc = vec_init(&dst, buffer_size);
assert(rc == 0);
struct rfb_pixel_format src_fmt;
rc = rfb_pixfmt_from_fourcc(&src_fmt, nvnc_fb_get_fourcc_format(fb));
assert(rc == 0);
rc = raw_encode_frame(ctx, &dst, &ctx->output_format, fb, &src_fmt,
&ctx->damage);
assert(rc == 0);
ctx->result = rcbuf_new(dst.data, dst.len);
assert(ctx->result);
}
static void raw_encoder_on_done(void* obj)
{
struct raw_encoder_work* ctx = aml_get_userdata(obj);
struct raw_encoder* self = ctx->parent;
assert(ctx->result);
self->encoder.n_rects = ctx->n_rects;
aml_unref(self->work);
self->work = NULL;
uint64_t pts = nvnc_fb_get_pts(ctx->fb);
encoder_finish_frame(&self->encoder, ctx->result, pts);
}
struct encoder* raw_encoder_new(void)
{
struct raw_encoder* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
encoder_init(&self->encoder, &encoder_impl_raw);
return (struct encoder*)self;
}
static void raw_encoder_destroy(struct encoder* encoder)
{
struct raw_encoder* self = raw_encoder(encoder);
if (self->work) {
aml_stop(aml_get_default(), self->work);
aml_unref(self->work);
}
free(self);
}
static void raw_encoder_set_output_format(struct encoder* encoder,
const struct rfb_pixel_format* pixfmt)
{
struct raw_encoder* self = raw_encoder(encoder);
memcpy(&self->output_format, pixfmt, sizeof(self->output_format));
}
static void raw_encoder_work_destroy(void* obj)
{
struct raw_encoder_work* ctx = obj;
nvnc_fb_unref(ctx->fb);
pixman_region_fini(&ctx->damage);
if (ctx->result)
rcbuf_unref(ctx->result);
free(ctx);
}
static int raw_encoder_encode(struct encoder* encoder, struct nvnc_fb* fb,
struct pixman_region16* damage)
{
struct raw_encoder* self = raw_encoder(encoder);
struct raw_encoder_work* ctx = calloc(1, sizeof(*ctx));
if (!ctx)
return -1;
self->work = aml_work_new(raw_encoder_do_work, raw_encoder_on_done,
ctx, raw_encoder_work_destroy);
if (!self->work) {
free(ctx);
return -1;
}
ctx->parent = self;
ctx->fb = fb;
memcpy(&ctx->output_format, &self->output_format,
sizeof(ctx->output_format));
ctx->x_pos = self->encoder.x_pos;
ctx->y_pos = self->encoder.y_pos;
nvnc_fb_ref(ctx->fb);
pixman_region_copy(&ctx->damage, damage);
int rc = aml_start(aml_get_default(), self->work);
if (rc < 0) {
aml_unref(self->work);
self->work = NULL;
}
return rc;
}
struct encoder_impl encoder_impl_raw = {
.destroy = raw_encoder_destroy,
.set_output_format = raw_encoder_set_output_format,
.encode = raw_encoder_encode,
};

243
src/resampler.c 100644
View File

@ -0,0 +1,243 @@
/*
* Copyright (c) 2021 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "resampler.h"
#include "neatvnc.h"
#include "fb.h"
#include "transform-util.h"
#include "pixels.h"
#include "usdt.h"
#include <stdlib.h>
#include <aml.h>
#include <pixman.h>
#include <assert.h>
#include <libdrm/drm_fourcc.h>
struct fb_side_data {
struct pixman_region16 buffer_damage;
LIST_ENTRY(fb_side_data) link;
};
LIST_HEAD(fb_side_data_list, fb_side_data);
struct resampler {
struct nvnc_fb_pool *pool;
struct fb_side_data_list fb_side_data_list;
};
struct resampler_work {
struct pixman_region16 frame_damage;
struct nvnc_fb* src;
struct nvnc_fb* dst;
resampler_fn on_done;
void* userdata;
};
static void fb_side_data_destroy(void* userdata)
{
struct fb_side_data* fb_side_data = userdata;
LIST_REMOVE(fb_side_data, link);
pixman_region_fini(&fb_side_data->buffer_damage);
free(fb_side_data);
}
static void resampler_damage_all_buffers(struct resampler* self,
struct pixman_region16* region)
{
struct fb_side_data *item;
LIST_FOREACH(item, &self->fb_side_data_list, link)
pixman_region_union(&item->buffer_damage, &item->buffer_damage,
region);
}
static void resampler_work_free(void* userdata)
{
struct resampler_work* work = userdata;
nvnc_fb_release(work->src);
nvnc_fb_unref(work->src);
nvnc_fb_unref(work->dst);
pixman_region_fini(&work->frame_damage);
free(work);
}
struct resampler* resampler_create(void)
{
struct resampler* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->pool = nvnc_fb_pool_new(0, 0, DRM_FORMAT_INVALID, 0);
if (!self->pool) {
free(self);
return NULL;
}
LIST_INIT(&self->fb_side_data_list);
return self;
}
void resampler_destroy(struct resampler* self)
{
nvnc_fb_pool_unref(self->pool);
free(self);
}
void resample_now(struct nvnc_fb* dst, struct nvnc_fb* src,
struct pixman_region16* damage)
{
assert(dst->transform == NVNC_TRANSFORM_NORMAL);
bool ok __attribute__((unused));
pixman_format_code_t dst_fmt = 0;
ok = fourcc_to_pixman_fmt(&dst_fmt, dst->fourcc_format);
assert(ok);
pixman_image_t* dstimg = pixman_image_create_bits_no_clear(
dst_fmt, dst->width, dst->height, dst->addr,
nvnc_fb_get_pixel_size(dst) * dst->stride);
pixman_format_code_t src_fmt = 0;
ok = fourcc_to_pixman_fmt(&src_fmt, src->fourcc_format);
assert(ok);
pixman_image_t* srcimg = pixman_image_create_bits_no_clear(
src_fmt, src->width, src->height, src->addr,
nvnc_fb_get_pixel_size(src) * src->stride);
pixman_transform_t pxform;
nvnc_transform_to_pixman_transform(&pxform, src->transform,
src->width, src->height);
pixman_image_set_transform(srcimg, &pxform);
/* Side data contains the union of the buffer damage and the frame
* damage.
*/
if (damage) {
pixman_image_set_clip_region(dstimg, damage);
}
pixman_image_composite(PIXMAN_OP_OVER, srcimg, NULL, dstimg,
0, 0,
0, 0,
0, 0,
dst->width, dst->height);
pixman_image_unref(srcimg);
pixman_image_unref(dstimg);
}
static void do_work(void* handle)
{
struct aml_work* work = handle;
struct resampler_work* ctx = aml_get_userdata(work);
struct nvnc_fb* src = ctx->src;
struct nvnc_fb* dst = ctx->dst;
struct fb_side_data* dst_side_data = nvnc_get_userdata(dst);
resample_now(dst, src, &dst_side_data->buffer_damage);
}
static void on_work_done(void* handle)
{
struct aml_work* work = handle;
struct resampler_work* ctx = aml_get_userdata(work);
ctx->on_done(ctx->dst, &ctx->frame_damage, ctx->userdata);
}
int resampler_feed(struct resampler* self, struct nvnc_fb* fb,
struct pixman_region16* damage, resampler_fn on_done,
void* userdata)
{
DTRACE_PROBE2(neatvnc, resampler_feed, self, fb->pts);
if (fb->transform == NVNC_TRANSFORM_NORMAL) {
on_done(fb, damage, userdata);
return 0;
}
uint32_t width = fb->width;
uint32_t height = fb->height;
nvnc_transform_dimensions(fb->transform, &width, &height);
nvnc_fb_pool_resize(self->pool, width, height, fb->fourcc_format,
width);
struct aml* aml = aml_get_default();
assert(aml);
struct resampler_work* ctx = calloc(1, sizeof(*ctx));
if (!ctx)
return -1;
pixman_region_init(&ctx->frame_damage);
pixman_region_copy(&ctx->frame_damage, damage);
ctx->dst = nvnc_fb_pool_acquire(self->pool);
if (!ctx->dst)
goto acquire_failure;
struct fb_side_data* fb_side_data = nvnc_get_userdata(fb);
if (!fb_side_data) {
fb_side_data = calloc(1, sizeof(*fb_side_data));
if (!fb_side_data)
goto side_data_failure;
/* This is a new buffer, so the whole surface is damaged. */
pixman_region_init_rect(&fb_side_data->buffer_damage, 0, 0,
width, height);
nvnc_set_userdata(fb, fb_side_data, fb_side_data_destroy);
LIST_INSERT_HEAD(&self->fb_side_data_list, fb_side_data, link);
}
resampler_damage_all_buffers(self, damage);
ctx->src = fb;
nvnc_fb_ref(fb);
nvnc_fb_hold(fb);
ctx->on_done = on_done;
ctx->userdata = userdata;
struct aml_work* work = aml_work_new(do_work, on_work_done, ctx,
resampler_work_free);
if (!work) {
resampler_work_free(ctx);
return -1;
}
nvnc_fb_map(fb);
int rc = aml_start(aml, work);
aml_unref(work);
return rc;
side_data_failure:
nvnc_fb_pool_release(self->pool, ctx->dst);
acquire_failure:
free(ctx);
return -1;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020 - 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "stream.h"
#include "stream-common.h"
#include <stdlib.h>
void stream_req__finish(struct stream_req* req, enum stream_req_status status)
{
if (req->on_done)
req->on_done(req->userdata, status);
// exec userdata is heap allocated
if (req->exec && req->userdata)
free(req->userdata);
rcbuf_unref(req->payload);
free(req);
}
void stream__remote_closed(struct stream* self)
{
stream_close(self);
if (self->on_event)
self->on_event(self, STREAM_EVENT_REMOTE_CLOSED);
}

292
src/stream-gnutls.c 100644
View File

@ -0,0 +1,292 @@
/*
* Copyright (c) 2020 - 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <sys/uio.h>
#include <limits.h>
#include <aml.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/socket.h>
#include <gnutls/gnutls.h>
#include "rcbuf.h"
#include "stream.h"
#include "stream-common.h"
#include "sys/queue.h"
struct stream_gnutls {
struct stream base;
gnutls_session_t session;
};
static_assert(sizeof(struct stream_gnutls) <= STREAM_ALLOC_SIZE,
"struct stream_gnutls has grown too large, increase STREAM_ALLOC_SIZE");
static int stream__try_tls_accept(struct stream* self);
static int stream_gnutls_close(struct stream* base)
{
struct stream_gnutls* self = (struct stream_gnutls*)base;
if (self->base.state == STREAM_STATE_CLOSED)
return -1;
self->base.state = STREAM_STATE_CLOSED;
while (!TAILQ_EMPTY(&self->base.send_queue)) {
struct stream_req* req = TAILQ_FIRST(&self->base.send_queue);
TAILQ_REMOVE(&self->base.send_queue, req, link);
stream_req__finish(req, STREAM_REQ_FAILED);
}
if (self->session)
gnutls_deinit(self->session);
self->session = NULL;
aml_stop(aml_get_default(), self->base.handler);
close(self->base.fd);
self->base.fd = -1;
return 0;
}
static void stream_gnutls_destroy(struct stream* self)
{
stream_close(self);
aml_unref(self->handler);
free(self);
}
static int stream_gnutls__flush(struct stream* base)
{
struct stream_gnutls* self = (struct stream_gnutls*)base;
while (!TAILQ_EMPTY(&self->base.send_queue)) {
assert(self->base.state != STREAM_STATE_CLOSED);
struct stream_req* req = TAILQ_FIRST(&self->base.send_queue);
ssize_t rc = gnutls_record_send(self->session,
req->payload->payload, req->payload->size);
if (rc < 0) {
if (gnutls_error_is_fatal(rc)) {
stream_close(base);
return -1;
}
stream__poll_rw(base);
return 0;
}
self->base.bytes_sent += rc;
ssize_t remaining = req->payload->size - rc;
if (remaining > 0) {
char* p = req->payload->payload;
size_t s = req->payload->size;
memmove(p, p + s - remaining, remaining);
req->payload->size = remaining;
stream__poll_rw(base);
return 1;
}
assert(remaining == 0);
TAILQ_REMOVE(&self->base.send_queue, req, link);
stream_req__finish(req, STREAM_REQ_DONE);
}
if (TAILQ_EMPTY(&base->send_queue) && base->state != STREAM_STATE_CLOSED)
stream__poll_r(base);
return 1;
}
static void stream_gnutls__on_readable(struct stream* self)
{
switch (self->state) {
case STREAM_STATE_NORMAL:
/* fallthrough */
case STREAM_STATE_TLS_READY:
if (self->on_event)
self->on_event(self, STREAM_EVENT_READ);
break;
case STREAM_STATE_TLS_HANDSHAKE:
stream__try_tls_accept(self);
break;
case STREAM_STATE_CLOSED:
break;
}
}
static void stream_gnutls__on_writable(struct stream* self)
{
switch (self->state) {
case STREAM_STATE_NORMAL:
/* fallthrough */
case STREAM_STATE_TLS_READY:
stream_gnutls__flush(self);
break;
case STREAM_STATE_TLS_HANDSHAKE:
stream__try_tls_accept(self);
break;
case STREAM_STATE_CLOSED:
break;
}
}
static void stream_gnutls__on_event(void* obj)
{
struct stream* self = aml_get_userdata(obj);
uint32_t events = aml_get_revents(obj);
if (events & AML_EVENT_READ)
stream_gnutls__on_readable(self);
if (events & AML_EVENT_WRITE)
stream_gnutls__on_writable(self);
}
static int stream_gnutls_send(struct stream* self, struct rcbuf* payload,
stream_req_fn on_done, void* userdata)
{
if (self->state == STREAM_STATE_CLOSED)
return -1;
struct stream_req* req = calloc(1, sizeof(*req));
if (!req)
return -1;
req->payload = payload;
req->on_done = on_done;
req->userdata = userdata;
TAILQ_INSERT_TAIL(&self->send_queue, req, link);
return stream_gnutls__flush(self);
}
static ssize_t stream_gnutls_read(struct stream* base, void* dst, size_t size)
{
struct stream_gnutls* self = (struct stream_gnutls*)base;
ssize_t rc = gnutls_record_recv(self->session, dst, size);
if (rc == 0) {
stream__remote_closed(base);
return rc;
}
if (rc > 0) {
self->base.bytes_received += rc;
return rc;
}
switch (rc) {
case GNUTLS_E_INTERRUPTED:
errno = EINTR;
break;
case GNUTLS_E_AGAIN:
errno = EAGAIN;
break;
default:
errno = 0;
break;
}
// Make sure data wasn't being written.
assert(gnutls_record_get_direction(self->session) == 0);
return -1;
}
static int stream__try_tls_accept(struct stream* base)
{
struct stream_gnutls* self = (struct stream_gnutls*)base;
int rc;
rc = gnutls_handshake(self->session);
if (rc == GNUTLS_E_SUCCESS) {
self->base.state = STREAM_STATE_TLS_READY;
stream__poll_r(base);
return 0;
}
if (gnutls_error_is_fatal(rc)) {
aml_stop(aml_get_default(), self->base.handler);
return -1;
}
int was_writing = gnutls_record_get_direction(self->session);
if (was_writing)
stream__poll_w(base);
else
stream__poll_r(base);
self->base.state = STREAM_STATE_TLS_HANDSHAKE;
return 0;
}
static struct stream_impl impl = {
.close = stream_gnutls_close,
.destroy = stream_gnutls_destroy,
.read = stream_gnutls_read,
.send = stream_gnutls_send,
};
int stream_upgrade_to_tls(struct stream* base, void* context)
{
struct stream_gnutls* self = (struct stream_gnutls*)base;
int rc;
rc = gnutls_init(&self->session, GNUTLS_SERVER | GNUTLS_NONBLOCK);
if (rc != GNUTLS_E_SUCCESS)
return -1;
rc = gnutls_set_default_priority(self->session);
if (rc != GNUTLS_E_SUCCESS)
goto failure;
rc = gnutls_credentials_set(self->session, GNUTLS_CRD_CERTIFICATE,
context);
if (rc != GNUTLS_E_SUCCESS)
goto failure;
aml_stop(aml_get_default(), self->base.handler);
aml_unref(self->base.handler);
self->base.handler = aml_handler_new(self->base.fd,
stream_gnutls__on_event, self, NULL);
assert(self->base.handler);
rc = aml_start(aml_get_default(), self->base.handler);
assert(rc >= 0);
gnutls_transport_set_int(self->session, self->base.fd);
self->base.impl = &impl;
return stream__try_tls_accept(base);
failure:
gnutls_deinit(self->session);
return -1;
}

View File

@ -0,0 +1,211 @@
/*
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <sys/param.h>
#include <arpa/inet.h>
#include "rcbuf.h"
#include "stream.h"
#include "stream-tcp.h"
#include "stream-common.h"
#include "crypto.h"
#include "neatvnc.h"
#define RSA_AES_BUFFER_SIZE 8192
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
struct stream_rsa_aes {
struct stream base;
size_t read_index;
uint8_t* read_buffer;
struct crypto_cipher* cipher;
};
static_assert(sizeof(struct stream_rsa_aes) <= STREAM_ALLOC_SIZE,
"struct stream_rsa_aes has grown too large, increase STREAM_ALLOC_SIZE");
static void stream_rsa_aes_destroy(struct stream* base)
{
struct stream_rsa_aes* self = (struct stream_rsa_aes*)base;
crypto_cipher_del(self->cipher);
free(self->read_buffer);
stream_tcp_destroy(base);
}
static void stream_rsa_aes_read_into_buffer(struct stream_rsa_aes* self)
{
ssize_t n_read = stream_tcp_read(&self->base,
self->read_buffer + self->read_index,
RSA_AES_BUFFER_SIZE - self->read_index);
if (n_read > 0)
self->read_index += n_read;
}
static ssize_t stream_rsa_aes_parse_header(struct stream_rsa_aes* self)
{
if (self->read_index <= 2) {
return -1;
}
uint16_t len_be;
memcpy(&len_be, self->read_buffer, sizeof(len_be));
size_t len = ntohs(len_be);
if (self->read_index < 2 + 16 + len) {
return -1;
}
return len;
}
static ssize_t stream_rsa_aes_read_message(struct stream_rsa_aes* self,
uint8_t* dst, size_t size)
{
ssize_t msg_len = stream_rsa_aes_parse_header(self);
if (msg_len < 0) {
return 0;
}
// The entire message must fit in dst
/* TODO: With this, stream_tcp__on_event won't run until network input
* is received. We need to somehow schedule on_event or also buffer the
* decrypted data here.
* Another option would be to keep back the message counter in the
* cipher until the message has been fully read.
*/
if ((size_t)msg_len > size)
return 0;
uint16_t msg_len_be = htons(msg_len);
uint8_t expected_mac[16];
ssize_t n = crypto_cipher_decrypt(self->cipher, dst, expected_mac,
self->read_buffer + 2, msg_len,
(uint8_t*)&msg_len_be, sizeof(msg_len_be));
uint8_t* actual_mac = self->read_buffer + 2 + msg_len;
if (memcmp(expected_mac, actual_mac, 16) != 0) {
nvnc_log(NVNC_LOG_DEBUG, "Message authentication failed");
errno = EBADMSG;
return -1;
}
self->read_index -= 2 + 16 + msg_len;
memmove(self->read_buffer, self->read_buffer + 2 + 16 + msg_len,
self->read_index);
return n;
}
static ssize_t stream_rsa_aes_read(struct stream* base, void* dst, size_t size)
{
struct stream_rsa_aes* self = (struct stream_rsa_aes*)base;
stream_rsa_aes_read_into_buffer(self);
if (self->base.state == STREAM_STATE_CLOSED)
return 0;
size_t total_read = 0;
while (true) {
ssize_t n_read = stream_rsa_aes_read_message(self, dst, size);
if (n_read == 0)
break;
if (n_read < 0) {
if (errno == EAGAIN) {
break;
}
return -1;
}
total_read += n_read;
dst += n_read;
size -= n_read;
}
return total_read;
}
static int stream_rsa_aes_send(struct stream* base, struct rcbuf* payload,
stream_req_fn on_done, void* userdata)
{
struct stream_rsa_aes* self = (struct stream_rsa_aes*)base;
size_t n_msg = UDIV_UP(payload->size, RSA_AES_BUFFER_SIZE);
struct vec buf;
vec_init(&buf, payload->size + n_msg * (2 + 16));
for (size_t i = 0; i < n_msg; ++i) {
size_t msglen = MIN(payload->size - i * RSA_AES_BUFFER_SIZE,
RSA_AES_BUFFER_SIZE);
uint16_t msglen_be = htons(msglen);
vec_append(&buf, &msglen_be, sizeof(msglen_be));
uint8_t mac[16];
crypto_cipher_encrypt(self->cipher, &buf, mac,
payload->payload + i * RSA_AES_BUFFER_SIZE,
msglen, (uint8_t*)&msglen_be, sizeof(msglen_be));
vec_append(&buf, mac, sizeof(mac));
}
size_t payload_size = payload->size;
rcbuf_unref(payload);
int r = stream_tcp_send(base, rcbuf_new(buf.data, buf.len), on_done,
userdata);
if (r < 0) {
return r;
}
return payload_size;
}
static struct stream_impl impl = {
.close = stream_tcp_close,
.destroy = stream_rsa_aes_destroy,
.read = stream_rsa_aes_read,
.send = stream_rsa_aes_send,
};
int stream_upgrade_to_rsa_eas(struct stream* base,
enum crypto_cipher_type cipher_type,
const uint8_t* enc_key, const uint8_t* dec_key)
{
struct stream_rsa_aes* self = (struct stream_rsa_aes*)base;
self->read_index = 0;
self->read_buffer = malloc(RSA_AES_BUFFER_SIZE);
if (!self->read_buffer)
return -1;
self->cipher = crypto_cipher_new(enc_key, dec_key, cipher_type);
if (!self->cipher) {
free(self->read_buffer);
return -1;
}
self->base.impl = &impl;
return 0;
}

312
src/stream-tcp.c 100644
View File

@ -0,0 +1,312 @@
/*
* Copyright (c) 2020 - 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <sys/uio.h>
#include <limits.h>
#include <aml.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/socket.h>
#include "rcbuf.h"
#include "stream.h"
#include "stream-common.h"
#include "stream-tcp.h"
#include "sys/queue.h"
#include "neatvnc.h"
static_assert(sizeof(struct stream) <= STREAM_ALLOC_SIZE,
"struct stream has grown too large, increase STREAM_ALLOC_SIZE");
int stream_tcp_close(struct stream* self)
{
if (self->state == STREAM_STATE_CLOSED)
return -1;
self->state = STREAM_STATE_CLOSED;
while (!TAILQ_EMPTY(&self->send_queue)) {
struct stream_req* req = TAILQ_FIRST(&self->send_queue);
TAILQ_REMOVE(&self->send_queue, req, link);
stream_req__finish(req, STREAM_REQ_FAILED);
}
aml_stop(aml_get_default(), self->handler);
close(self->fd);
self->fd = -1;
return 0;
}
void stream_tcp_destroy(struct stream* self)
{
vec_destroy(&self->tmp_buf);
stream_close(self);
aml_unref(self->handler);
free(self);
}
static int stream_tcp__flush(struct stream* self)
{
if (self->cork)
return 0;
static struct iovec iov[IOV_MAX];
size_t n_msgs = 0;
ssize_t bytes_sent;
struct stream_req* req;
TAILQ_FOREACH(req, &self->send_queue, link) {
if (req->exec) {
if (req->payload)
rcbuf_unref(req->payload);
struct rcbuf* payload = req->exec(self, req->userdata);
req->payload = payload;
}
iov[n_msgs].iov_base = req->payload->payload;
iov[n_msgs].iov_len = req->payload->size;
if (++n_msgs >= IOV_MAX)
break;
}
if (n_msgs == 0)
return 0;
struct msghdr msghdr = {
.msg_iov = iov,
.msg_iovlen = n_msgs,
};
bytes_sent = sendmsg(self->fd, &msghdr, MSG_NOSIGNAL);
if (bytes_sent < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
stream__poll_rw(self);
errno = EAGAIN;
bytes_sent = 0;
} else if (errno == EPIPE) {
stream__remote_closed(self);
errno = EPIPE;
}
return bytes_sent;
}
self->bytes_sent += bytes_sent;
ssize_t bytes_left = bytes_sent;
struct stream_req* tmp;
TAILQ_FOREACH_SAFE(req, &self->send_queue, link, tmp) {
bytes_left -= req->payload->size;
if (bytes_left >= 0) {
TAILQ_REMOVE(&self->send_queue, req, link);
stream_req__finish(req, STREAM_REQ_DONE);
} else {
if (req->exec) {
free(req->userdata);
req->userdata = NULL;
req->exec = NULL;
}
char* p = req->payload->payload;
size_t s = req->payload->size;
memmove(p, p + s + bytes_left, -bytes_left);
req->payload->size = -bytes_left;
stream__poll_rw(self);
}
if (bytes_left <= 0)
break;
}
if (bytes_left == 0 && self->state != STREAM_STATE_CLOSED)
stream__poll_r(self);
assert(bytes_left <= 0);
return bytes_sent;
}
static void stream_tcp__on_readable(struct stream* self)
{
switch (self->state) {
case STREAM_STATE_NORMAL:
/* fallthrough */
if (self->on_event)
self->on_event(self, STREAM_EVENT_READ);
break;
case STREAM_STATE_CLOSED:
break;
default:;
}
}
static void stream_tcp__on_writable(struct stream* self)
{
switch (self->state) {
case STREAM_STATE_NORMAL:
/* fallthrough */
stream_tcp__flush(self);
break;
case STREAM_STATE_CLOSED:
break;
default:;
}
}
static void stream_tcp__on_event(void* obj)
{
struct stream* self = aml_get_userdata(obj);
uint32_t events = aml_get_revents(obj);
if (events & AML_EVENT_READ)
stream_tcp__on_readable(self);
if (events & AML_EVENT_WRITE)
stream_tcp__on_writable(self);
}
ssize_t stream_tcp_read(struct stream* self, void* dst, size_t size)
{
if (self->state != STREAM_STATE_NORMAL)
return -1;
uint8_t* read_buffer = dst;
if (self->cipher) {
vec_reserve(&self->tmp_buf, size);
read_buffer = self->tmp_buf.data;
}
ssize_t rc = read(self->fd, read_buffer, size);
if (rc == 0)
stream__remote_closed(self);
if (rc > 0)
self->bytes_received += rc;
return rc;
}
int stream_tcp_send(struct stream* self, struct rcbuf* payload,
stream_req_fn on_done, void* userdata)
{
if (self->state == STREAM_STATE_CLOSED)
return -1;
struct stream_req* req = calloc(1, sizeof(*req));
if (!req)
return -1;
req->payload = payload;
req->on_done = on_done;
req->userdata = userdata;
TAILQ_INSERT_TAIL(&self->send_queue, req, link);
return stream_tcp__flush(self);
}
int stream_tcp_send_first(struct stream* self, struct rcbuf* payload)
{
if (self->state == STREAM_STATE_CLOSED)
return -1;
struct stream_req* req = calloc(1, sizeof(*req));
if (!req)
return -1;
req->payload = payload;
TAILQ_INSERT_HEAD(&self->send_queue, req, link);
return stream_tcp__flush(self);
}
void stream_tcp_exec_and_send(struct stream* self,
stream_exec_fn exec_fn, void* userdata)
{
if (self->state == STREAM_STATE_CLOSED)
return;
struct stream_req* req = calloc(1, sizeof(*req));
if (!req)
return;
req->exec = exec_fn;
req->userdata = userdata;
TAILQ_INSERT_TAIL(&self->send_queue, req, link);
stream_tcp__flush(self);
}
static struct stream_impl impl = {
.close = stream_tcp_close,
.destroy = stream_tcp_destroy,
.read = stream_tcp_read,
.send = stream_tcp_send,
.send_first = stream_tcp_send_first,
.exec_and_send = stream_tcp_exec_and_send,
};
int stream_tcp_init(struct stream* self, int fd, stream_event_fn on_event,
void* userdata)
{
self->impl = &impl,
self->fd = fd;
self->on_event = on_event;
self->userdata = userdata;
TAILQ_INIT(&self->send_queue);
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
self->handler = aml_handler_new(fd, stream_tcp__on_event, self, NULL);
if (!self->handler)
return -1;
if (aml_start(aml_get_default(), self->handler) < 0)
goto start_failure;
stream__poll_r(self);
return 0;
start_failure:
aml_unref(self->handler);
return -1;
}
struct stream* stream_new(int fd, stream_event_fn on_event, void* userdata)
{
struct stream* self = calloc(1, STREAM_ALLOC_SIZE);
if (!self)
return NULL;
if (stream_tcp_init(self, fd, on_event, userdata) < 0) {
free(self);
return NULL;
}
return self;
}

314
src/stream-ws.c 100644
View File

@ -0,0 +1,314 @@
/*
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "stream.h"
#include "stream-common.h"
#include "stream-tcp.h"
#include "websocket.h"
#include "vec.h"
#include "neatvnc.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/param.h>
enum stream_ws_state {
STREAM_WS_STATE_HANDSHAKE = 0,
STREAM_WS_STATE_READY,
};
struct stream_ws_exec_ctx {
stream_exec_fn exec;
void* userdata;
};
struct stream_ws {
struct stream base;
stream_event_fn on_event;
enum stream_ws_state ws_state;
struct ws_frame_header header;
enum ws_opcode current_opcode;
size_t read_index;
uint8_t read_buffer[4096]; // TODO: Is this a reasonable size?
};
static void stream_ws_read_into_buffer(struct stream_ws* ws)
{
ssize_t n_read = stream_tcp_read(&ws->base,
ws->read_buffer + ws->read_index,
sizeof(ws->read_buffer) - ws->read_index);
if (n_read > 0)
ws->read_index += n_read;
}
static void stream_ws_advance_read_buffer(struct stream_ws* ws, size_t size,
size_t offset)
{
size_t payload_len = MIN(size, ws->read_index - offset);
payload_len = MIN(payload_len, ws->header.payload_length);
ws->read_index -= offset + payload_len;
memmove(ws->read_buffer, ws->read_buffer + offset + payload_len,
ws->read_index);
ws->header.payload_length -= payload_len;
}
static ssize_t stream_ws_copy_payload(struct stream_ws* ws, void* dst,
size_t size, size_t offset)
{
size_t payload_len = MIN(size, ws->read_index - offset);
payload_len = MIN(payload_len, ws->header.payload_length);
ws_copy_payload(&ws->header, dst, ws->read_buffer + offset, payload_len);
stream_ws_advance_read_buffer(ws, size, offset);
return payload_len;
}
static ssize_t stream_ws_process_ping(struct stream_ws* ws, size_t offset)
{
if (offset > 0) {
// This means we're at the start, so send a header
struct ws_frame_header reply = {
.fin = true,
.opcode = WS_OPCODE_PONG,
.payload_length = ws->header.payload_length,
};
uint8_t buf[WS_HEADER_MIN_SIZE];
int reply_len = ws_write_frame_header(buf, &reply);
stream_tcp_send(&ws->base, rcbuf_from_mem(buf, reply_len),
NULL, NULL);
}
int payload_len = MIN(ws->read_index, ws->header.payload_length);
// Feed back the payload:
stream_tcp_send(&ws->base, rcbuf_from_mem(ws->read_buffer + offset,
payload_len), NULL, NULL);
stream_ws_advance_read_buffer(ws, payload_len, offset);
return 0;
}
static ssize_t stream_ws_process_payload(struct stream_ws* ws, void* dst,
size_t size, size_t offset)
{
switch (ws->current_opcode) {
case WS_OPCODE_CONT:
// Remote end started with a continuation frame. This is
// unexpected, so we'll just close.
stream__remote_closed(&ws->base);
return 0;
case WS_OPCODE_TEXT:
// This is unexpected, but let's just ignore it...
stream_ws_advance_read_buffer(ws, SIZE_MAX, offset);
return 0;
case WS_OPCODE_BIN:
return stream_ws_copy_payload(ws, dst, size, offset);
case WS_OPCODE_CLOSE:
stream__remote_closed(&ws->base);
return 0;
case WS_OPCODE_PING:
return stream_ws_process_ping(ws, offset);
case WS_OPCODE_PONG:
// Don't care
stream_ws_advance_read_buffer(ws, SIZE_MAX, offset);
return 0;
}
return -1;
}
/* We don't really care about framing. The binary data is just passed on as it
* arrives and it's not gathered into individual frames.
*/
static ssize_t stream_ws_read_frame(struct stream_ws* ws, void* dst,
size_t size)
{
if (ws->header.payload_length > 0) {
nvnc_trace("Processing left-over payload chunk");
return stream_ws_process_payload(ws, dst, size, 0);
}
if (!ws_parse_frame_header(&ws->header, ws->read_buffer,
ws->read_index)) {
return 0;
}
if (ws->header.opcode != WS_OPCODE_CONT) {
ws->current_opcode = ws->header.opcode;
}
// The header is located at the start of the buffer, so an offset is
// needed.
return stream_ws_process_payload(ws, dst, size,
ws->header.header_length);
}
static ssize_t stream_ws_read_ready(struct stream_ws* ws, void* dst,
size_t size)
{
size_t total_read = 0;
while (true) {
ssize_t n_read = stream_ws_read_frame(ws, dst, size);
if (n_read == 0)
break;
if (n_read < 0) {
if (errno == EAGAIN) {
break;
}
return -1;
}
total_read += n_read;
dst += n_read;
size -= n_read;
}
return total_read;
}
static ssize_t stream_ws_read_handshake(struct stream_ws* ws, void* dst,
size_t size)
{
if (ws->read_index >= sizeof(ws->read_buffer)) {
// This header is suspiciously long
stream__remote_closed(&ws->base);
return -1;
}
ws->read_buffer[ws->read_index] = '\0';
char reply[512];
ssize_t header_len = ws_handshake(reply, sizeof(reply),
(const char*)ws->read_buffer);
if (header_len < 0)
return 0;
ws->base.cork = false;
stream_tcp_send_first(&ws->base, rcbuf_from_mem(reply, strlen(reply)));
ws->read_index -= header_len;
memmove(ws->read_buffer, ws->read_buffer + header_len, ws->read_index);
ws->ws_state = STREAM_WS_STATE_READY;
return stream_ws_read_ready(ws, dst, size);
}
static ssize_t stream_ws_read(struct stream* self, void* dst, size_t size)
{
struct stream_ws* ws = (struct stream_ws*)self;
stream_ws_read_into_buffer(ws);
if (self->state == STREAM_STATE_CLOSED)
return 0;
switch (ws->ws_state) {
case STREAM_WS_STATE_HANDSHAKE:
return stream_ws_read_handshake(ws, dst, size);
case STREAM_WS_STATE_READY:
return stream_ws_read_ready(ws, dst, size);
}
abort();
return -1;
}
static int stream_ws_send(struct stream* self, struct rcbuf* payload,
stream_req_fn on_done, void* userdata)
{
struct stream_ws* ws = (struct stream_ws*)self;
struct ws_frame_header head = {
.fin = true,
.opcode = WS_OPCODE_BIN,
.payload_length = payload->size,
};
uint8_t raw_head[WS_HEADER_MIN_SIZE];
int head_len = ws_write_frame_header(raw_head, &head);
stream_tcp_send(&ws->base, rcbuf_from_mem(&raw_head, head_len),
NULL, NULL);
return stream_tcp_send(&ws->base, payload, on_done, userdata);
}
static struct rcbuf* stream_ws_chained_exec(struct stream* tcp_stream,
void* userdata)
{
struct stream_ws* ws = (struct stream_ws*)tcp_stream;
struct stream_ws_exec_ctx* ctx = userdata;
struct rcbuf* buf = ctx->exec(&ws->base, ctx->userdata);
// TODO: This also needs to be cleaned it it's left on the send queue
// when the stream is destroyed.
free(ctx->userdata);
struct vec out;
vec_init(&out, WS_HEADER_MIN_SIZE + buf->size + 1);
struct ws_frame_header head = {
.fin = true,
.opcode = WS_OPCODE_BIN,
.payload_length = buf->size,
};
int head_len = ws_write_frame_header(out.data, &head);
out.len += head_len;
vec_append(&out, buf->payload, buf->size);
rcbuf_unref(buf);
return rcbuf_new(out.data, out.len);
}
static void stream_ws_exec_and_send(struct stream* self, stream_exec_fn exec,
void* userdata)
{
struct stream_ws* ws = (struct stream_ws*)self;
struct stream_ws_exec_ctx* ctx = calloc(1, sizeof(*ctx));
assert(ctx);
ctx->exec = exec;
ctx->userdata = userdata;
stream_tcp_exec_and_send(&ws->base, stream_ws_chained_exec, ctx);
}
static struct stream_impl impl = {
.close = stream_tcp_close,
.destroy = stream_tcp_destroy,
.read = stream_ws_read,
.send = stream_ws_send,
.exec_and_send = stream_ws_exec_and_send,
};
struct stream* stream_ws_new(int fd, stream_event_fn on_event, void* userdata)
{
struct stream_ws *self = calloc(1, sizeof(*self));
if (!self)
return NULL;
stream_tcp_init(&self->base, fd, on_event, userdata);
self->base.impl = &impl;
// Don't send anything until handshake is done:
self->base.cork = true;
return &self->base;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Andri Yngvason
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -14,313 +14,33 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <sys/uio.h>
#include <uv.h>
#ifdef ENABLE_TLS
#include <gnutls/gnutls.h>
#endif
#include "type-macros.h"
#include "rcbuf.h"
#include "stream.h"
#include "sys/queue.h"
static void stream__on_event(uv_poll_t* uv_poll, int status, int events);
#ifdef ENABLE_TLS
static int stream__try_tls_accept(struct stream* self);
#endif
static inline void stream__poll_r(struct stream* self)
{
uv_poll_start(&self->uv_poll, UV_READABLE | UV_DISCONNECT,
stream__on_event);
}
static inline void stream__poll_w(struct stream* self)
{
uv_poll_start(&self->uv_poll, UV_WRITABLE | UV_DISCONNECT,
stream__on_event);
}
static inline void stream__poll_rw(struct stream* self)
{
uv_poll_start(&self->uv_poll, UV_READABLE | UV_DISCONNECT | UV_WRITABLE,
stream__on_event);
}
static void stream_req__finish(struct stream_req* req, enum stream_req_status status)
{
if (req->on_done)
req->on_done(req->userdata, status);
rcbuf_unref(req->payload);
free(req);
}
#include <assert.h>
int stream_close(struct stream* self)
{
if (self->state == STREAM_STATE_CLOSED)
return -1;
self->state = STREAM_STATE_CLOSED;
while (!TAILQ_EMPTY(&self->send_queue)) {
struct stream_req* req = TAILQ_FIRST(&self->send_queue);
TAILQ_REMOVE(&self->send_queue, req, link);
stream_req__finish(req, STREAM_REQ_FAILED);
}
#ifdef ENABLE_TLS
if (self->tls_session)
gnutls_deinit(self->tls_session);
self->tls_session = NULL;
#endif
uv_poll_stop(&self->uv_poll);
close(self->fd);
self->fd = -1;
return 0;
}
void stream__free_poll_handle(uv_handle_t* handle)
{
uv_poll_t* uv_poll = (uv_poll_t*)handle;
struct stream* self = container_of(uv_poll, struct stream, uv_poll);
free(self);
assert(self->impl && self->impl->close);
return self->impl->close(self);
}
void stream_destroy(struct stream* self)
{
stream_close(self);
uv_close((uv_handle_t*)&self->uv_poll, stream__free_poll_handle);
}
static void stream__remote_closed(struct stream* self)
{
stream_close(self);
if (self->on_event)
self->on_event(self, STREAM_EVENT_REMOTE_CLOSED);
}
static int stream__flush_plain(struct stream* self)
{
static struct iovec iov[IOV_MAX];
size_t n_msgs = 0;
ssize_t bytes_sent;
struct stream_req* req;
TAILQ_FOREACH(req, &self->send_queue, link) {
iov[n_msgs].iov_base = req->payload->payload;
iov[n_msgs].iov_len = req->payload->size;
if (++n_msgs >= IOV_MAX)
break;
}
if (n_msgs < 0)
return 0;
bytes_sent = writev(self->fd, iov, n_msgs);
if (bytes_sent < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
stream__poll_rw(self);
errno = EAGAIN;
} else if (errno == EPIPE) {
stream__remote_closed(self);
errno = EPIPE;
}
return bytes_sent;
}
ssize_t bytes_left = bytes_sent;
struct stream_req* tmp;
TAILQ_FOREACH_SAFE(req, &self->send_queue, link, tmp) {
bytes_left -= req->payload->size;
if (bytes_left >= 0) {
TAILQ_REMOVE(&self->send_queue, req, link);
stream_req__finish(req, STREAM_REQ_DONE);
} else {
char* p = req->payload->payload;
size_t s = req->payload->size;
memmove(p, p + s + bytes_left, -bytes_left);
req->payload->size = -bytes_left;
stream__poll_rw(self);
}
if (bytes_left <= 0)
break;
}
if (bytes_left == 0 && self->state != STREAM_STATE_CLOSED)
stream__poll_r(self);
assert(bytes_left <= 0);
return bytes_sent;
}
#ifdef ENABLE_TLS
static int stream__flush_tls(struct stream* self)
{
while (!TAILQ_EMPTY(&self->send_queue)) {
struct stream_req* req = TAILQ_FIRST(&self->send_queue);
ssize_t rc = gnutls_record_send(
self->tls_session, req->payload->payload,
req->payload->size);
if (rc < 0) {
gnutls_record_discard_queued(self->tls_session);
if (gnutls_error_is_fatal(rc))
stream_close(self);
return -1;
}
ssize_t remaining = req->payload->size - rc;
if (remaining > 0) {
char* p = req->payload->payload;
size_t s = req->payload->size;
memmove(p, p + s - remaining, remaining);
req->payload->size = remaining;
stream__poll_rw(self);
return 1;
}
assert(remaining == 0);
TAILQ_REMOVE(&self->send_queue, req, link);
stream_req__finish(req, STREAM_REQ_DONE);
}
if (TAILQ_EMPTY(&self->send_queue) && self->state != STREAM_STATE_CLOSED)
stream__poll_r(self);
return 1;
}
#endif
static int stream__flush(struct stream* self)
{
switch (self->state) {
case STREAM_STATE_NORMAL: return stream__flush_plain(self);
#ifdef ENABLE_TLS
case STREAM_STATE_TLS_READY: return stream__flush_tls(self);
#endif
default:
break;
}
abort();
return -1;
}
static void stream__on_readable(struct stream* self)
{
switch (self->state) {
case STREAM_STATE_NORMAL:
#ifdef ENABLE_TLS
case STREAM_STATE_TLS_READY:
if (self->on_event)
self->on_event(self, STREAM_EVENT_READ);
break;
case STREAM_STATE_TLS_HANDSHAKE:
stream__try_tls_accept(self);
break;
#endif
case STREAM_STATE_CLOSED:
abort();
break;
}
}
static void stream__on_writable(struct stream* self)
{
switch (self->state) {
case STREAM_STATE_NORMAL:
#ifdef ENABLE_TLS
case STREAM_STATE_TLS_READY:
stream__flush(self);
break;
case STREAM_STATE_TLS_HANDSHAKE:
stream__try_tls_accept(self);
break;
#endif
case STREAM_STATE_CLOSED:
abort();
break;
}
}
static void stream__on_event(uv_poll_t* uv_poll, int status, int events)
{
struct stream* self = container_of(uv_poll, struct stream, uv_poll);
if (events & UV_DISCONNECT) {
stream__remote_closed(self);
return;
}
if (events & UV_READABLE)
stream__on_readable(self);
if (events & UV_WRITABLE)
stream__on_writable(self);
}
struct stream* stream_new(int fd, stream_event_fn on_event, void* userdata)
{
struct stream* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->fd = fd;
self->on_event = on_event;
self->userdata = userdata;
TAILQ_INIT(&self->send_queue);
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
if (uv_poll_init(uv_default_loop(), &self->uv_poll, fd) < 0)
goto failure;
stream__poll_r(self);
return self;
failure:
free(self);
return NULL;
assert(self->impl && self->impl->destroy);
return self->impl->destroy(self);
}
int stream_send(struct stream* self, struct rcbuf* payload,
stream_req_fn on_done, void* userdata)
{
if (self->state == STREAM_STATE_CLOSED)
return -1;
assert(self->impl && self->impl->send);
return self->impl->send(self, payload, on_done, userdata);
}
struct stream_req* req = calloc(1, sizeof(*req));
if (!req)
return -1;
req->payload = payload;
req->on_done = on_done;
req->userdata = userdata;
TAILQ_INSERT_TAIL(&self->send_queue, req, link);
return stream__flush(self);
int stream_send_first(struct stream* self, struct rcbuf* payload)
{
assert(self->impl && self->impl->send);
return self->impl->send_first(self, payload);
}
int stream_write(struct stream* self, const void* payload, size_t len,
@ -330,100 +50,18 @@ int stream_write(struct stream* self, const void* payload, size_t len,
return buf ? stream_send(self, buf, on_done, userdata) : -1;
}
ssize_t stream__read_plain(struct stream* self, void* dst, size_t size)
{
return read(self->fd, dst, size);
}
#ifdef ENABLE_TLS
ssize_t stream__read_tls(struct stream* self, void* dst, size_t size)
{
ssize_t rc = gnutls_record_recv(self->tls_session, dst, size);
if (rc >= 0)
return rc;
switch (rc) {
case GNUTLS_E_INTERRUPTED:
errno = EINTR;
break;
case GNUTLS_E_AGAIN:
errno = EAGAIN;
break;
default:
errno = 0;
break;
}
// Make sure data wasn't being written.
assert(gnutls_record_get_direction(self->tls_session) == 0);
return -1;
}
#endif
ssize_t stream_read(struct stream* self, void* dst, size_t size)
{
switch (self->state) {
case STREAM_STATE_NORMAL: return stream__read_plain(self, dst, size);
#ifdef ENABLE_TLS
case STREAM_STATE_TLS_READY: return stream__read_tls(self, dst, size);
#endif
default: break;
}
abort();
return -1;
assert(self->impl && self->impl->read);
return self->impl->read(self, dst, size);
}
#ifdef ENABLE_TLS
static int stream__try_tls_accept(struct stream* self)
void stream_exec_and_send(struct stream* self, stream_exec_fn exec_fn,
void* userdata)
{
int rc;
rc = gnutls_handshake(self->tls_session);
if (rc == GNUTLS_E_SUCCESS) {
self->state = STREAM_STATE_TLS_READY;
stream__poll_r(self);
return 0;
}
if (gnutls_error_is_fatal(rc)) {
uv_poll_stop(&self->uv_poll);
return -1;
}
int was_writing = gnutls_record_get_direction(self->tls_session);
if (was_writing)
stream__poll_w(self);
assert(self->impl);
if (self->impl->exec_and_send)
self->impl->exec_and_send(self, exec_fn, userdata);
else
stream__poll_r(self);
self->state = STREAM_STATE_TLS_HANDSHAKE;
return 0;
stream_send(self, exec_fn(self, userdata), NULL, NULL);
}
int stream_upgrade_to_tls(struct stream* self, void* context)
{
int rc;
rc = gnutls_init(&self->tls_session, GNUTLS_SERVER | GNUTLS_NONBLOCK);
if (rc != GNUTLS_E_SUCCESS)
return -1;
rc = gnutls_set_default_priority(self->tls_session);
if (rc != GNUTLS_E_SUCCESS)
goto failure;
rc = gnutls_credentials_set(self->tls_session, GNUTLS_CRD_CERTIFICATE,
context);
if (rc != GNUTLS_E_SUCCESS)
goto failure;
gnutls_transport_set_int(self->tls_session, self->fd);
return stream__try_tls_accept(self);
failure:
gnutls_deinit(self->tls_session);
return -1;
}
#endif

View File

@ -1,21 +1,327 @@
/*
* Copyright (c) 2019 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "neatvnc.h"
#include "rfb-proto.h"
#include "vec.h"
#include "fb.h"
#include "tight.h"
#include "common.h"
#include "pixels.h"
#include "vec.h"
#include "config.h"
#include "enc-util.h"
#include "fb.h"
#include "rcbuf.h"
#include "encoder.h"
#include <pixman.h>
#include <turbojpeg.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <zlib.h>
#include <pixels.h>
#include <pthread.h>
#include <assert.h>
#include <aml.h>
#include <libdrm/drm_fourcc.h>
#ifdef HAVE_JPEG
#include <turbojpeg.h>
#endif
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
#define TIGHT_FILL 0x80
#define TIGHT_JPEG 0x90
#define TIGHT_PNG 0xA0
#define TIGHT_BASIC 0x00
enum TJPF get_jpeg_pixfmt(uint32_t fourcc)
#define TIGHT_STREAM(n) ((n) << 4)
#define TIGHT_RESET(n) (1 << (n))
#define TSL 64 /* Tile Side Length */
#define MAX_TILE_SIZE (2 * TSL * TSL * 4)
struct encoder* tight_encoder_new(uint16_t width, uint16_t height);
typedef void (*tight_done_fn)(struct vec* frame, void*);
struct tight_encoder {
struct encoder encoder;
uint32_t width;
uint32_t height;
uint32_t grid_width;
uint32_t grid_height;
int quality;
struct tight_tile* grid;
z_stream zs[4];
struct aml_work* zs_worker[4];
struct rfb_pixel_format dfmt;
struct rfb_pixel_format sfmt;
struct nvnc_fb* fb;
uint64_t pts;
uint32_t n_rects;
uint32_t n_jobs;
struct vec dst;
tight_done_fn on_frame_done;
void* userdata;
};
enum tight_tile_state {
TIGHT_TILE_READY = 0,
TIGHT_TILE_DAMAGED,
TIGHT_TILE_ENCODED,
};
struct tight_tile {
enum tight_tile_state state;
size_t size;
uint8_t type;
char buffer[MAX_TILE_SIZE];
};
struct tight_zs_worker_ctx {
struct tight_encoder* encoder;
int index;
};
struct encoder_impl encoder_impl_tight;
static void do_tight_zs_work(void*);
static void on_tight_zs_work_done(void*);
static int schedule_tight_finish(struct tight_encoder* self);
static inline struct tight_encoder* tight_encoder(struct encoder* encoder)
{
assert(encoder->impl == &encoder_impl_tight);
return (struct tight_encoder*)encoder;
}
static int tight_encoder_init_stream(z_stream* zs)
{
int rc = deflateInit2(zs,
/* compression level: */ 1,
/* method: */ Z_DEFLATED,
/* window bits: */ 15,
/* mem level: */ 9,
/* strategy: */ Z_DEFAULT_STRATEGY);
return rc == Z_OK ? 0 : -1;
}
static inline struct tight_tile* tight_tile(struct tight_encoder* self,
uint32_t x, uint32_t y)
{
return &self->grid[x + y * self->grid_width];
}
static inline uint32_t tight_tile_width(struct tight_encoder* self,
uint32_t x)
{
return x + TSL > self->width ? self->width - x : TSL;
}
static inline uint32_t tight_tile_height(struct tight_encoder* self,
uint32_t y)
{
return y + TSL > self->height ? self->height - y : TSL;
}
static int tight_init_zs_worker(struct tight_encoder* self, int index)
{
struct tight_zs_worker_ctx* ctx = calloc(1, sizeof(*ctx));
if (!ctx)
return -1;
ctx->encoder = self;
ctx->index = index;
self->zs_worker[index] =
aml_work_new(do_tight_zs_work, on_tight_zs_work_done, ctx, free);
if (!self->zs_worker[index])
goto failure;
return 0;
failure:
free(ctx);
return -1;
}
static int tight_encoder_resize(struct tight_encoder* self, uint32_t width,
uint32_t height)
{
self->width = width;
self->height = height;
self->grid_width = UDIV_UP(width, 64);
self->grid_height = UDIV_UP(height, 64);
if (self->grid)
free(self->grid);
self->grid = calloc(self->grid_width * self->grid_height,
sizeof(*self->grid));
return self->grid ? 0 : -1;
}
static int tight_encoder_init(struct tight_encoder* self, uint32_t width,
uint32_t height)
{
memset(self, 0, sizeof(*self));
if (tight_encoder_resize(self, width, height) < 0)
return -1;
tight_encoder_init_stream(&self->zs[0]);
tight_encoder_init_stream(&self->zs[1]);
tight_encoder_init_stream(&self->zs[2]);
tight_encoder_init_stream(&self->zs[3]);
tight_init_zs_worker(self, 0);
tight_init_zs_worker(self, 1);
tight_init_zs_worker(self, 2);
tight_init_zs_worker(self, 3);
aml_require_workers(aml_get_default(), 1);
self->pts = NVNC_NO_PTS;
return 0;
}
static void tight_encoder_destroy(struct tight_encoder* self)
{
aml_unref(self->zs_worker[3]);
aml_unref(self->zs_worker[2]);
aml_unref(self->zs_worker[1]);
aml_unref(self->zs_worker[0]);
deflateEnd(&self->zs[3]);
deflateEnd(&self->zs[2]);
deflateEnd(&self->zs[1]);
deflateEnd(&self->zs[0]);
free(self->grid);
}
static int tight_apply_damage(struct tight_encoder* self,
struct pixman_region16* damage)
{
int n_damaged = 0;
/* Align damage to tile grid */
for (uint32_t y = 0; y < self->grid_height; ++y)
for (uint32_t x = 0; x < self->grid_width; ++x) {
struct pixman_box16 box = {
.x1 = x * TSL,
.y1 = y * TSL,
.x2 = ((x + 1) * TSL) - 1,
.y2 = ((y + 1) * TSL) - 1,
};
pixman_region_overlap_t overlap
= pixman_region_contains_rectangle(damage, &box);
if (overlap != PIXMAN_REGION_OUT) {
++n_damaged;
tight_tile(self, x, y)->state = TIGHT_TILE_DAMAGED;
} else {
tight_tile(self, x, y)->state = TIGHT_TILE_READY;
}
}
return n_damaged;
}
static void tight_encode_size(struct vec* dst, size_t size)
{
vec_fast_append_8(dst, (size & 0x7f) | ((size >= 128) << 7));
if (size >= 128)
vec_fast_append_8(dst, ((size >> 7) & 0x7f) | ((size >= 16384) << 7));
if (size >= 16384)
vec_fast_append_8(dst, (size >> 14) & 0xff);
}
static int tight_deflate(struct tight_tile* tile, void* src,
size_t len, z_stream* zs, bool flush)
{
zs->next_in = src;
zs->avail_in = len;
do {
if (tile->size >= MAX_TILE_SIZE)
return -1;
zs->next_out = ((Bytef*)tile->buffer) + tile->size;
zs->avail_out = MAX_TILE_SIZE - tile->size;
int r = deflate(zs, flush ? Z_SYNC_FLUSH : Z_NO_FLUSH);
if (r == Z_STREAM_ERROR)
return -1;
tile->size = zs->next_out - (Bytef*)tile->buffer;
} while (zs->avail_out == 0);
assert(zs->avail_in == 0);
return 0;
}
static void tight_encode_tile_basic(struct tight_encoder* self,
struct tight_tile* tile, uint32_t x, uint32_t y_start,
uint32_t width, uint32_t height, int zs_index)
{
z_stream* zs = &self->zs[zs_index];
tile->type = TIGHT_BASIC | TIGHT_STREAM(zs_index);
int bytes_per_cpixel = calc_bytes_per_cpixel(&self->dfmt);
assert(bytes_per_cpixel <= 4);
uint8_t row[TSL * 4];
struct rfb_pixel_format cfmt = { 0 };
if (bytes_per_cpixel == 3)
rfb_pixfmt_from_fourcc(&cfmt, DRM_FORMAT_XBGR8888);
else
memcpy(&cfmt, &self->dfmt, sizeof(cfmt));
uint8_t* addr = nvnc_fb_get_addr(self->fb);
int32_t bpp = self->sfmt.bits_per_pixel / 8;
int32_t byte_stride = nvnc_fb_get_stride(self->fb) * bpp;
int32_t xoff = x * bpp;
// TODO: Limit width and hight to the sides
for (uint32_t y = y_start; y < y_start + height; ++y) {
uint8_t* img = addr + xoff + y * byte_stride;
pixel_to_cpixel(row, &cfmt, img, &self->sfmt,
bytes_per_cpixel, width);
// TODO What to do if the buffer fills up?
if (tight_deflate(tile, row, bytes_per_cpixel * width,
zs, y == y_start + height - 1) < 0)
abort();
}
}
#ifdef HAVE_JPEG
static enum TJPF tight_get_jpeg_pixfmt(uint32_t fourcc)
{
switch (fourcc) {
case DRM_FORMAT_RGBA8888:
@ -30,96 +336,296 @@ enum TJPF get_jpeg_pixfmt(uint32_t fourcc)
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_XBGR8888:
return TJPF_RGBX;
case DRM_FORMAT_BGR888:
return TJPF_RGB;
case DRM_FORMAT_RGB888:
return TJPF_BGR;
}
return TJPF_UNKNOWN;
}
static void tight_encode_size(struct vec* dst, size_t size)
{
vec_fast_append_8(dst, (size & 0x7f) | ((size >= 128) << 7));
if (size >= 128)
vec_fast_append_8(dst, ((size >> 7) & 0x7f) | ((size >= 16384) << 7));
if (size >= 16384)
vec_fast_append_8(dst, (size >> 14) & 0x7f);
}
int tight_encode_box(struct vec* dst, struct nvnc_client* client,
const struct nvnc_fb* fb, uint32_t x, uint32_t y,
uint32_t stride, uint32_t width, uint32_t height)
static int tight_encode_tile_jpeg(struct tight_encoder* self,
struct tight_tile* tile, uint32_t x, uint32_t y, uint32_t width,
uint32_t height)
{
tile->type = TIGHT_JPEG;
unsigned char* buffer = NULL;
size_t size = 0;
unsigned long size = 0;
int quality = 50; /* 1 - 100 */
enum TJPF tjfmt = get_jpeg_pixfmt(fb->fourcc_format);
int quality = 11 * self->quality + 1;
uint32_t fourcc = nvnc_fb_get_fourcc_format(self->fb);
enum TJPF tjfmt = tight_get_jpeg_pixfmt(fourcc);
if (tjfmt == TJPF_UNKNOWN)
return -1;
vec_reserve(dst, 4096);
struct rfb_server_fb_rect rect = {
.encoding = htonl(RFB_ENCODING_TIGHT),
.x = htons(x),
.y = htons(y),
.width = htons(width),
.height = htons(height),
};
vec_append(dst, &rect, sizeof(rect));
tjhandle handle = tjInitCompress();
if (!handle)
return -1;
void* img = (uint32_t*)fb->addr + x + y * stride;
uint8_t* addr = nvnc_fb_get_addr(self->fb);
int32_t bpp = self->sfmt.bits_per_pixel / 8;
int32_t byte_stride = nvnc_fb_get_stride(self->fb) * bpp;
int32_t xoff = x * bpp;
uint8_t* img = addr + xoff + y * byte_stride;
tjCompress2(handle, img, width, stride * 4, height, tjfmt, &buffer,
&size, TJSAMP_422, quality, TJFLAG_FASTDCT);
enum TJSAMP subsampling = (quality == 9) ? TJSAMP_444 : TJSAMP_420;
vec_fast_append_8(dst, TIGHT_JPEG);
int rc = -1;
rc = tjCompress2(handle, img, width, byte_stride, height, tjfmt, &buffer,
&size, subsampling, quality, TJFLAG_FASTDCT);
if (rc < 0) {
nvnc_log(NVNC_LOG_ERROR, "Failed to encode tight JPEG box: %s",
tjGetErrorStr());
goto failure;
}
tight_encode_size(dst, size);
if (size > MAX_TILE_SIZE) {
nvnc_log(NVNC_LOG_ERROR, "Whoops, encoded JPEG was too big for the buffer");
goto failure;
}
vec_append(dst, buffer, size);
memcpy(tile->buffer, buffer, size);
tile->size = size;
rc = 0;
tjFree(buffer);
failure:
tjDestroy(handle);
return 0;
return rc;
}
#endif /* HAVE_JPEG */
int tight_encode_frame(struct vec* dst, struct nvnc_client* client,
const struct nvnc_fb* fb, struct pixman_region16* region)
static void tight_encode_tile(struct tight_encoder* self,
uint32_t gx, uint32_t gy)
{
int rc = -1;
struct tight_tile* tile = tight_tile(self, gx, gy);
int n_rects = 0;
struct pixman_box16* box = pixman_region_rectangles(region, &n_rects);
if (n_rects > UINT16_MAX) {
box = pixman_region_extents(region);
n_rects = 1;
uint32_t x = gx * TSL;
uint32_t y = gy * TSL;
uint32_t width = tight_tile_width(self, x);
uint32_t height = tight_tile_height(self, y);
tile->size = 0;
#ifdef HAVE_JPEG
if (self->quality >= 10) {
tight_encode_tile_basic(self, tile, x, y, width, height, gx % 4);
} else {
tight_encode_tile_jpeg(self, tile, x, y, width, height);
}
#else
tight_encode_tile_basic(self, tile, x, y, width, height, gx % 4);
#endif
tile->state = TIGHT_TILE_ENCODED;
}
static void do_tight_zs_work(void* obj)
{
struct tight_zs_worker_ctx* ctx = aml_get_userdata(obj);
struct tight_encoder* self = ctx->encoder;
int index = ctx->index;
for (uint32_t y = 0; y < self->grid_height; ++y)
for (uint32_t x = index; x < self->grid_width; x += 4)
if (tight_tile(self, x, y)->state == TIGHT_TILE_DAMAGED)
tight_encode_tile(self, x, y);
}
static void on_tight_zs_work_done(void* obj)
{
struct tight_zs_worker_ctx* ctx = aml_get_userdata(obj);
struct tight_encoder* self = ctx->encoder;
if (--self->n_jobs == 0) {
nvnc_fb_unref(self->fb);
schedule_tight_finish(self);
}
struct rfb_server_fb_update_msg head = {
.type = RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE,
.n_rects = htons(n_rects),
};
encoder_unref(&self->encoder);
}
rc = vec_append(dst, &head, sizeof(head));
static int tight_schedule_zs_work(struct tight_encoder* self, int index)
{
encoder_ref(&self->encoder);
int rc = aml_start(aml_get_default(), self->zs_worker[index]);
if (rc >= 0)
++self->n_jobs;
else
encoder_unref(&self->encoder);
return rc;
}
static int tight_schedule_encoding_jobs(struct tight_encoder* self)
{
for (int i = 0; i < 4; ++i)
if (tight_schedule_zs_work(self, i) < 0)
return -1;
return 0;
}
static void tight_finish_tile(struct tight_encoder* self,
uint32_t gx, uint32_t gy)
{
struct tight_tile* tile = tight_tile(self, gx, gy);
uint16_t x_pos = self->encoder.x_pos;
uint16_t y_pos = self->encoder.y_pos;
uint32_t x = gx * TSL;
uint32_t y = gy * TSL;
uint32_t width = tight_tile_width(self, x);
uint32_t height = tight_tile_height(self, y);
encode_rect_head(&self->dst, RFB_ENCODING_TIGHT, x_pos + x, y_pos + y,
width, height);
vec_append(&self->dst, &tile->type, sizeof(tile->type));
tight_encode_size(&self->dst, tile->size);
vec_append(&self->dst, tile->buffer, tile->size);
tile->state = TIGHT_TILE_READY;
}
static void tight_finish(struct tight_encoder* self)
{
for (uint32_t y = 0; y < self->grid_height; ++y)
for (uint32_t x = 0; x < self->grid_width; ++x)
if (tight_tile(self, x, y)->state == TIGHT_TILE_ENCODED)
tight_finish_tile(self, x, y);
}
static void do_tight_finish(void* obj)
{
struct tight_encoder* self = aml_get_userdata(obj);
tight_finish(self);
}
static void on_tight_finished(void* obj)
{
struct tight_encoder* self = aml_get_userdata(obj);
struct rcbuf* result = rcbuf_new(self->dst.data, self->dst.len);
assert(result);
self->encoder.n_rects = self->n_rects;
encoder_finish_frame(&self->encoder, result, self->pts);
self->pts = NVNC_NO_PTS;
rcbuf_unref(result);
encoder_unref(&self->encoder);
}
static int schedule_tight_finish(struct tight_encoder* self)
{
encoder_ref(&self->encoder);
struct aml_work* work = aml_work_new(do_tight_finish, on_tight_finished,
self, NULL);
if (!work) {
encoder_unref(&self->encoder);
return -1;
}
int rc = aml_start(aml_get_default(), work);
aml_unref(work);
return rc;
}
struct encoder* tight_encoder_new(uint16_t width, uint16_t height)
{
struct tight_encoder* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
if (tight_encoder_init(self, width, height) < 0) {
free(self);
return NULL;
}
encoder_init(&self->encoder, &encoder_impl_tight);
return (struct encoder*)self;
}
static void tight_encoder_destroy_wrapper(struct encoder* encoder)
{
tight_encoder_destroy(tight_encoder(encoder));
free(encoder);
}
static void tight_encoder_set_output_format(struct encoder* encoder,
const struct rfb_pixel_format* pixfmt)
{
struct tight_encoder* self = tight_encoder(encoder);
memcpy(&self->dfmt, pixfmt, sizeof(self->dfmt));
}
static void tight_encoder_set_quality(struct encoder* encoder, int value)
{
struct tight_encoder* self = tight_encoder(encoder);
self->quality = value;
}
static int tight_encoder_resize_wrapper(struct encoder* encoder, uint16_t width,
uint16_t height)
{
struct tight_encoder* self = tight_encoder(encoder);
return tight_encoder_resize(self, width, height);
}
static int tight_encoder_encode(struct encoder* encoder, struct nvnc_fb* fb,
struct pixman_region16* damage)
{
struct tight_encoder* self = tight_encoder(encoder);
int rc;
self->encoder.n_rects = 0;
rc = rfb_pixfmt_from_fourcc(&self->sfmt, nvnc_fb_get_fourcc_format(fb));
assert(rc == 0);
self->fb = fb;
self->pts = nvnc_fb_get_pts(fb);
rc = nvnc_fb_map(self->fb);
if (rc < 0)
return -1;
for (int i = 0; i < n_rects; ++i) {
int x = box[i].x1;
int y = box[i].y1;
int box_width = box[i].x2 - x;
int box_height = box[i].y2 - y;
rc = tight_encode_box(dst, client, fb, x, y,
fb->width, box_width, box_height);
uint32_t width = nvnc_fb_get_width(fb);
uint32_t height = nvnc_fb_get_height(fb);
rc = vec_init(&self->dst, width * height * 4);
if (rc < 0)
return -1;
self->n_rects = tight_apply_damage(self, damage);
assert(self->n_rects > 0);
nvnc_fb_ref(self->fb);
if (tight_schedule_encoding_jobs(self) < 0) {
nvnc_fb_unref(self->fb);
vec_destroy(&self->dst);
return -1;
}
return 0;
}
struct encoder_impl encoder_impl_tight = {
.destroy = tight_encoder_destroy_wrapper,
.set_output_format = tight_encoder_set_output_format,
.set_quality = tight_encoder_set_quality,
.resize = tight_encoder_resize_wrapper,
.encode = tight_encoder_encode,
};

View File

@ -0,0 +1,238 @@
/*
* Copyright (c) 2020 - 2021 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
* For code borrowed from wlroots:
* Copyright (c) 2017, 2018 Drew DeVault
* Copyright (c) 2014 Jari Vetoniemi
*
* 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.
*/
#include "transform-util.h"
#include "neatvnc.h"
#include <stdlib.h>
#include <pixman.h>
/* Note: This function yields the inverse pixman transform of the
* nvnc_transform.
*/
void nvnc_transform_to_pixman_transform(pixman_transform_t* dst,
enum nvnc_transform src, int width, int height)
{
#define F1 pixman_fixed_1
switch (src) {
case NVNC_TRANSFORM_NORMAL:
{
pixman_transform_t t = {{
{ F1, 0, 0 },
{ 0, F1, 0 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case NVNC_TRANSFORM_90:
{
pixman_transform_t t = {{
{ 0, F1, 0 },
{ -F1, 0, height * F1 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case NVNC_TRANSFORM_180:
{
pixman_transform_t t = {{
{ -F1, 0, width * F1 },
{ 0, -F1, height * F1 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case NVNC_TRANSFORM_270:
{
pixman_transform_t t = {{
{ 0, -F1, width * F1 },
{ F1, 0, 0 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case NVNC_TRANSFORM_FLIPPED:
{
pixman_transform_t t = {{
{ -F1, 0, width * F1 },
{ 0, F1, 0 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case NVNC_TRANSFORM_FLIPPED_90:
{
pixman_transform_t t = {{
{ 0, F1, 0 },
{ F1, 0, 0 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case NVNC_TRANSFORM_FLIPPED_180:
{
pixman_transform_t t = {{
{ F1, 0, 0 },
{ 0, -F1, height * F1 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case NVNC_TRANSFORM_FLIPPED_270:
{
pixman_transform_t t = {{
{ 0, -F1, width * F1 },
{ -F1, 0, height * F1 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
}
#undef F1
abort();
}
static bool is_transform_90_degrees(enum nvnc_transform transform)
{
switch (transform) {
case NVNC_TRANSFORM_90:
case NVNC_TRANSFORM_270:
case NVNC_TRANSFORM_FLIPPED_90:
case NVNC_TRANSFORM_FLIPPED_270:
return true;
default:
break;
}
return false;
}
void nvnc_transform_dimensions(enum nvnc_transform transform, uint32_t* width,
uint32_t* height)
{
if (is_transform_90_degrees(transform)) {
uint32_t tmp = *width;
*width = *height;
*height = tmp;
}
}
/* Borrowed this from wlroots */
void nvnc_transform_region(struct pixman_region16* dst,
struct pixman_region16* src, enum nvnc_transform transform,
int width, int height)
{
if (transform == NVNC_TRANSFORM_NORMAL) {
pixman_region_copy(dst, src);
return;
}
int nrects = 0;
pixman_box16_t* src_rects = pixman_region_rectangles(src, &nrects);
pixman_box16_t* dst_rects = malloc(nrects * sizeof(*dst_rects));
if (dst_rects == NULL) {
return;
}
for (int i = 0; i < nrects; ++i) {
switch (transform) {
case NVNC_TRANSFORM_NORMAL:
dst_rects[i].x1 = src_rects[i].x1;
dst_rects[i].y1 = src_rects[i].y1;
dst_rects[i].x2 = src_rects[i].x2;
dst_rects[i].y2 = src_rects[i].y2;
break;
case NVNC_TRANSFORM_90:
dst_rects[i].x1 = height - src_rects[i].y2;
dst_rects[i].y1 = src_rects[i].x1;
dst_rects[i].x2 = height - src_rects[i].y1;
dst_rects[i].y2 = src_rects[i].x2;
break;
case NVNC_TRANSFORM_180:
dst_rects[i].x1 = width - src_rects[i].x2;
dst_rects[i].y1 = height - src_rects[i].y2;
dst_rects[i].x2 = width - src_rects[i].x1;
dst_rects[i].y2 = height - src_rects[i].y1;
break;
case NVNC_TRANSFORM_270:
dst_rects[i].x1 = src_rects[i].y1;
dst_rects[i].y1 = width - src_rects[i].x2;
dst_rects[i].x2 = src_rects[i].y2;
dst_rects[i].y2 = width - src_rects[i].x1;
break;
case NVNC_TRANSFORM_FLIPPED:
dst_rects[i].x1 = width - src_rects[i].x2;
dst_rects[i].y1 = src_rects[i].y1;
dst_rects[i].x2 = width - src_rects[i].x1;
dst_rects[i].y2 = src_rects[i].y2;
break;
case NVNC_TRANSFORM_FLIPPED_90:
dst_rects[i].x1 = src_rects[i].y1;
dst_rects[i].y1 = src_rects[i].x1;
dst_rects[i].x2 = src_rects[i].y2;
dst_rects[i].y2 = src_rects[i].x2;
break;
case NVNC_TRANSFORM_FLIPPED_180:
dst_rects[i].x1 = src_rects[i].x1;
dst_rects[i].y1 = height - src_rects[i].y2;
dst_rects[i].x2 = src_rects[i].x2;
dst_rects[i].y2 = height - src_rects[i].y1;
break;
case NVNC_TRANSFORM_FLIPPED_270:
dst_rects[i].x1 = height - src_rects[i].y2;
dst_rects[i].y1 = width - src_rects[i].x2;
dst_rects[i].x2 = height - src_rects[i].y1;
dst_rects[i].y2 = width - src_rects[i].x1;
break;
}
}
pixman_region_fini(dst);
pixman_region_init_rects(dst, dst_rects, nrects);
free(dst_rects);
}

153
src/ws-framing.c 100644
View File

@ -0,0 +1,153 @@
/*
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "websocket.h"
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <arpa/inet.h>
static inline uint64_t u64_from_network_order(uint64_t x)
{
#if __BYTE_ORDER__ == __BIG_ENDIAN__
return x;
#else
return __builtin_bswap64(x);
#endif
}
static inline uint64_t u64_to_network_order(uint64_t x)
{
#if __BYTE_ORDER__ == __BIG_ENDIAN__
return x;
#else
return __builtin_bswap64(x);
#endif
}
const char *ws_opcode_name(enum ws_opcode op)
{
switch (op) {
case WS_OPCODE_CONT: return "cont";
case WS_OPCODE_TEXT: return "text";
case WS_OPCODE_BIN: return "bin";
case WS_OPCODE_CLOSE: return "close";
case WS_OPCODE_PING: return "ping";
case WS_OPCODE_PONG: return "pong";
}
return "INVALID";
}
bool ws_parse_frame_header(struct ws_frame_header* header,
const uint8_t* payload, size_t length)
{
if (length < 2)
return false;
int i = 0;
header->fin = !!(payload[i] & 0x80);
header->opcode = (payload[i++] & 0x0f);
header->mask = !!(payload[i] & 0x80);
header->payload_length = payload[i++] & 0x7f;
if (header->payload_length == 126) {
if (length - i < 2)
return false;
uint16_t value = 0;
memcpy(&value, &payload[i], 2);
header->payload_length = ntohs(value);
i += 2;
} else if (header->payload_length == 127) {
if (length - i < 8)
return false;
uint64_t value = 0;
memcpy(&value, &payload[i], 8);
header->payload_length = u64_from_network_order(value);
i += 8;
}
if (header->mask) {
if (length - i < 4)
return false;
memcpy(header->masking_key, &payload[i], 4);
i += 4;
}
header->header_length = i;
return true;
}
void ws_apply_mask(const struct ws_frame_header* header,
uint8_t* restrict payload)
{
assert(header->mask);
uint64_t len = header->payload_length;
const uint8_t* restrict key = header->masking_key;
for (uint64_t i = 0; i < len; ++i) {
payload[i] ^= key[i % 4];
}
}
void ws_copy_payload(const struct ws_frame_header* header,
uint8_t* restrict dst, const uint8_t* restrict src, size_t len)
{
if (!header->mask) {
memcpy(dst, src, len);
return;
}
const uint8_t* restrict key = header->masking_key;
for (uint64_t i = 0; i < len; ++i) {
dst[i] = src[i] ^ key[i % 4];
}
}
int ws_write_frame_header(uint8_t* dst, const struct ws_frame_header* header)
{
int i = 0;
dst[i++] = ((uint8_t)header->fin << 7) | (header->opcode);
if (header->payload_length <= 125) {
dst[i++] = ((uint8_t)header->mask << 7) | header->payload_length;
} else if (header->payload_length <= UINT16_MAX) {
dst[i++] = ((uint8_t)header->mask << 7) | 126;
uint16_t be = htons(header->payload_length);
memcpy(&dst[i], &be, 2);
i += 2;
} else {
dst[i++] = ((uint8_t)header->mask << 7) | 127;
uint64_t be = u64_to_network_order(header->payload_length);
memcpy(&dst[i], &be, 8);
i += 8;
}
if (header->mask) {
memcpy(dst, header->masking_key, 4);
i += 4;
}
return i;
}

106
src/ws-handshake.c 100644
View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2023 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "websocket.h"
#include "http.h"
#include "crypto.h"
#include "base64.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
static const char magic_uuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
static void tolower_and_remove_ws(char* dst, const char* src)
{
while (*src)
if (!isspace(*src))
*dst++ = tolower(*src++);
*dst = '\0';
}
// TODO: Do some more sanity checks on the input
ssize_t ws_handshake(char* output, size_t output_maxlen, const char* input)
{
bool ok = false;
struct http_req req = {};
if (http_req_parse(&req, input) < 0)
return -1;
char protocols[256] = ",";
char versions[256] = ",";
char tmpstring[256];
const char *challenge = NULL;
for (size_t i = 0; i < req.field_index; ++i) {
if (strcasecmp(req.field[i].key, "Sec-WebSocket-Key") == 0) {
challenge = req.field[i].value;
}
if (strcasecmp(req.field[i].key, "Sec-WebSocket-Protocol") == 0) {
snprintf(tmpstring, sizeof(tmpstring), "%s%s,",
protocols, req.field[i].value);
tolower_and_remove_ws(protocols, tmpstring);
}
if (strcasecmp(req.field[i].key, "Sec-WebSocket-Version") == 0) {
snprintf(tmpstring, sizeof(tmpstring), "%s%s,",
versions, req.field[i].value);
tolower_and_remove_ws(versions, tmpstring);
}
}
if (!challenge)
goto failure;
bool have_protocols = strlen(protocols) != 1;
bool have_versions = strlen(versions) != 1;
if (have_protocols && !strstr(protocols, ",chat,"))
goto failure;
if (have_versions && !strstr(versions, ",13,"))
goto failure;
uint8_t hash[20];
crypto_hash_many(hash, sizeof(hash), CRYPTO_HASH_SHA1,
(struct crypto_data_entry[]){
{ (uint8_t*)challenge, strlen(challenge) },
{ (uint8_t*)magic_uuid, strlen(magic_uuid) },
{}
});
char response[BASE64_ENCODED_SIZE(sizeof(hash))] = {};
base64_encode(response, hash, sizeof(hash));
size_t len = snprintf(output, output_maxlen,
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n"
"%s%s"
"\r\n",
response,
have_protocols ? "Sec-WebSocket-Protocol: char\r\n" : "",
have_versions ? "Sec-WebSocket-Version: 13\r\n" : "");
ssize_t header_len = req.header_length;
ok = len < output_maxlen;
failure:
http_req_free(&req);
return ok ? header_len : -1;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Andri Yngvason
* Copyright (c) 2019 - 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -16,77 +16,99 @@
#include "rfb-proto.h"
#include "vec.h"
#include "zrle.h"
#include "miniz.h"
#include "neatvnc.h"
#include "pixels.h"
#include "fb.h"
#include "enc-util.h"
#include "encoder.h"
#include "rcbuf.h"
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <uv.h>
#include <pixman.h>
#include <zlib.h>
#include <aml.h>
#define TILE_LENGTH 64
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
static inline int find_colour_in_palette(uint32_t* palette, int len,
uint32_t colour)
struct encoder* zrle_encoder_new(void);
struct zrle_encoder {
struct encoder encoder;
struct rfb_pixel_format output_format;
struct nvnc_fb* current_fb;
struct pixman_region16 current_damage;
struct rcbuf *current_result;
z_stream zs;
struct aml_work* work;
};
struct encoder_impl encoder_impl_zrle;
static inline struct zrle_encoder* zrle_encoder(struct encoder* encoder)
{
assert(encoder->impl == &encoder_impl_zrle);
return (struct zrle_encoder*)encoder;
}
static inline int find_colour_in_palette(uint8_t* palette, int len,
const uint8_t* colour, int bpp)
{
for (int i = 0; i < len; ++i)
if (palette[i] == colour)
if (memcmp(palette + i * bpp, colour, bpp) == 0)
return i;
return -1;
}
static inline int calc_bytes_per_cpixel(const struct rfb_pixel_format* fmt)
{
return fmt->bits_per_pixel == 32 ? fmt->depth / 8
: fmt->bits_per_pixel / 8;
}
int zrle_get_tile_palette(uint32_t* palette, const uint32_t* src, size_t length)
static int zrle_get_tile_palette(uint8_t* palette, const uint8_t* src,
const int src_bpp, size_t length)
{
int n = 0;
/* TODO: Maybe ignore the alpha channel */
palette[n++] = src[0];
memcpy(palette + (n++ * src_bpp), src, src_bpp);
for (size_t i = 0; i < length; ++i) {
uint32_t colour = src[i];
const uint8_t* colour_addr = src + i * src_bpp;
if (find_colour_in_palette(palette, n, colour) < 0) {
if (find_colour_in_palette(palette, n, colour_addr, src_bpp) < 0) {
if (n >= 16)
return -1;
palette[n++] = colour;
memcpy(palette + (n++ * src_bpp), colour_addr, src_bpp);
}
}
return n;
}
void zrle_encode_unichrome_tile(struct vec* dst,
static void zrle_encode_unichrome_tile(struct vec* dst,
const struct rfb_pixel_format* dst_fmt,
uint32_t colour,
uint8_t* colour,
const struct rfb_pixel_format* src_fmt)
{
int bytes_per_cpixel = calc_bytes_per_cpixel(dst_fmt);
vec_fast_append_8(dst, 1);
pixel32_to_cpixel(((uint8_t*)dst->data) + 1, dst_fmt, &colour, src_fmt,
pixel_to_cpixel(((uint8_t*)dst->data) + 1, dst_fmt, colour, src_fmt,
bytes_per_cpixel, 1);
dst->len += bytes_per_cpixel;
}
void encode_run_length(struct vec* dst, uint8_t index, int run_length)
static void encode_run_length(struct vec* dst, uint8_t index, int run_length)
{
if (run_length == 1) {
vec_fast_append_8(dst, index);
@ -103,16 +125,18 @@ void encode_run_length(struct vec* dst, uint8_t index, int run_length)
vec_fast_append_8(dst, run_length - 1);
}
void zrle_encode_packed_tile(struct vec* dst,
static void zrle_encode_packed_tile(struct vec* dst,
const struct rfb_pixel_format* dst_fmt,
const uint32_t* src,
const uint8_t* src,
const struct rfb_pixel_format* src_fmt,
size_t length, uint32_t* palette, int palette_size)
size_t length, uint8_t* palette,
int palette_size)
{
int bytes_per_cpixel = calc_bytes_per_cpixel(dst_fmt);
int src_bpp = src_fmt->bits_per_pixel / 8;
uint8_t cpalette[16 * 3];
pixel32_to_cpixel((uint8_t*)cpalette, dst_fmt, palette, src_fmt,
pixel_to_cpixel(cpalette, dst_fmt, palette, src_fmt,
bytes_per_cpixel, palette_size);
vec_fast_append_8(dst, 128 | palette_size);
@ -123,43 +147,46 @@ void zrle_encode_packed_tile(struct vec* dst,
int run_length = 1;
for (size_t i = 1; i < length; ++i) {
if (src[i] == src[i - 1]) {
if (memcmp(src + i * src_bpp, src + (i - 1) * src_bpp, src_bpp) == 0) {
run_length++;
continue;
}
index = find_colour_in_palette(palette, palette_size, src[i - 1]);
index = find_colour_in_palette(palette, palette_size, src + (i - 1) * src_bpp, src_bpp);
encode_run_length(dst, index, run_length);
run_length = 1;
}
if (run_length > 0) {
index = find_colour_in_palette(palette, palette_size,
src[length - 1]);
src + (length - 1) * src_bpp, src_bpp);
encode_run_length(dst, index, run_length);
}
}
void zrle_copy_tile(uint32_t* dst, const uint32_t* src, int stride, int width,
int height)
static void zrle_copy_tile(uint8_t* tile, const uint8_t* src, int src_bpp,
int stride, int width, int height)
{
int byte_stride = stride * src_bpp;
for (int y = 0; y < height; ++y)
memcpy(dst + y * width, src + y * stride, width * 4);
memcpy(tile + y * width * src_bpp, src + y * byte_stride, width * src_bpp);
}
void zrle_encode_tile(struct vec* dst, const struct rfb_pixel_format* dst_fmt,
const uint32_t* src,
const struct rfb_pixel_format* src_fmt, size_t length)
static void zrle_encode_tile(struct vec* dst,
const struct rfb_pixel_format* dst_fmt,
const uint8_t* src,
const struct rfb_pixel_format* src_fmt,
size_t length)
{
int bytes_per_cpixel = calc_bytes_per_cpixel(dst_fmt);
int src_bpp = src_fmt->bits_per_pixel / 8;
vec_clear(dst);
uint32_t palette[16];
int palette_size = zrle_get_tile_palette(palette, src, length);
uint8_t palette[16 * 4];
int palette_size = zrle_get_tile_palette(palette, src, src_bpp, length);
if (palette_size == 1) {
zrle_encode_unichrome_tile(dst, dst_fmt, palette[0], src_fmt);
zrle_encode_unichrome_tile(dst, dst_fmt, &palette[0], src_fmt);
return;
}
@ -171,16 +198,15 @@ void zrle_encode_tile(struct vec* dst, const struct rfb_pixel_format* dst_fmt,
vec_fast_append_8(dst, 0);
pixel32_to_cpixel(((uint8_t*)dst->data) + 1, dst_fmt, src, src_fmt,
pixel_to_cpixel(((uint8_t*)dst->data) + 1, dst_fmt, (uint8_t*)src, src_fmt,
bytes_per_cpixel, length);
dst->len += bytes_per_cpixel * length;
}
int zrle_deflate(struct vec* dst, const struct vec* src, z_stream* zs, bool flush)
static int zrle_deflate(struct vec* dst, const struct vec* src, z_stream* zs,
bool flush)
{
int r = Z_STREAM_ERROR;
zs->next_in = src->data;
zs->avail_in = src->len;
@ -191,8 +217,9 @@ int zrle_deflate(struct vec* dst, const struct vec* src, z_stream* zs, bool flus
zs->next_out = ((Bytef*)dst->data) + dst->len;
zs->avail_out = dst->cap - dst->len;
r = deflate(zs, flush ? Z_SYNC_FLUSH : Z_NO_FLUSH);
assert(r != Z_STREAM_ERROR);
int r = deflate(zs, flush ? Z_SYNC_FLUSH : Z_NO_FLUSH);
if (r == Z_STREAM_ERROR)
return -1;
dst->len = zs->next_out - (Bytef*)dst->data;
} while (zs->avail_out == 0);
@ -202,31 +229,29 @@ int zrle_deflate(struct vec* dst, const struct vec* src, z_stream* zs, bool flus
return 0;
}
int zrle_encode_box(struct vec* out, const struct rfb_pixel_format* dst_fmt,
static int zrle_encode_box(struct zrle_encoder* self, struct vec* out,
const struct rfb_pixel_format* dst_fmt,
const struct nvnc_fb* fb,
const struct rfb_pixel_format* src_fmt, int x, int y,
int stride, int width, int height, z_stream* zs)
{
int r = -1;
int bytes_per_cpixel = calc_bytes_per_cpixel(dst_fmt);
int src_bpp = src_fmt->bits_per_pixel / 8;
struct vec in;
uint32_t* tile = malloc(TILE_LENGTH * TILE_LENGTH * 4);
uint16_t x_pos = self->encoder.x_pos;
uint16_t y_pos = self->encoder.y_pos;
uint8_t* tile = malloc(TILE_LENGTH * TILE_LENGTH * 4);
if (!tile)
goto failure;
if (vec_init(&in, 1 + bytes_per_cpixel * TILE_LENGTH * TILE_LENGTH) < 0)
goto failure;
struct rfb_server_fb_rect rect = {
.encoding = htonl(RFB_ENCODING_ZRLE),
.x = htons(x),
.y = htons(y),
.width = htons(width),
.height = htons(height),
};
r = vec_append(out, &rect, sizeof(rect));
r = encode_rect_head(out, RFB_ENCODING_ZRLE, x_pos + x, y_pos + y,
width, height);
if (r < 0)
goto failure;
@ -246,10 +271,11 @@ int zrle_encode_box(struct vec* out, const struct rfb_pixel_format* dst_fmt,
? TILE_LENGTH
: height - tile_y;
int y_off = y + tile_y;
int y_off = (y + tile_y) * stride * src_bpp;
int x_off = (x + tile_x) * src_bpp;
zrle_copy_tile(tile,
((uint32_t*)fb->addr) + x + tile_x + y_off * stride,
((uint8_t*)fb->addr) + x_off + y_off, src_bpp,
stride, tile_width, tile_height);
zrle_encode_tile(&in, dst_fmt, tile, src_fmt,
@ -270,14 +296,15 @@ failure:
#undef CHUNK
}
int zrle_encode_frame(z_stream* zs, struct vec* dst,
const struct rfb_pixel_format* dst_fmt,
const struct nvnc_fb* src,
const struct rfb_pixel_format* src_fmt,
static int zrle_encode_frame(struct zrle_encoder* self, z_stream* zs,
struct vec* dst, const struct rfb_pixel_format* dst_fmt,
struct nvnc_fb* src, const struct rfb_pixel_format* src_fmt,
struct pixman_region16* region)
{
int rc = -1;
self->encoder.n_rects = 0;
int n_rects = 0;
struct pixman_box16* box = pixman_region_rectangles(region, &n_rects);
if (n_rects > UINT16_MAX) {
@ -285,12 +312,7 @@ int zrle_encode_frame(z_stream* zs, struct vec* dst,
n_rects = 1;
}
struct rfb_server_fb_update_msg head = {
.type = RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE,
.n_rects = htons(n_rects),
};
rc = vec_append(dst, &head, sizeof(head));
rc = nvnc_fb_map(src);
if (rc < 0)
return -1;
@ -300,11 +322,147 @@ int zrle_encode_frame(z_stream* zs, struct vec* dst,
int box_width = box[i].x2 - x;
int box_height = box[i].y2 - y;
rc = zrle_encode_box(dst, dst_fmt, src, src_fmt, x, y,
src->width, box_width, box_height, zs);
rc = zrle_encode_box(self, dst, dst_fmt, src, src_fmt, x, y,
src->stride, box_width, box_height, zs);
if (rc < 0)
return -1;
}
self->encoder.n_rects = n_rects;
return 0;
}
static void zrle_encoder_do_work(void* obj)
{
struct zrle_encoder* self = aml_get_userdata(obj);
int rc;
struct nvnc_fb* fb = self->current_fb;
assert(fb);
// TODO: Calculate the ideal buffer size based on the size of the
// damaged area.
size_t buffer_size = nvnc_fb_get_stride(fb) * nvnc_fb_get_height(fb) *
nvnc_fb_get_pixel_size(fb);
struct vec dst;
rc = vec_init(&dst, buffer_size);
assert(rc == 0);
struct rfb_pixel_format src_fmt;
rc = rfb_pixfmt_from_fourcc(&src_fmt, nvnc_fb_get_fourcc_format(fb));
assert(rc == 0);
rc = zrle_encode_frame(self, &self->zs, &dst, &self->output_format, fb,
&src_fmt, &self->current_damage);
assert(rc == 0);
self->current_result = rcbuf_new(dst.data, dst.len);
assert(self->current_result);
}
static void zrle_encoder_on_done(void* obj)
{
struct zrle_encoder* self = aml_get_userdata(obj);
assert(self->current_result);
uint64_t pts = nvnc_fb_get_pts(self->current_fb);
nvnc_fb_unref(self->current_fb);
self->current_fb = NULL;
pixman_region_clear(&self->current_damage);
struct rcbuf* result = self->current_result;
self->current_result = NULL;
aml_unref(self->work);
self->work = NULL;
encoder_finish_frame(&self->encoder, result, pts);
rcbuf_unref(result);
encoder_unref(&self->encoder);
}
struct encoder* zrle_encoder_new(void)
{
struct zrle_encoder* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
encoder_init(&self->encoder, &encoder_impl_zrle);
int rc = deflateInit2(&self->zs,
/* compression level: */ 1,
/* method: */ Z_DEFLATED,
/* window bits: */ 15,
/* mem level: */ 9,
/* strategy: */ Z_DEFAULT_STRATEGY);
if (rc != Z_OK)
goto deflate_failure;
pixman_region_init(&self->current_damage);
return (struct encoder*)self;
deflate_failure:
free(self);
return NULL;
}
static void zrle_encoder_destroy(struct encoder* encoder)
{
struct zrle_encoder* self = zrle_encoder(encoder);
pixman_region_fini(&self->current_damage);
deflateEnd(&self->zs);
if (self->work)
aml_unref(self->work);
if (self->current_result)
rcbuf_unref(self->current_result);
free(self);
}
static void zrle_encoder_set_output_format(struct encoder* encoder,
const struct rfb_pixel_format* pixfmt)
{
struct zrle_encoder* self = zrle_encoder(encoder);
memcpy(&self->output_format, pixfmt, sizeof(self->output_format));
}
static int zrle_encoder_encode(struct encoder* encoder, struct nvnc_fb* fb,
struct pixman_region16* damage)
{
struct zrle_encoder* self = zrle_encoder(encoder);
assert(!self->current_fb);
self->work = aml_work_new(zrle_encoder_do_work, zrle_encoder_on_done,
self, NULL);
if (!self->work)
return -1;
self->current_fb = fb;
nvnc_fb_ref(self->current_fb);
pixman_region_copy(&self->current_damage, damage);
encoder_ref(&self->encoder);
int rc = aml_start(aml_get_default(), self->work);
if (rc < 0) {
encoder_unref(&self->encoder);
aml_unref(self->work);
self->work = NULL;
pixman_region_clear(&self->current_damage);
nvnc_fb_unref(self->current_fb);
self->current_fb = NULL;
}
return rc;
}
struct encoder_impl encoder_impl_zrle = {
.destroy = zrle_encoder_destroy,
.set_output_format = zrle_encoder_set_output_format,
.encode = zrle_encoder_encode,
};

22
test/meson.build 100644
View File

@ -0,0 +1,22 @@
pixels = executable('pixels',
[
'test-pixels.c',
'../src/pixels.c',
],
include_directories: inc,
dependencies: [
pixman,
libdrm_inc,
libm,
],
)
test('pixels', pixels)
base64 = executable('base64',
[
'test-base64.c',
'../src/base64.c',
],
include_directories: inc,
)
test('base64', base64)

129
test/test-base64.c 100644
View File

@ -0,0 +1,129 @@
#include "base64.h"
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
static bool test_encode_0(void)
{
char buf[1] = {};
base64_encode(buf, (const uint8_t*)"", 0);
return strlen(buf) == 0;
}
static bool test_encode_1(void)
{
static const char input[] = "a";
char buf[BASE64_ENCODED_SIZE(sizeof(input) - 1)] = {};
base64_encode(buf, (const uint8_t*)input, sizeof(input) - 1);
return strcmp(buf, "YQ==") == 0;
}
static bool test_encode_2(void)
{
static const char input[] = "ab";
char buf[BASE64_ENCODED_SIZE(sizeof(input) - 1)] = {};
base64_encode(buf, (const uint8_t*)input, sizeof(input) - 1);
return strcmp(buf, "YWI=") == 0;
}
static bool test_encode_3(void)
{
static const char input[] = "abc";
char buf[BASE64_ENCODED_SIZE(sizeof(input) - 1)] = {};
base64_encode(buf, (const uint8_t*)input, sizeof(input) - 1);
return strcmp(buf, "YWJj") == 0;
}
static bool test_encode_4(void)
{
static const char input[] = "abcd";
char buf[BASE64_ENCODED_SIZE(sizeof(input) - 1)] = {};
base64_encode(buf, (const uint8_t*)input, sizeof(input) - 1);
return strcmp(buf, "YWJjZA==") == 0;
}
static bool test_encode_5(void)
{
static const char input[] = "abcde";
char buf[BASE64_ENCODED_SIZE(sizeof(input) - 1)] = {};
base64_encode(buf, (const uint8_t*)input, sizeof(input) - 1);
return strcmp(buf, "YWJjZGU=") == 0;
}
static bool test_decode_0(void)
{
uint8_t buf[1] = {};
ssize_t r = base64_decode(buf, "");
return r == 0 && buf[0] == 0;
}
static bool test_decode_1(void)
{
static const char input[] = "YQ==";
uint8_t buf[BASE64_DECODED_MAX_SIZE(sizeof(input) - 1)] = {};
ssize_t r = base64_decode(buf, input);
return r == 1 && memcmp(buf, "a", r) == 0;
}
static bool test_decode_2(void)
{
static const char input[] = "YWI=";
uint8_t buf[BASE64_DECODED_MAX_SIZE(sizeof(input) - 1)] = {};
ssize_t r = base64_decode(buf, input);
return r == 2 && memcmp(buf, "ab", r) == 0;
}
static bool test_decode_3(void)
{
static const char input[] = "YWJj";
uint8_t buf[BASE64_DECODED_MAX_SIZE(sizeof(input) - 1)] = {};
ssize_t r = base64_decode(buf, input);
return r == 3 && memcmp(buf, "abc", r) == 0;
}
static bool test_decode_4(void)
{
static const char input[] = "YWJjZA==";
uint8_t buf[BASE64_DECODED_MAX_SIZE(sizeof(input) - 1)] = {};
ssize_t r = base64_decode(buf, input);
return r == 4 && memcmp(buf, "abcd", r) == 0;
}
static bool test_decode_5(void)
{
static const char input[] = "YWJjZGU=";
uint8_t buf[BASE64_DECODED_MAX_SIZE(sizeof(input) - 1)] = {};
ssize_t r = base64_decode(buf, input);
return r == 5 && memcmp(buf, "abcde", r) == 0;
}
#define XSTR(s) STR(s)
#define STR(s) #s
#define RUN_TEST(name) ({ \
bool ok = test_ ## name(); \
printf("[%s] %s\n", ok ? " OK " : "FAIL", XSTR(name)); \
ok; \
})
int main()
{
bool ok = true;
ok &= RUN_TEST(encode_0);
ok &= RUN_TEST(encode_1);
ok &= RUN_TEST(encode_2);
ok &= RUN_TEST(encode_3);
ok &= RUN_TEST(encode_4);
ok &= RUN_TEST(encode_5);
ok &= RUN_TEST(decode_0);
ok &= RUN_TEST(decode_1);
ok &= RUN_TEST(decode_2);
ok &= RUN_TEST(decode_3);
ok &= RUN_TEST(decode_4);
ok &= RUN_TEST(decode_5);
return ok ? 0 : 1;
}

222
test/test-pixels.c 100644
View File

@ -0,0 +1,222 @@
#include "pixels.h"
#include "rfb-proto.h"
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <libdrm/drm_fourcc.h>
#define swap32(x) (((x >> 24) & 0xff) | ((x << 8) & 0xff0000) | \
((x >> 8) & 0xff00) | ((x << 24) & 0xff000000))
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define u32_le(x) x
#else
#define u32_le(x) swap32(x)
#endif
#define XSTR(s) STR(s)
#define STR(s) #s
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
#define ARRAY_LEN(a) (sizeof(a) / (sizeof(a[0])))
static bool test_pixel_to_cpixel_4bpp(void)
{
uint32_t src = u32_le(0x11223344u);
uint32_t dst;
uint8_t* src_addr = (uint8_t*)&src;
struct rfb_pixel_format dstfmt = { 0 }, srcfmt = { 0 };
rfb_pixfmt_from_fourcc(&dstfmt, DRM_FORMAT_RGBA8888);
dst = 0;
rfb_pixfmt_from_fourcc(&srcfmt, DRM_FORMAT_RGBA8888);
pixel_to_cpixel((uint8_t*)&dst, &dstfmt, src_addr, &srcfmt, 4, 1);
if ((src & 0xffffff00u) != (dst & 0xffffff00u))
return false;
dst = 0;
rfb_pixfmt_from_fourcc(&dstfmt, DRM_FORMAT_ABGR8888);
pixel_to_cpixel((uint8_t*)&dst, &dstfmt, src_addr, &srcfmt, 4, 1);
if (dst != u32_le(0x00332211u))
return false;
dst = 0;
rfb_pixfmt_from_fourcc(&dstfmt, DRM_FORMAT_ARGB8888);
pixel_to_cpixel((uint8_t*)&dst, &dstfmt, src_addr, &srcfmt, 4, 1);
if (dst != u32_le(0x00112233u))
return false;
dst = 0;
rfb_pixfmt_from_fourcc(&dstfmt, DRM_FORMAT_BGRA8888);
pixel_to_cpixel((uint8_t*)&dst, &dstfmt, src_addr, &srcfmt, 4, 1);
if (dst != u32_le(0x33221100u))
return false;
return true;
}
static bool test_pixel_to_cpixel_3bpp(void)
{
//44 is extra data that should not be copied anywhere below.
uint32_t src = u32_le(0x44112233u);
uint32_t dst;
uint8_t* src_addr = (uint8_t*)&src;
struct rfb_pixel_format dstfmt = { 0 }, srcfmt = { 0 };
rfb_pixfmt_from_fourcc(&srcfmt, DRM_FORMAT_RGB888);
dst = 0;
rfb_pixfmt_from_fourcc(&dstfmt, DRM_FORMAT_RGBA8888);
pixel_to_cpixel((uint8_t*)&dst, &dstfmt, src_addr, &srcfmt, 4, 1);
if (dst != u32_le(0x11223300u))
return false;
dst = 0;
rfb_pixfmt_from_fourcc(&dstfmt, DRM_FORMAT_ABGR8888);
pixel_to_cpixel((uint8_t*)&dst, &dstfmt, src_addr, &srcfmt, 4, 1);
if (dst != u32_le(0x00332211u))
return false;
dst = 0;
rfb_pixfmt_from_fourcc(&dstfmt, DRM_FORMAT_ARGB8888);
pixel_to_cpixel((uint8_t*)&dst, &dstfmt, src_addr, &srcfmt, 4, 1);
if (dst != u32_le(0x00112233u))
return false;
dst = 0;
rfb_pixfmt_from_fourcc(&dstfmt, DRM_FORMAT_BGRA8888);
pixel_to_cpixel((uint8_t*)&dst, &dstfmt, src_addr, &srcfmt, 4, 1);
if (dst != u32_le(0x33221100u))
return false;
return true;
}
static bool test_fourcc_to_pixman_fmt(void)
{
pixman_format_code_t r;
#define check(a, b) \
r = 0; \
if (!fourcc_to_pixman_fmt(&r, b) || a != r) { \
fprintf(stderr, "Failed check for %s\n", XSTR(a)); \
return false; \
} while(0)
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
check(PIXMAN_a2r10g10b10, DRM_FORMAT_ARGB2101010);
check(PIXMAN_r8g8b8a8, DRM_FORMAT_RGBA8888);
check(PIXMAN_b8g8r8a8, DRM_FORMAT_BGRA8888);
check(PIXMAN_r5g6b5, DRM_FORMAT_RGB565);
#else
check(PIXMAN_r8g8b8a8, DRM_FORMAT_ABGR8888);
check(PIXMAN_b8g8r8a8, DRM_FORMAT_ARGB8888);
check(PIXMAN_r5g6b5, DRM_FORMAT_BGR565);
#endif
#undef check
return true;
}
static bool test_extract_alpha_mask_rgba8888(void)
{
uint32_t test_data[] = {
u32_le(0x00000000u), // Black, transparent
u32_le(0xff000000u), // Red, transparent
u32_le(0x00ff0000u), // Red, transparent
u32_le(0x0000ff00u), // Green, transparent
u32_le(0x000000ffu), // Black, opaque
u32_le(0xff0000ffu), // Red, opaque
u32_le(0x00ff00ffu), // Red, opaque
u32_le(0x0000ffffu), // Green, opaque
};
uint8_t mask[UDIV_UP(ARRAY_LEN(test_data), 8)] = { 0 };
bool ok = extract_alpha_mask(mask, test_data, DRM_FORMAT_RGBA8888,
ARRAY_LEN(test_data));
if (!ok) {
fprintf(stderr, "Failed to extract alpha mask");
return false;
}
if (mask[0] != 0x0f) {
fprintf(stderr, "Expected alpha mask to be 0b00001111 (0x0f), but it was %x",
mask[0]);
return false;
}
memset(mask, 0, sizeof(mask));
ok = extract_alpha_mask(mask, test_data, DRM_FORMAT_BGRA8888,
ARRAY_LEN(test_data));
if (!ok) {
fprintf(stderr, "Failed to extract alpha mask");
return false;
}
if (mask[0] != 0x0f) {
fprintf(stderr, "Expected alpha mask to be 0b00001111 (0x0f), but it was %x",
mask[0]);
return false;
}
return true;
}
static bool test_drm_format_to_string(void)
{
if (strcmp(drm_format_to_string(DRM_FORMAT_RGBA8888), "RGBA8888") != 0)
return false;
if (strcmp(drm_format_to_string(DRM_FORMAT_RGBX8888), "RGBX8888") != 0)
return false;
if (strcmp(drm_format_to_string(DRM_FORMAT_RGB565), "RGB565") != 0)
return false;
return true;
}
static bool test_rfb_pixfmt_to_string(void)
{
struct rfb_pixel_format rgbx8888;
struct rfb_pixel_format bgrx8888;
struct rfb_pixel_format xrgb8888;
struct rfb_pixel_format xbgr8888;
rfb_pixfmt_from_fourcc(&rgbx8888, DRM_FORMAT_RGBX8888);
rfb_pixfmt_from_fourcc(&bgrx8888, DRM_FORMAT_BGRX8888);
rfb_pixfmt_from_fourcc(&xrgb8888, DRM_FORMAT_XRGB8888);
rfb_pixfmt_from_fourcc(&xbgr8888, DRM_FORMAT_XBGR8888);
if (strcmp(rfb_pixfmt_to_string(&rgbx8888), "RGBX8888") != 0)
return false;
if (strcmp(rfb_pixfmt_to_string(&bgrx8888), "BGRX8888") != 0)
return false;
if (strcmp(rfb_pixfmt_to_string(&xrgb8888), "XRGB8888") != 0)
return false;
if (strcmp(rfb_pixfmt_to_string(&xbgr8888), "XBGR8888") != 0)
return false;
return true;
}
int main()
{
bool ok = test_pixel_to_cpixel_4bpp() &&
test_pixel_to_cpixel_3bpp() &&
test_fourcc_to_pixman_fmt() &&
test_extract_alpha_mask_rgba8888() &&
test_drm_format_to_string() &&
test_rfb_pixfmt_to_string();
return ok ? 0 : 1;
}