From f2ebab8e0f3cea1267eab2fd06de92e7a9336bc4 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:34:46 +0200 Subject: [PATCH 01/10] Add SlirpService for Ethernet over BLE --- app/build.gradle.kts | 19 + app/src/main/cpp/CMakeLists.txt | 43 + app/src/main/cpp/libslirp/.clang-format | 58 + app/src/main/cpp/libslirp/.gitignore | 11 + app/src/main/cpp/libslirp/.gitlab-ci.yml | 110 ++ app/src/main/cpp/libslirp/.gitpublish | 3 + app/src/main/cpp/libslirp/CHANGELOG.md | 237 +++ app/src/main/cpp/libslirp/CMakeLists.txt | 45 + app/src/main/cpp/libslirp/COPYRIGHT | 62 + app/src/main/cpp/libslirp/README.md | 60 + .../cpp/libslirp/build-aux/git-version-gen | 158 ++ .../main/cpp/libslirp/fuzzing/IN_arp/arp.pcap | Bin 0 -> 162 bytes .../cpp/libslirp/fuzzing/IN_dhcp/dhcp.pkt | Bin 0 -> 439 bytes .../fuzzing/IN_dhcp/dhcp_capture.pcap | Bin 0 -> 1952 bytes .../fuzzing/IN_icmp/icmp_capture.pcap | Bin 0 -> 264 bytes .../fuzzing/IN_icmp/ping_10-0-2-2.pcap | Bin 0 -> 252 bytes .../fuzzing/IN_icmp6/icmp_capture.pcap | Bin 0 -> 304 bytes .../cpp/libslirp/fuzzing/IN_icmp6/ndp.pcap | 1 + .../fuzzing/IN_icmp6/ping_10-0-2-2.pcap | Bin 0 -> 292 bytes .../IN_ip-header/DNS_freedesktop_1-1-1-1.pcap | 1 + .../libslirp/fuzzing/IN_ip-header/dhcp.pkt | 1 + .../fuzzing/IN_ip-header/dhcp_capture.pcap | 1 + .../fuzzing/IN_ip-header/icmp_capture.pcap | 1 + .../IN_ip-header/nc-10.0.2.2-8080.pcap | 1 + .../fuzzing/IN_ip-header/nc-ident.pcap | 1 + .../fuzzing/IN_ip-header/ping_10-0-2-2.pcap | 1 + .../fuzzing/IN_ip-header/tcp_qemucapt.pcap | 1 + .../fuzzing/IN_ip-header/tftp-get-blah.pkt | 1 + .../fuzzing/IN_ip-header/tftp_capture.pcap | 1 + .../IN_ip-header/tftp_get_libslirp-txt.pcap | 1 + .../DNS_freedesktop_1-1-1-1.pcap | 1 + .../fuzzing/IN_ip6-header/icmp_capture.pcap | 1 + .../fuzzing/IN_ip6-header/ping_10-0-2-2.pcap | 1 + .../fuzzing/IN_ip6-header/tcp_qemucapt.pcap | 1 + .../fuzzing/IN_ip6-header/tftp_capture.pcap | 1 + .../IN_ip6-header/tftp_get_libslirp-txt.pcap | 1 + .../main/cpp/libslirp/fuzzing/IN_ndp/ndp.pcap | Bin 0 -> 228 bytes app/src/main/cpp/libslirp/fuzzing/IN_tcp-d | 1 + app/src/main/cpp/libslirp/fuzzing/IN_tcp-h | 1 + .../fuzzing/IN_tcp/nc-10.0.2.2-8080.pcap | Bin 0 -> 898 bytes .../cpp/libslirp/fuzzing/IN_tcp/nc-ident.pcap | Bin 0 -> 332 bytes .../libslirp/fuzzing/IN_tcp/tcp_qemucapt.pcap | Bin 0 -> 9048 bytes app/src/main/cpp/libslirp/fuzzing/IN_tcp6-d | 1 + app/src/main/cpp/libslirp/fuzzing/IN_tcp6-h | 1 + .../fuzzing/IN_tcp6/tcp_qemucapt.pcap | Bin 0 -> 9758 bytes .../fuzzing/IN_tftp/tftp-get-blah.pkt | Bin 0 -> 55 bytes .../fuzzing/IN_tftp/tftp_capture.pcap | Bin 0 -> 1767 bytes .../IN_tftp/tftp_get_libslirp-txt.pcap | Bin 0 -> 185 bytes .../fuzzing/IN_tftp6/tftp_capture.pcap | Bin 0 -> 1907 bytes .../IN_tftp6/tftp_get_libslirp-txt.pcap | Bin 0 -> 302 bytes app/src/main/cpp/libslirp/fuzzing/IN_udp-h | 1 + .../IN_udp/DNS_freedesktop_1-1-1-1.pcap | Bin 0 -> 256 bytes .../main/cpp/libslirp/fuzzing/IN_udp/dhcp.pkt | 1 + .../libslirp/fuzzing/IN_udp/dhcp_capture.pcap | 1 + .../libslirp/fuzzing/IN_udp/tftp-get-blah.pkt | 1 + .../libslirp/fuzzing/IN_udp/tftp_capture.pcap | 1 + .../fuzzing/IN_udp/tftp_get_libslirp-txt.pcap | 1 + app/src/main/cpp/libslirp/fuzzing/IN_udp6-h | 1 + .../IN_udp6/DNS_freedesktop_1-1-1-1.pcap | Bin 0 -> 296 bytes .../fuzzing/IN_udp6/tftp_capture.pcap | 1 + .../IN_udp6/tftp_get_libslirp-txt.pcap | 1 + app/src/main/cpp/libslirp/fuzzing/README.md | 59 + app/src/main/cpp/libslirp/fuzzing/coverage.py | 37 + .../cpp/libslirp/fuzzing/fuzz-input.options | 2 + app/src/main/cpp/libslirp/fuzzing/fuzz-main.c | 35 + app/src/main/cpp/libslirp/fuzzing/helper.c | 271 +++ app/src/main/cpp/libslirp/fuzzing/helper.h | 24 + app/src/main/cpp/libslirp/fuzzing/meson.build | 64 + app/src/main/cpp/libslirp/fuzzing/oss-fuzz.sh | 48 + .../main/cpp/libslirp/fuzzing/reproducer.c | 45 + .../cpp/libslirp/fuzzing/slirp_base_fuzz.h | 23 + .../cpp/libslirp/fuzzing/slirp_fuzz_arp.c | 90 + .../cpp/libslirp/fuzzing/slirp_fuzz_icmp.c | 129 ++ .../cpp/libslirp/fuzzing/slirp_fuzz_icmp6.c | 134 ++ .../libslirp/fuzzing/slirp_fuzz_ip6_header.c | 103 ++ .../libslirp/fuzzing/slirp_fuzz_ip_header.c | 112 ++ .../cpp/libslirp/fuzzing/slirp_fuzz_tcp.c | 138 ++ .../cpp/libslirp/fuzzing/slirp_fuzz_tcp6.c | 134 ++ .../libslirp/fuzzing/slirp_fuzz_tcp6_data.c | 137 ++ .../libslirp/fuzzing/slirp_fuzz_tcp6_header.c | 136 ++ .../libslirp/fuzzing/slirp_fuzz_tcp_data.c | 141 ++ .../libslirp/fuzzing/slirp_fuzz_tcp_header.c | 140 ++ .../cpp/libslirp/fuzzing/slirp_fuzz_udp.c | 121 ++ .../cpp/libslirp/fuzzing/slirp_fuzz_udp6.c | 120 ++ .../libslirp/fuzzing/slirp_fuzz_udp6_data.c | 122 ++ .../libslirp/fuzzing/slirp_fuzz_udp6_header.c | 121 ++ .../libslirp/fuzzing/slirp_fuzz_udp_data.c | 123 ++ .../libslirp/fuzzing/slirp_fuzz_udp_header.c | 122 ++ app/src/main/cpp/libslirp/fuzzing/tftp/toto | Bin 0 -> 1300 bytes app/src/main/cpp/libslirp/meson.build | 243 +++ app/src/main/cpp/libslirp/meson_options.txt | 14 + app/src/main/cpp/libslirp/src/arp_table.c | 98 + app/src/main/cpp/libslirp/src/bootp.c | 398 ++++ app/src/main/cpp/libslirp/src/bootp.h | 131 ++ app/src/main/cpp/libslirp/src/cksum.c | 179 ++ app/src/main/cpp/libslirp/src/debug.h | 66 + app/src/main/cpp/libslirp/src/dhcpv6.c | 224 +++ app/src/main/cpp/libslirp/src/dhcpv6.h | 69 + app/src/main/cpp/libslirp/src/dnssearch.c | 306 +++ app/src/main/cpp/libslirp/src/if.c | 201 ++ app/src/main/cpp/libslirp/src/if.h | 25 + app/src/main/cpp/libslirp/src/ip.h | 238 +++ app/src/main/cpp/libslirp/src/ip6.h | 224 +++ app/src/main/cpp/libslirp/src/ip6_icmp.c | 652 +++++++ app/src/main/cpp/libslirp/src/ip6_icmp.h | 246 +++ app/src/main/cpp/libslirp/src/ip6_input.c | 88 + app/src/main/cpp/libslirp/src/ip6_output.c | 45 + app/src/main/cpp/libslirp/src/ip_icmp.c | 547 ++++++ app/src/main/cpp/libslirp/src/ip_icmp.h | 183 ++ app/src/main/cpp/libslirp/src/ip_input.c | 450 +++++ app/src/main/cpp/libslirp/src/ip_output.c | 171 ++ app/src/main/cpp/libslirp/src/libslirp.h | 342 ++++ app/src/main/cpp/libslirp/src/libslirp.map | 40 + app/src/main/cpp/libslirp/src/libvdeslirp.c | 525 ++++++ app/src/main/cpp/libslirp/src/libvdeslirp.h | 48 + app/src/main/cpp/libslirp/src/main.h | 23 + app/src/main/cpp/libslirp/src/mbuf.c | 291 +++ app/src/main/cpp/libslirp/src/mbuf.h | 225 +++ app/src/main/cpp/libslirp/src/minglib.h | 37 + app/src/main/cpp/libslirp/src/misc.c | 448 +++++ app/src/main/cpp/libslirp/src/misc.h | 87 + app/src/main/cpp/libslirp/src/ncsi-pkt.h | 527 ++++++ app/src/main/cpp/libslirp/src/ncsi.c | 326 ++++ app/src/main/cpp/libslirp/src/ndp_table.c | 98 + app/src/main/cpp/libslirp/src/sbuf.c | 157 ++ app/src/main/cpp/libslirp/src/sbuf.h | 46 + app/src/main/cpp/libslirp/src/slirp.c | 1642 +++++++++++++++++ app/src/main/cpp/libslirp/src/slirp.h | 386 ++++ app/src/main/cpp/libslirp/src/socket.c | 1249 +++++++++++++ app/src/main/cpp/libslirp/src/socket.h | 236 +++ app/src/main/cpp/libslirp/src/state.c | 381 ++++ app/src/main/cpp/libslirp/src/stream.c | 120 ++ app/src/main/cpp/libslirp/src/stream.h | 47 + app/src/main/cpp/libslirp/src/tcp.h | 169 ++ app/src/main/cpp/libslirp/src/tcp_input.c | 1566 ++++++++++++++++ app/src/main/cpp/libslirp/src/tcp_output.c | 513 +++++ app/src/main/cpp/libslirp/src/tcp_subr.c | 986 ++++++++++ app/src/main/cpp/libslirp/src/tcp_timer.c | 283 +++ app/src/main/cpp/libslirp/src/tcp_timer.h | 133 ++ app/src/main/cpp/libslirp/src/tcp_var.h | 161 ++ app/src/main/cpp/libslirp/src/tcpip.h | 105 ++ app/src/main/cpp/libslirp/src/tftp.c | 484 +++++ app/src/main/cpp/libslirp/src/tftp.h | 64 + app/src/main/cpp/libslirp/src/udp.c | 427 +++++ app/src/main/cpp/libslirp/src/udp.h | 106 ++ app/src/main/cpp/libslirp/src/udp6.c | 196 ++ app/src/main/cpp/libslirp/src/util.c | 441 +++++ app/src/main/cpp/libslirp/src/util.h | 208 +++ app/src/main/cpp/libslirp/src/version.c | 8 + app/src/main/cpp/libslirp/src/vmstate.c | 445 +++++ app/src/main/cpp/libslirp/src/vmstate.h | 401 ++++ app/src/main/cpp/libslirp/test/ncsitest.c | 176 ++ app/src/main/cpp/libslirp/test/pingtest.c | 490 +++++ app/src/main/cpp/sync.cpp | 78 + .../sync/asteroid/AsteroidBleManager.java | 26 +- .../sync/asteroid/IAsteroidDevice.java | 1 + .../sync/connectivity/SlirpService.java | 141 ++ .../sync/services/SynchronizationService.java | 7 + .../asteroidos/sync/utils/AsteroidUUIDS.java | 5 + 159 files changed, 23264 insertions(+), 3 deletions(-) create mode 100644 app/src/main/cpp/CMakeLists.txt create mode 100644 app/src/main/cpp/libslirp/.clang-format create mode 100644 app/src/main/cpp/libslirp/.gitignore create mode 100644 app/src/main/cpp/libslirp/.gitlab-ci.yml create mode 100644 app/src/main/cpp/libslirp/.gitpublish create mode 100644 app/src/main/cpp/libslirp/CHANGELOG.md create mode 100644 app/src/main/cpp/libslirp/CMakeLists.txt create mode 100644 app/src/main/cpp/libslirp/COPYRIGHT create mode 100644 app/src/main/cpp/libslirp/README.md create mode 100755 app/src/main/cpp/libslirp/build-aux/git-version-gen create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_arp/arp.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_dhcp/dhcp.pkt create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_dhcp/dhcp_capture.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_icmp/icmp_capture.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_icmp/ping_10-0-2-2.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_icmp6/icmp_capture.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_icmp6/ndp.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_icmp6/ping_10-0-2-2.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip-header/DNS_freedesktop_1-1-1-1.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip-header/dhcp.pkt create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip-header/dhcp_capture.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip-header/icmp_capture.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip-header/nc-10.0.2.2-8080.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip-header/nc-ident.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip-header/ping_10-0-2-2.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip-header/tcp_qemucapt.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip-header/tftp-get-blah.pkt create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip-header/tftp_capture.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip-header/tftp_get_libslirp-txt.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip6-header/DNS_freedesktop_1-1-1-1.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip6-header/icmp_capture.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip6-header/ping_10-0-2-2.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip6-header/tcp_qemucapt.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip6-header/tftp_capture.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_ip6-header/tftp_get_libslirp-txt.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_ndp/ndp.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_tcp-d create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_tcp-h create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_tcp/nc-10.0.2.2-8080.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_tcp/nc-ident.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_tcp/tcp_qemucapt.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_tcp6-d create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_tcp6-h create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_tcp6/tcp_qemucapt.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_tftp/tftp-get-blah.pkt create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_tftp/tftp_capture.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_tftp/tftp_get_libslirp-txt.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_tftp6/tftp_capture.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_tftp6/tftp_get_libslirp-txt.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_udp-h create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_udp/DNS_freedesktop_1-1-1-1.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_udp/dhcp.pkt create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_udp/dhcp_capture.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_udp/tftp-get-blah.pkt create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_udp/tftp_capture.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_udp/tftp_get_libslirp-txt.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_udp6-h create mode 100644 app/src/main/cpp/libslirp/fuzzing/IN_udp6/DNS_freedesktop_1-1-1-1.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_udp6/tftp_capture.pcap create mode 100755 app/src/main/cpp/libslirp/fuzzing/IN_udp6/tftp_get_libslirp-txt.pcap create mode 100644 app/src/main/cpp/libslirp/fuzzing/README.md create mode 100755 app/src/main/cpp/libslirp/fuzzing/coverage.py create mode 100644 app/src/main/cpp/libslirp/fuzzing/fuzz-input.options create mode 100644 app/src/main/cpp/libslirp/fuzzing/fuzz-main.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/helper.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/helper.h create mode 100644 app/src/main/cpp/libslirp/fuzzing/meson.build create mode 100755 app/src/main/cpp/libslirp/fuzzing/oss-fuzz.sh create mode 100644 app/src/main/cpp/libslirp/fuzzing/reproducer.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_base_fuzz.h create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_arp.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_icmp.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_icmp6.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_ip6_header.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_ip_header.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp6.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp6_data.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp6_header.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp_data.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp_header.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp6.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp6_data.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp6_header.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp_data.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp_header.c create mode 100644 app/src/main/cpp/libslirp/fuzzing/tftp/toto create mode 100644 app/src/main/cpp/libslirp/meson.build create mode 100644 app/src/main/cpp/libslirp/meson_options.txt create mode 100644 app/src/main/cpp/libslirp/src/arp_table.c create mode 100644 app/src/main/cpp/libslirp/src/bootp.c create mode 100644 app/src/main/cpp/libslirp/src/bootp.h create mode 100644 app/src/main/cpp/libslirp/src/cksum.c create mode 100644 app/src/main/cpp/libslirp/src/debug.h create mode 100644 app/src/main/cpp/libslirp/src/dhcpv6.c create mode 100644 app/src/main/cpp/libslirp/src/dhcpv6.h create mode 100644 app/src/main/cpp/libslirp/src/dnssearch.c create mode 100644 app/src/main/cpp/libslirp/src/if.c create mode 100644 app/src/main/cpp/libslirp/src/if.h create mode 100644 app/src/main/cpp/libslirp/src/ip.h create mode 100644 app/src/main/cpp/libslirp/src/ip6.h create mode 100644 app/src/main/cpp/libslirp/src/ip6_icmp.c create mode 100644 app/src/main/cpp/libslirp/src/ip6_icmp.h create mode 100644 app/src/main/cpp/libslirp/src/ip6_input.c create mode 100644 app/src/main/cpp/libslirp/src/ip6_output.c create mode 100644 app/src/main/cpp/libslirp/src/ip_icmp.c create mode 100644 app/src/main/cpp/libslirp/src/ip_icmp.h create mode 100644 app/src/main/cpp/libslirp/src/ip_input.c create mode 100644 app/src/main/cpp/libslirp/src/ip_output.c create mode 100644 app/src/main/cpp/libslirp/src/libslirp.h create mode 100644 app/src/main/cpp/libslirp/src/libslirp.map create mode 100644 app/src/main/cpp/libslirp/src/libvdeslirp.c create mode 100644 app/src/main/cpp/libslirp/src/libvdeslirp.h create mode 100644 app/src/main/cpp/libslirp/src/main.h create mode 100644 app/src/main/cpp/libslirp/src/mbuf.c create mode 100644 app/src/main/cpp/libslirp/src/mbuf.h create mode 100644 app/src/main/cpp/libslirp/src/minglib.h create mode 100644 app/src/main/cpp/libslirp/src/misc.c create mode 100644 app/src/main/cpp/libslirp/src/misc.h create mode 100644 app/src/main/cpp/libslirp/src/ncsi-pkt.h create mode 100644 app/src/main/cpp/libslirp/src/ncsi.c create mode 100644 app/src/main/cpp/libslirp/src/ndp_table.c create mode 100644 app/src/main/cpp/libslirp/src/sbuf.c create mode 100644 app/src/main/cpp/libslirp/src/sbuf.h create mode 100644 app/src/main/cpp/libslirp/src/slirp.c create mode 100644 app/src/main/cpp/libslirp/src/slirp.h create mode 100644 app/src/main/cpp/libslirp/src/socket.c create mode 100644 app/src/main/cpp/libslirp/src/socket.h create mode 100644 app/src/main/cpp/libslirp/src/state.c create mode 100644 app/src/main/cpp/libslirp/src/stream.c create mode 100644 app/src/main/cpp/libslirp/src/stream.h create mode 100644 app/src/main/cpp/libslirp/src/tcp.h create mode 100644 app/src/main/cpp/libslirp/src/tcp_input.c create mode 100644 app/src/main/cpp/libslirp/src/tcp_output.c create mode 100644 app/src/main/cpp/libslirp/src/tcp_subr.c create mode 100644 app/src/main/cpp/libslirp/src/tcp_timer.c create mode 100644 app/src/main/cpp/libslirp/src/tcp_timer.h create mode 100644 app/src/main/cpp/libslirp/src/tcp_var.h create mode 100644 app/src/main/cpp/libslirp/src/tcpip.h create mode 100644 app/src/main/cpp/libslirp/src/tftp.c create mode 100644 app/src/main/cpp/libslirp/src/tftp.h create mode 100644 app/src/main/cpp/libslirp/src/udp.c create mode 100644 app/src/main/cpp/libslirp/src/udp.h create mode 100644 app/src/main/cpp/libslirp/src/udp6.c create mode 100644 app/src/main/cpp/libslirp/src/util.c create mode 100644 app/src/main/cpp/libslirp/src/util.h create mode 100644 app/src/main/cpp/libslirp/src/version.c create mode 100644 app/src/main/cpp/libslirp/src/vmstate.c create mode 100644 app/src/main/cpp/libslirp/src/vmstate.h create mode 100644 app/src/main/cpp/libslirp/test/ncsitest.c create mode 100644 app/src/main/cpp/libslirp/test/pingtest.c create mode 100644 app/src/main/cpp/sync.cpp create mode 100644 app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6d8e6703..43176688 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,6 +12,16 @@ android { targetSdk = 33 versionCode = 29 versionName = "0.29" + ndk.abiFilters.clear() + ndk.abiFilters.add("arm64-v8a") + ndk.abiFilters.add("armeabi-v7a") + ndk.abiFilters.add("x86") + ndk.abiFilters.add("x86_64") + externalNativeBuild { + cmake { + cppFlags += "" + } + } } buildTypes { named("release") { @@ -31,6 +41,9 @@ android { srcDir("src/main/lib/material-intro-screen/material-intro-screen/src/main/res/") srcDir("src/main/lib/powerampapi/poweramp_api_lib/res/") } + jniLibs { + srcDir("src/main/cpp/lib") + } } } @@ -43,6 +56,12 @@ android { disable += "MissingTranslation" } namespace = "org.asteroidos.sync" + externalNativeBuild { + cmake { + path = file("src/main/cpp/CMakeLists.txt") + version = "3.22.1" + } + } } repositories { diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..01bde151 --- /dev/null +++ b/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,43 @@ + +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html. +# For more examples on how to use CMake, see https://github.com/android/ndk-samples. + +# Sets the minimum CMake version required for this project. +cmake_minimum_required(VERSION 3.22.1) + +# Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, +# Since this is the top level CMakeLists.txt, the project name is also accessible +# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level +# build script scope). +project("sync") + +add_subdirectory(libslirp) + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. +# +# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define +# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} +# is preferred for the same purpose. +# +# In order to load a library into your app from Java/Kotlin, you must call +# System.loadLibrary() and pass the name of the library defined here; +# for GameActivity/NativeActivity derived applications, the same library name must be +# used in the AndroidManifest.xml file. +add_library(${CMAKE_PROJECT_NAME} SHARED + # List C/C++ source files with relative paths to this CMakeLists.txt. + sync.cpp) + +# Specifies libraries CMake should link to your target library. You +# can link libraries from various origins, such as libraries defined in this +# build script, prebuilt third-party libraries, or Android system libraries. +target_link_libraries(${CMAKE_PROJECT_NAME} + slirp + android + log) + +target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE + -fvisibility=hidden) diff --git a/app/src/main/cpp/libslirp/.clang-format b/app/src/main/cpp/libslirp/.clang-format new file mode 100644 index 00000000..17fb49fe --- /dev/null +++ b/app/src/main/cpp/libslirp/.clang-format @@ -0,0 +1,58 @@ +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +--- +Language: Cpp +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false # although we like it, it creates churn +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: true +AlignTrailingComments: false # churn +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None # AlwaysBreakAfterDefinitionReturnType is taken into account +AlwaysBreakBeforeMultilineStrings: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterStruct: false + AfterUnion: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: false +BreakStringLiterals: true +ColumnLimit: 80 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '.*_BEGIN$' # only PREC_BEGIN ? +MacroBlockEnd: '.*_END$' +MaxEmptyLinesToKeep: 2 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInContainerLiterals: true +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +UseTab: Never +... diff --git a/app/src/main/cpp/libslirp/.gitignore b/app/src/main/cpp/libslirp/.gitignore new file mode 100644 index 00000000..bd362c29 --- /dev/null +++ b/app/src/main/cpp/libslirp/.gitignore @@ -0,0 +1,11 @@ +*.[aod] +*.gcda +*.gcno +*.gcov +*.lib +*.obj +/build/ +/TAGS +/cscope* +/src/libslirp-version.h +/tags diff --git a/app/src/main/cpp/libslirp/.gitlab-ci.yml b/app/src/main/cpp/libslirp/.gitlab-ci.yml new file mode 100644 index 00000000..7c4584eb --- /dev/null +++ b/app/src/main/cpp/libslirp/.gitlab-ci.yml @@ -0,0 +1,110 @@ +image: fedora:latest + +variables: + DEPS: meson ninja-build + gcc libasan liblsan libubsan pkg-config glib2-devel + mingw64-gcc mingw64-pkg-config mingw64-glib2 + clang-analyzer git-core + +before_script: + - dnf install -y $DEPS + - git fetch --tags https://gitlab.freedesktop.org/slirp/libslirp.git + - git describe + +build: + script: + - meson --werror build || (cat build/meson-logs/meson-log.txt && exit 1) + - ninja -C build + - (cd build && meson test) || (cat build/meson-logs/testlog.txt && exit 1) + - ninja -C build scan-build + +build-asan: + script: + - CFLAGS=-fsanitize=address meson --werror build || (cat build/meson-logs/meson-log.txt && exit 1) + - ninja -C build + - (cd build && ASAN_OPTIONS=detect_leaks=0 meson test) || (cat build/meson-logs/testlog.txt && exit 1) + +build-lsan: + script: + - CFLAGS=-fsanitize=leak meson --werror build || (cat build/meson-logs/meson-log.txt && exit 1) + - ninja -C build + - (cd build && meson test) || (cat build/meson-logs/testlog.txt && exit 1) + +build-usan: + script: + - CFLAGS=-fsanitize=undefined meson --werror build || (cat build/meson-logs/meson-log.txt && exit 1) + - ninja -C build + - (cd build && meson test) || (cat build/meson-logs/testlog.txt && exit 1) + +fuzz: + parallel: + matrix: + - TARGET: [arp, ip-header, udp, udp-h, tftp, dhcp, icmp, tcp, tcp-h, ndp, ip6-header, udp6, udp6-h, tftp6, icmp6, tcp6, tcp6-h] + script: + - CC=clang CXX=clang++ meson build -Dllvm-fuzz=true || (cat build/meson-logs/meson-log.txt && exit 1) + - ninja -C build + - build/fuzzing/fuzz-$TARGET -seed=1234 -runs=1000000 fuzzing/IN_$TARGET + artifacts: + when: on_failure + paths: + - crash-* + - leak-* + - oom-* + - timeout-* + +build-mingw64: + script: + - (mkdir buildw && cd buildw && mingw64-meson --werror) || (cat buildw/meson-logs/meson-log.txt && exit 1) + - ninja -C buildw + +Coverity: + only: + refs: + - master + - coverity + script: + - dnf update -y + - dnf install -y curl clang + - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 + --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN + - tar xfz /tmp/cov-analysis-linux64.tgz + - CC=clang meson build + - cov-analysis-linux64-*/bin/cov-build --dir cov-int ninja -C build + - tar cfz cov-int.tar.gz cov-int + - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME + --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL + --form file=@cov-int.tar.gz --form version="`git describe --tags`" + --form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID " + +integration-slirp4netns: + variables: + SLIRP4NETNS_VERSION: "v1.1.12" + # Consumed by `make benchmark` + BENCHMARK_IPERF3_DURATION: "10" + script: + # Install libslirp + - meson build + - ninja -C build install + # Register the path of libslirp.so.0 + - echo /usr/local/lib64 >/etc/ld.so.conf.d/libslirp.conf + - ldconfig + # Install the dependencies of slirp4netns and its test suite + # TODO: install udhcpc for `slirp4netns/tests/test-slirp4netns-dhcp.sh` (currently skipped, due to lack of udhcpc) + - dnf install -y autoconf automake findutils iperf3 iproute iputils jq libcap-devel libseccomp-devel nmap-ncat util-linux + # Check whether the runner environment is configured correctly + - unshare -rn true || (echo Make sure you have relaxed seccomp and appamor && exit 1) + - unshare -rn ip tap add tap0 mode tap || (echo Make sure you have /dev/net/tun && exit 1) + # Install slirp4netns + - git clone https://github.com/rootless-containers/slirp4netns -b "${SLIRP4NETNS_VERSION}" + - cd slirp4netns + - ./autogen.sh + - ./configure + - make + - make install + - slirp4netns --version + # Run slirp4netns integration test + - make distcheck || (cat $(find . -name 'test-suite.log' ) && exit 1) + # Run benchmark test to ensure that libslirp can actually handle packets, with several MTU configurations + - make benchmark MTU=1500 + - make benchmark MTU=512 + - make benchmark MTU=65520 diff --git a/app/src/main/cpp/libslirp/.gitpublish b/app/src/main/cpp/libslirp/.gitpublish new file mode 100644 index 00000000..7b852951 --- /dev/null +++ b/app/src/main/cpp/libslirp/.gitpublish @@ -0,0 +1,3 @@ +[gitpublishprofile "default"] +base = master +to = slirp@lists.freedesktop.org diff --git a/app/src/main/cpp/libslirp/CHANGELOG.md b/app/src/main/cpp/libslirp/CHANGELOG.md new file mode 100644 index 00000000..29717f0c --- /dev/null +++ b/app/src/main/cpp/libslirp/CHANGELOG.md @@ -0,0 +1,237 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [4.8.0] - TODO + +## Security + + - tcp: Fix testing for last fragment + - tftp: Fix use-after-free + +### Added + + - Add support for Haiku !123 + - ncsi: Add manufacturer's ID !122 + - ncsi: Add Get Version ID command !122 + - ncsi: Add out-of-band ethernet address !125 + - ncsi: Add Mellanox Get Mac Address handler !125 + - icmp6: Add echo request forwarding support + - Add fuzzing infrastructure + +### Fixed + + - Fix missing cleanups + - windows: Build fixes + - ipv6: Use target address from Neighbor Advertisement !129 + - dns: Reject domain-search when any entry ends with ".." + - dns: Use localhost as dns when /etc/resolv.conf empty !130 + - icmp: Handle ICMP packets as IPPROTO_IP on BSD !133 + - eth: pad ethernet frames to 60 bytes #34 + +### Removed + + - windows: Bump the minimum Windows version to Windows 7 + +## [4.7.0] - 2022-04-26 + +### Added + + - Allow disabling the internal DHCP server !22 + - icmp: Support falling back on trying a SOCK_RAW socket !92 + - Support Unix sockets in hostfwd !103 + - IPv6 DNS proxying support !110 + - bootp: add support for UEFI HTTP boot !111 + - New callback that supports CFI better !117 + +### Fixed + + - dhcp: Always send DHCP_OPT_LEN bytes in options !97 + - Fix Haiku build !98 !99 + - Fix memory leak when using libresolv !100 + - Ensure sin6_scope_id is zero for global addresses !102 + - resolv: fix IPv6 resolution on Darwin !104 + - socket: Initialize so_type in socreate !109 + - Handle ECONNABORTED from recv !116 + +## [4.6.1] - 2021-06-18 + +### Fixed + + - Fix DHCP regression introduced in 4.6.0. !95 + +## [4.6.0] - 2021-06-14 + +### Added + + - mbuf: Add debugging helpers for allocation. !90 + +### Changed + + - Revert "Set macOS deployment target to macOS 10.4". !93 + +### Fixed + + - mtod()-related buffer overflows (CVE-2021-3592 #44, CVE-2021-3593 #45, + CVE-2021-3594 #47, CVE-2021-3595 #46). + - poll_fd: add missing fd registration for UDP and ICMP + - ncsi: make ncsi_calculate_checksum work with unaligned data. !89 + - Various typos and doc fixes. !88 + +## [4.5.0] - 2021-05-18 + +### Added + + - IPv6 forwarding. !62 !75 !77 + - slirp_neighbor_info() to dump the ARP/NDP tables. !71 + +### Changed + + - Lazy guest address resolution for IPv6. !81 + - Improve signal handling when spawning a child. !61 + - Set macOS deployment target to macOS 10.4. !72 + - slirp_add_hostfwd: Ensure all error paths set errno. !80 + - More API documentation. + +### Fixed + + - Assertion failure on unspecified IPv6 address. !86 + - Disable polling for PRI on MacOS, fixing some closing streams issues. !73 + - Various memory leak fixes on fastq/batchq. !68 + - Memory leak on IPv6 fast-send. !67 + - Slow socket response on Windows. !64 + - Misc build and code cleanups. !60 !63 !76 !79 !84 + +## [4.4.0] - 2020-12-02 + +### Added + + - udp, udp6, icmp: handle TTL value. !48 + - Enable forwarding ICMP errors. !49 + - Add DNS resolving for iOS. !54 + +### Changed + + - Improve meson subproject() support. !53 + - Removed Makefile-based build system. !56 + +### Fixed + + - socket: consume empty packets. !55 + - check pkt_len before reading protocol header (CVE-2020-29129). !57 + - ip_stripoptions use memmove (fixes undefined behaviour). !47 + - various Coverity-related changes/fixes. + +## [4.3.1] - 2020-07-08 + +### Changed + + - A silent truncation could occur in `slirp_fmt()`, which will now print a + critical message. See also #22. + +### Fixed + + - CVE-2020-10756 - Drop bogus IPv6 messages that could lead to data leakage. + See !44 and !42. + - Fix win32 builds by using the SLIRP_PACKED definition. + - Various coverity scan errors fixed. !41 + - Fix new GCC warnings. !43 + +## [4.3.0] - 2020-04-22 + +### Added + + - `SLIRP_VERSION_STRING` macro, with the git sha suffix when building from git + - `SlirpConfig.disable_dns`, to disable DNS redirection #16 + +### Changed + + - `slirp_version_string()` now has the git sha suffix when building form git + - Limit DNS redirection to port 53 #16 + +### Fixed + + - Fix build regression with mingw & NetBSD + - Fix use-afte-free in `ip_reass()` (CVE-2020-1983) + +## [4.2.0] - 2020-03-17 + +### Added + + - New API function `slirp_add_unix`: add a forward rule to a Unix socket. + - New API function `slirp_remove_guestfwd`: remove a forward rule previously + added by `slirp_add_exec`, `slirp_add_unix` or `slirp_add_guestfwd` + - New `SlirpConfig.outbound_addr{,6}` fields to bind output socket to a + specific address + +### Changed + + - socket: do not fallback on host loopback if `get_dns_addr()` failed + or the address is in slirp network + +### Fixed + + - ncsi: fix checksum OOB memory access + - `tcp_emu()`: fix OOB accesses + - tftp: restrict relative path access + - state: fix loading of guestfwd state + +## [4.1.0] - 2019-12-02 + +### Added + + - The `slirp_new()` API, simpler and more extensible than `slirp_init()`. + - Allow custom MTU configuration. + - Option to disable host loopback connections. + - CI now runs scan-build too. + +### Changed + + - Disable `tcp_emu()` by default. `tcp_emu()` is known to have caused + several CVEs, and not useful today in most cases. The feature can + be still enabled by setting `SlirpConfig.enable_emu` to true. + - meson build system is now `subproject()` friendly. + - Replace remaining `malloc()`/`free()` with glib (which aborts on OOM) + - Various code cleanups. + +### Deprecated + + - The `slirp_init()` API. + +### Fixed + + - `getpeername()` error after `shutdown(SHUT_WR)`. + - Exec forward: correctly parse command lines that contain spaces. + - Allow 0.0.0.0 destination address. + - Make host receive broadcast packets. + - Various memory related fixes (heap overflow, leaks, NULL + dereference). + - Compilation warnings, dead code. + +## [4.0.0] - 2019-05-24 + +### Added + + - Installable as a shared library. + - meson build system + (& make build system for in-tree QEMU integration) + +### Changed + + - Standalone project, removing any QEMU dependency. + - License clarifications. + +[Unreleased]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.7.0...master +[4.7.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.6.1...v4.7.0 +[4.6.1]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.6.0...v4.6.1 +[4.6.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.5.0...v4.6.0 +[4.5.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.4.0...v4.5.0 +[4.4.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.3.1...v4.4.0 +[4.3.1]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.3.0...v4.3.1 +[4.3.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.2.0...v4.3.0 +[4.2.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.1.0...v4.2.0 +[4.1.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.0.0...v4.1.0 +[4.0.0]: https://gitlab.freedesktop.org/slirp/libslirp/commits/v4.0.0 diff --git a/app/src/main/cpp/libslirp/CMakeLists.txt b/app/src/main/cpp/libslirp/CMakeLists.txt new file mode 100644 index 00000000..83795de1 --- /dev/null +++ b/app/src/main/cpp/libslirp/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.22.1) + +project(libslirp) + +add_library(slirp STATIC + src/arp_table.c + src/bootp.c + src/cksum.c + src/dhcpv6.c + src/dnssearch.c + src/if.c + src/ip6_icmp.c + src/ip6_input.c + src/ip6_output.c + src/ip_icmp.c + src/ip_input.c + src/ip_output.c + src/mbuf.c + src/misc.c + src/ncsi.c + src/ndp_table.c + src/sbuf.c + src/slirp.c + src/socket.c + src/state.c + src/stream.c + src/tcp_input.c + src/tcp_output.c + src/tcp_subr.c + src/tcp_timer.c + src/tftp.c + src/udp.c + src/udp6.c + src/util.c + src/version.c + src/vmstate.c + src/libvdeslirp.c + ) + +find_package(PkgConfig REQUIRED) + +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) + +target_include_directories(slirp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) +target_link_libraries(slirp PUBLIC PkgConfig::GLIB) \ No newline at end of file diff --git a/app/src/main/cpp/libslirp/COPYRIGHT b/app/src/main/cpp/libslirp/COPYRIGHT new file mode 100644 index 00000000..ed49512d --- /dev/null +++ b/app/src/main/cpp/libslirp/COPYRIGHT @@ -0,0 +1,62 @@ +Slirp was written by Danny Gasparovski. +Copyright (c), 1995,1996 All Rights Reserved. + +Slirp is free software; "free" as in you don't have to pay for it, and you +are free to do whatever you want with it. I do not accept any donations, +monetary or otherwise, for Slirp. Instead, I would ask you to pass this +potential donation to your favorite charity. In fact, I encourage +*everyone* who finds Slirp useful to make a small donation to their +favorite charity (for example, GreenPeace). This is not a requirement, but +a suggestion from someone who highly values the service they provide. + +The copyright terms and conditions: + +---BEGIN--- + + Copyright (c) 1995,1996 Danny Gasparovski. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + DANNY GASPAROVSKI OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---END--- + +This basically means you can do anything you want with the software, except +1) call it your own, and 2) claim warranty on it. There is no warranty for +this software. None. Nada. If you lose a million dollars while using +Slirp, that's your loss not mine. So, ***USE AT YOUR OWN RISK!***. + +If these conditions cannot be met due to legal restrictions (E.g. where it +is against the law to give out Software without warranty), you must cease +using the software and delete all copies you have. + +Slirp uses code that is copyrighted by the following people/organizations: + +Juha Pirkola. +Gregory M. Christy. +The Regents of the University of California. +Carnegie Mellon University. +The Australian National University. +RSA Data Security, Inc. + +Please read the top of each source file for the details on the various +copyrights. diff --git a/app/src/main/cpp/libslirp/README.md b/app/src/main/cpp/libslirp/README.md new file mode 100644 index 00000000..9f9c1b14 --- /dev/null +++ b/app/src/main/cpp/libslirp/README.md @@ -0,0 +1,60 @@ +# libslirp + +libslirp is a user-mode networking library used by virtual machines, +containers or various tools. + +## Getting Started + +### Prerequisites + +A C compiler, meson and glib2 development libraries. + +(see also [.gitlab-ci.yml](.gitlab-ci.yml) DEPS variable for the list +of dependencies on Fedora) + +### Building + +You may build and install the shared library with meson: + +``` sh +meson build +ninja -C build install +``` +And configure QEMU with --enable-slirp=system to link against it. + +(QEMU may build with the submodule static library using --enable-slirp=git) + +### Testing + +Unfortunately, there are no automated tests available. + +You may run QEMU ``-net user`` linked with your development version. + +## Contributing + +Feel free to open issues on the [project +issues](https://gitlab.freedesktop.org/slirp/libslirp/issues) page. + +You may clone the [gitlab +project](https://gitlab.freedesktop.org/slirp/libslirp) and create a +merge request. + +Contributing with gitlab allows gitlab workflow, tracking issues, +running CI etc. + +Alternatively, you may send patches to slirp@lists.freedesktop.org +mailing list. + +## Versioning + +We intend to use [libtool's +versioning](https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html) +for the shared libraries and use [SemVer](http://semver.org/) for +project versions. + +For the versions available, see the [tags on this +repository](https://gitlab.freedesktop.org/slirp/libslirp/releases). + +## License + +See the [COPYRIGHT](COPYRIGHT) file for details. diff --git a/app/src/main/cpp/libslirp/build-aux/git-version-gen b/app/src/main/cpp/libslirp/build-aux/git-version-gen new file mode 100755 index 00000000..5617eb8d --- /dev/null +++ b/app/src/main/cpp/libslirp/build-aux/git-version-gen @@ -0,0 +1,158 @@ +#!/bin/sh +# Print a version string. +scriptversion=2010-06-14.19; # UTC + +# Copyright (C) 2007-2010 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1|2) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version" \ + '[TAG-NORMALIZATION-SED-SCRIPT]' + exit 1;; +esac + +tarball_version_file=$1 +tag_sed_script="${2:-s/x/x/}" +nl=' +' + +# Avoid meddling by environment variable of the same name. +v= + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif test -d .git \ + && v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && v=`printf '%s\n' "$v" | sed "$tag_sed_script"` \ + && case $v in + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git update-index --refresh > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d "$nl" + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_arp/arp.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_arp/arp.pcap new file mode 100644 index 0000000000000000000000000000000000000000..f920e213e18cc7e8785805b9c17cc19121c6ecd3 GIT binary patch literal 162 zcmca|c+)~A1{MYcU}0bca+;58rCCj3WY7Y#LHIuy1cfjNnS^n$F)(s4umKf9_*@K3 n{1E*>786M4gsF@S4nQFghNuY&1R=5Hw29O+s3xhM`)Qy)I8QB;rQlgX@SU@tU z0HPmNY$)@Rn}U-~85u2^SP#T9F)+k2GcqySCsN&Ik$CL_v(8{0{(E|1fj_ literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_dhcp/dhcp_capture.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_dhcp/dhcp_capture.pcap new file mode 100644 index 0000000000000000000000000000000000000000..99ae0a83a8ad4654179c8c7aabb4aecb8f9a33a5 GIT binary patch literal 1952 zcmca|c+)~A1{MYcU}0bca-L@Kr8P+LGlT&-Ap9Q;f4_Sfarr=~#Mn)qRE(Ruk9=4R!q|C%Tb6!Rn zCT4!NavmLhsY)$d;s2mO1BNQ2?qv5YND%yoc=;JHl#5e%82p$Rz!)0Jp+Mu9K%va! zz#PKB;2@ak0TKa+vNMAVlMbH%6Iv*P8~{=SGIw}}GLso7l$jV=KxvH`%w_|T%*MbJ z8`uC1(IK0ZXy9;A>Wa}PJrC<%9C>+|8Irol$jP8kegzC=Jb5@2oQK^($rq7_xkq3g LW*yDLB<6Df;F@`G literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_icmp/icmp_capture.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_icmp/icmp_capture.pcap new file mode 100644 index 0000000000000000000000000000000000000000..6a0f0b83518a6741049abeb0957e7d65b4306af1 GIT binary patch literal 264 zcmca|c+)~A1{MYcU||qpWMKIC_ibvBA2&k=Py&QGAPSfm7+Kgj7}PCZ3o$UbGBAWJ z(|2HSV7%GL#lXaW!|K{P4u-9j42%qnApIaMGkv+iIt2uUghfQf#3dx9q-A8~b%7 literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_icmp/ping_10-0-2-2.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_icmp/ping_10-0-2-2.pcap new file mode 100644 index 0000000000000000000000000000000000000000..69b60fbca5e9c0c3bc1f8a81da69abc42be02963 GIT binary patch literal 252 zcmca|c+)~A1{MYcU}0bcazblV6ZQ)+FeCxlARH9R#lXZA6v7~662`&c%D@o9Z12F} zz$hOD6z2!YaWE*^0JSoLbOJRn%mL~K0RcfFVG&U=aS2H&X&G5Lc?Cr!WffI5bq!4| wZ5>@beFH-yV-r&|bCAC2!bmoP?Sj|~v5|?#f#E-6Vh_kRkgW_1S`^s`0Hsk=0Q}oG^Z)<= literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_icmp6/ndp.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_icmp6/ndp.pcap new file mode 100755 index 00000000..74443f1e --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/IN_icmp6/ndp.pcap @@ -0,0 +1 @@ +../IN_ndp/ndp.pcap \ No newline at end of file diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_icmp6/ping_10-0-2-2.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_icmp6/ping_10-0-2-2.pcap new file mode 100644 index 0000000000000000000000000000000000000000..87f63899df8e2d79f201f680f3efd0243af2de19 GIT binary patch literal 292 zcmca|c+)~A1{MYcU}0bca*nlVrOjBy#ZU%hgK$t7kYEZ5VGuG2YrC7ka^H%R b)(ez@nFh19gW<6I1E3^K9Hx$m5n>Vm%~~w1 literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_tcp-d b/app/src/main/cpp/libslirp/fuzzing/IN_tcp-d new file mode 100755 index 00000000..1bca80b4 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/IN_tcp-d @@ -0,0 +1 @@ +IN_tcp \ No newline at end of file diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_tcp-h b/app/src/main/cpp/libslirp/fuzzing/IN_tcp-h new file mode 100755 index 00000000..1bca80b4 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/IN_tcp-h @@ -0,0 +1 @@ +IN_tcp \ No newline at end of file diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_tcp/nc-10.0.2.2-8080.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_tcp/nc-10.0.2.2-8080.pcap new file mode 100644 index 0000000000000000000000000000000000000000..48bd881649fe15ec81367871a24428a6ff174c2e GIT binary patch literal 898 zcmaLV&r1SP5C`z@K6P7G35}p`MTeA#z=9Bw*oq>AAc}4kAq7D~5JFVP4${qYUHbJ8 z6!jN)D1xAa5cC_8$gP7Fbnw*7+sbwo9}Ku}eV?6qGr8>1u7Dfm<%U8Jsg3=qKEsPR z{pJ{-)FDJPtc+ZpXcz)w>1zXqy7fr=;CGj1OJ?}_@QI}zQ7Xn~0O9r=yM?A7OiVZN zzT$G#v0Ui)B1+FPq-pt5?&Nd~HF3%+>n)i}6Nx>A^!#i?D{_mL%#vroi(ZmADsl{^ zZ)A?2jvVu(i7|hv7>EEXqy*dR5hH=4Lg`B1O69Gp(p)NDi~fsGRw`KkmyvZ} z|7r`tBWpW%)F!AlbIjisDVV@2DVZK0Iw%_sTSh7r4JtLsvN%SuIEA=Jsb&Kz}V)TdY3 XCz^B4Wzbhs(WK&>WJ_tJZa)Cu&4JEc literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_tcp/nc-ident.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_tcp/nc-ident.pcap new file mode 100644 index 0000000000000000000000000000000000000000..3d0421b556c5a8c03fe0770428c6250a43d8c604 GIT binary patch literal 332 zcmca|c+)~A1{MYcU}0bca=NB0j#+Yxi@^)X2H~JkE(RthD?>()>svV(Tp1W_@>e-9 zIIwLN0E#n!qIv5_Iy1fXetA9(P&X)V|^MKsgaf!!nsbkk`D1^`|^O!)u+ literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_tcp/tcp_qemucapt.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_tcp/tcp_qemucapt.pcap new file mode 100644 index 0000000000000000000000000000000000000000..83a0eddfa7b4ce450010f1b7196f0957280cf70f GIT binary patch literal 9048 zcmeI1IY6jFGAQAr^xc#B{ZwGaY9)JDXlQ4z6FL<^HbC0Hn?T8~0J zT5Z%$1VITF7828GWoa+itut>uHi=nDDa^uy>?Zp$d|CdT_g>=W>`(#)ehvj}c(OTJ zI1w!e7{Jdo28Z)NQU`~?*))<1eE^+WqZhn68w+^7h`x(Bm3Sh2eL|!IDYWG30gx2+ zLXmRwp5p2oUAM`yoyc1i2*7h122=1QL9Uf60mm%ay#Y#}LrkBHYakxk4YUTbhv{Qp1CbKpSkUj# z^|@vEJcxB<&8iZt-E`J{39)AG%&cQ;R<&U5q_ZA;#F~nlS^eDr`}xL5UFXS<$~D9q zs?Wr7Yn-szKU;hU8(;%$fDNz#Hoykh02}z*0L>yUv0Jy;Np16Dx4yB4-MVSN_rYfm z%hP_x;_cT?Y6I^3ey^&C{obpZ<#2Up3i*q0!2 zMrLMqkZlr^SQz|(d=Q4%1G1A*7-$<%gh7JsKf3AYwllB;?VFm#2(&RTLFoT~{}PC8 zAkzzhrh5U60bwkD5h239+sL1*0J_8UiCX1VH8N zc~Hp$3<65W$e-{tOw9w1k^h8`k+1*E!cfG`0LGYQ7Pk$t?d3dRdl^z@&AvZ!<7(9B zqaiRF0;3@?8UmvsFd71*Aut*OqalDV1i)>3U<;gDZTm{C0D+GWVC!Gbe`jGh1hgK6 zG28YGJ;b){E45&4d-p5&d_!9~P*`08`UoCY6pZdyE&}^V@c)1J+mNxF56C7{GTsF2 YVO1^)5cu8ziv_12NN%Ro?U8AfK7XqL}=yDU7dVoF$VT?qmpmY23$<#+~*=z-ezLTG~qnrahpZQ81E20F-(@;ZJ+Fz-jLhmbAwN(rt5#kHHC8 zmVz*PxyNY2Eec9|VJ*mMk5R$g3YhkoM{>C1@;fL*TYw>or!2S)j*o{};)59gOwvj> literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_tftp/tftp_get_libslirp-txt.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_tftp/tftp_get_libslirp-txt.pcap new file mode 100644 index 0000000000000000000000000000000000000000..18defe4d0f4c633c70cfeda90edd2cb5d12c6130 GIT binary patch literal 185 zcmca|c+)~A1{MYcU}0bca{B+MB#55kWN-qqK{zOsi-CzLD1<@CB#eW>m4U&;ugZbJ zLGX$mP@Eqm*Z37EtRUgUz?hSnRGgDpRG?QPhZFdtm)Vdi&1s(n! z0Ez-ZKnTPCe?S=+A0o=Yv`mn}l|i&Ije)Twza*a_Ke;5egdqcH^c-m>1}7#4FoqZk zu>fc^P%jgY;D2PGcbeQ3$<~S8i^qQN>S5ffGG;-c@V~uSb2y{ ztUQgez}&zHGErTQN@>&zm_~tVeH4u35CEkp33)1}Q6`Xyc?wJnVZiblgfYueMg`)^ bQAUMpg1|J&Jd)!Fm)}95%&17kcw+_tt-NPhZFdtG8R8ja1s(n! z0Ez-ZKnTPCe?S=+A0o=YwA-G+l|i;KkAX2KGpRTyv#3C?q@si&Ke;5e1Z>4!6=sG& zplT3?7!I)rXa&d&X~F;KM#A_oD;ZpYmPj}LVPIuobj!?1RmjUPQAo=#%}W7W(51%A W-~==Ygt7R6he$v0G{yo0i4g$7IzaXS literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_udp-h b/app/src/main/cpp/libslirp/fuzzing/IN_udp-h new file mode 100755 index 00000000..d1958903 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/IN_udp-h @@ -0,0 +1 @@ +IN_udp \ No newline at end of file diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_udp/DNS_freedesktop_1-1-1-1.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_udp/DNS_freedesktop_1-1-1-1.pcap new file mode 100644 index 0000000000000000000000000000000000000000..9950156aee9cb0ab6d70cfb516e36ca39a7720f7 GIT binary patch literal 256 zcmca|c+)~A1{MYcU}0bcay0*_B*;axG9&@nARH9R#lXZA6v7~662`&c%D@n^@CyTj zgW#ztKyiLXAea!uV9MaYmlS%GQ2}fO0|O&>T2X3hN@{U-Nqzxyeo;D52qdK`05lN@ zco?`CIPQr$zwv;euHOp=G83Md7_5c@%H5flWv0;Iu3@ISh( m=&oWg1-ipUGTF(ou>oQk4p$uDfk?AFXJBdmb7=#@RSW=ED?E+> literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_udp6/tftp_capture.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_udp6/tftp_capture.pcap new file mode 100755 index 00000000..9bd68303 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/IN_udp6/tftp_capture.pcap @@ -0,0 +1 @@ +../IN_tftp6/tftp_capture.pcap \ No newline at end of file diff --git a/app/src/main/cpp/libslirp/fuzzing/IN_udp6/tftp_get_libslirp-txt.pcap b/app/src/main/cpp/libslirp/fuzzing/IN_udp6/tftp_get_libslirp-txt.pcap new file mode 100755 index 00000000..39fc722b --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/IN_udp6/tftp_get_libslirp-txt.pcap @@ -0,0 +1 @@ +../IN_tftp6/tftp_get_libslirp-txt.pcap \ No newline at end of file diff --git a/app/src/main/cpp/libslirp/fuzzing/README.md b/app/src/main/cpp/libslirp/fuzzing/README.md new file mode 100644 index 00000000..a028a98b --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/README.md @@ -0,0 +1,59 @@ +# Fuzzing libslirp state and instructions + +## Current state +We chose to use libFuzzer because of its custom mutator feature, which allows to keep coherent informations inside the packets being sent to libslirp. This ease the process of fuzzing as packets are less likely to be rejected early during processing them. + +In the current state, the `meson.build` file is not compatible with the original one used by libSlirp main repository but it should be easy to merge them in a clean way. Also **in the current state, it seems that there is a memory leak inside the fuzzing code**, which make it run out of memory. The current goal is to find and get rid of this leak to allow fuzzing for longer without the process being interrupted because of it. + +Six harness are currently available, more are to be added later to focus on other parts of the code : + +- **fuzz-ip-header** : the mutator focuses on the ip header field informations, +- **fuzz-udp** : the mutator only work on udp packets, mutating the udp header and content, or only one or the other (-h,-d), +- **fuzz-tcp** : the mutator targets tcp packets, header+data or only one or the other, or only one or the other (-h,-d), +- **fuzz-icmp** : the mutator focuses on icmp packets, + +These harness should be good starting examples on how to fuzz libslirp using libFuzzer. + +## Running the fuzzer + +Building the fuzzers/harness requires the use of clang as libFuzzer is part of LLVM. +You can build it running : + +`CC=clang meson build && ninja -C build` + +It will build the fuzzer in the ./build/fuzzing/ directory. + +A script named `fuzzing/coverage.py` is available to generate coverage informations. **It makes a lot of assumptions on the directory structure** and should be read before use. + +To run the fuzzer, simply run some of: + +- `build/fuzzing/fuzz-ip-header fuzzing/IN_ip-header` +- `build/fuzzing/fuzz-udp fuzzing/IN_udp` +- `build/fuzzing/fuzz-udp-h fuzzing/IN_udp-h` +- `build/fuzzing/fuzz-tftp fuzzing/IN_tftp` +- `build/fuzzing/fuzz-dhcp fuzzing/IN_dhcp` +- `build/fuzzing/fuzz-icmp fuzzing/IN_icmp` +- `build/fuzzing/fuzz-tcp fuzzing/IN_tcp` + +Your current directory should be a separate directory as crashes to it. New inputs found by the fuzzer will go directly in the `IN` folder. + +# Adding new files to the corpus + +In its current state, the fuzzing code is taking pcap files as input, we produced some using `tcpdump` on linux inside qemu with default settings. +Those files should be captured using the `EN10MB (Ethernet)` data link type, this can be set with the flag `-y` but it seems this can't be done while listening on all interfaces (`-i any`). +New files should give new coverage, to ensure a new file is usefull the `coverage.py` script (see next section) can be used to compare the coverage with and without that new file. + +# Coverage + +The `coverage.py` script allows to see coverage informations about the corpus. It makes a lot of assumptions on the directory structure so it should be read and probably modified before running it. +It must be called with the protocol to cover: `python coverage.py udp report`. +To generate coverage informations, the following flags are passed to the fuzzer and libslirp : + +- g +- fsanitize-coverage=edge,indirect-calls,trace-cmp +- fprofile-instr-generate +- fcoverage-mapping + +The last 2 arguments should also be passed to the linker. + +Then the `llvm-profdata` and `llvm-cov` tools can be used to generate a report and a fancy set of HTML files with line-coverage informations. diff --git a/app/src/main/cpp/libslirp/fuzzing/coverage.py b/app/src/main/cpp/libslirp/fuzzing/coverage.py new file mode 100755 index 00000000..861f2acf --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/coverage.py @@ -0,0 +1,37 @@ +from os import chdir,listdir,environ +from os.path import isfile,join,isdir +from subprocess import DEVNULL, run +import sys + +ignored_files = "-ignore-filename-regex=glib -ignore-filename-regex=fuzz -ignore-filename-regex=helper -ignore-filename-regex=h$" + +if __name__ == "__main__": + chdir("build/fuzzing/out") + available_targets = [exe for exe in listdir("../") if isfile(join("..", exe))] + available_corpus_path = [exe for exe in listdir("../../../fuzzing/") if isdir(join("../../../fuzzing/", exe))] + available_result_types = ["export", "show", "report"] + if len(sys.argv) != 4 or sys.argv[1] not in available_targets or sys.argv[2] not in available_corpus_path or sys.argv[3] not in available_result_types: + print("usage : python coverage.py fuzz_target IN_protol result_type") + print(" - available targets : ") + print(available_targets) + print(" - available_corpus_path : ") + print(available_corpus_path) + print(" - available result types : ") + print(available_result_types) + exit(0) + fuzzing_target = sys.argv[1] + corpus_path = "../../../fuzzing/"+sys.argv[2]+"/" + result_type = sys.argv[3] + if fuzzing_target in available_targets: + environ["LLVM_PROFILE_FILE"] = fuzzing_target + "_%p.profraw" + corpus = listdir(corpus_path) + for f in corpus: + #print(corpus_path+f) + run(["../" + fuzzing_target, corpus_path+f,"-detect_leaks=0"], stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL) + run(["llvm-profdata merge -sparse " + fuzzing_target + "_*.profraw -o " + fuzzing_target + ".profdata"], shell=True) + if result_type == "export" : + run(["llvm-cov show ../" + fuzzing_target + " -format=html -output-dir=../report -instr-profile=" + fuzzing_target + ".profdata " + ignored_files], shell=True) + elif result_type == "show" : + run(["llvm-cov show ../" + fuzzing_target + " -instr-profile=" + fuzzing_target + ".profdata " + ignored_files], shell=True) + else: + run(["llvm-cov report ../" + fuzzing_target + " -instr-profile=" + fuzzing_target + ".profdata " + ignored_files], shell=True) diff --git a/app/src/main/cpp/libslirp/fuzzing/fuzz-input.options b/app/src/main/cpp/libslirp/fuzzing/fuzz-input.options new file mode 100644 index 00000000..79488880 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/fuzz-input.options @@ -0,0 +1,2 @@ +[libfuzzer] +max_len = 1024 diff --git a/app/src/main/cpp/libslirp/fuzzing/fuzz-main.c b/app/src/main/cpp/libslirp/fuzzing/fuzz-main.c new file mode 100644 index 00000000..1de031c2 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/fuzz-main.c @@ -0,0 +1,35 @@ +#include +#include + +#define MIN_NUMBER_OF_RUNS 1 +#define EXIT_TEST_SKIP 77 + +extern int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size); + +int main(int argc, char **argv) +{ + int i, j; + + for (i = 1; i < argc; i++) { + GError *err = NULL; + char *name = argv[i]; + char *buf; + size_t size; + + if (!g_file_get_contents(name, &buf, &size, &err)) { + g_warning("Failed to read '%s': %s", name, err->message); + g_clear_error(&err); + return EXIT_FAILURE; + } + + g_print("%s...\n", name); + for (j = 0; j < MIN_NUMBER_OF_RUNS; j++) { + if (LLVMFuzzerTestOneInput((void *)buf, size) == EXIT_TEST_SKIP) { + return EXIT_TEST_SKIP; + } + } + g_free(buf); + } + + return EXIT_SUCCESS; +} diff --git a/app/src/main/cpp/libslirp/fuzzing/helper.c b/app/src/main/cpp/libslirp/fuzzing/helper.c new file mode 100644 index 00000000..399cfb5c --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/helper.c @@ -0,0 +1,271 @@ +#include "helper.h" +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "slirp_base_fuzz.h" + +#define MIN_NUMBER_OF_RUNS 1 +#define EXIT_TEST_SKIP 77 + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +struct in6_addr ip6_host; +struct in6_addr ip6_dns; + +/// Function to compute the checksum of the ip header, should be compatible with +/// TCP and UDP checksum calculation too. +uint16_t compute_checksum(uint8_t *Data, size_t Size) +{ + uint32_t sum = 0; + uint16_t *Data_as_u16 = (uint16_t *)Data; + + for (size_t i = 0; i < Size / 2; i++) { + uint16_t val = ntohs(*(Data_as_u16 + i)); + sum += val; + } + if (Size % 2 == 1) + sum += Data[Size - 1] << 8; + + uint16_t carry = sum >> 16; + uint32_t sum_val = carry + (sum & 0xFFFF); + uint16_t result = (sum_val >> 16) + (sum_val & 0xFFFF); + return ~result; +} + +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + /* FIXME: fail on some addr? */ + return 0; +} + +int listen(int sockfd, int backlog) +{ + return 0; +} + +int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + /* FIXME: fail on some addr? */ + return 0; +} + +ssize_t send(int sockfd, const void *buf, size_t len, int flags) +{ + /* FIXME: partial send? */ + return len; +} + +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + /* FIXME: partial send? */ + return len; +} + +ssize_t recv(int sockfd, void *buf, size_t len, int flags) +{ + memset(buf, 0, len); + return len / 2; +} + +ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) +{ + memset(buf, 0, len); + memset(src_addr, 0, *addrlen); + return len / 2; +} + +int setsockopt(int sockfd, int level, int optname, const void *optval, + socklen_t optlen) +{ + return 0; +} + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +static void empty_logging_func(const gchar *log_domain, + GLogLevelFlags log_level, const gchar *message, + gpointer user_data) +{ +} +#endif + +/* Disables logging for oss-fuzz. Must be used with each target. */ +static void fuzz_set_logging_func(void) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + g_log_set_default_handler(empty_logging_func, NULL); +#endif +} + +static ssize_t send_packet(const void *pkt, size_t pkt_len, void *opaque) +{ + return pkt_len; +} + +static int64_t clock_get_ns(void *opaque) +{ + return 0; +} + +static void *timer_new(SlirpTimerCb cb, void *cb_opaque, void *opaque) +{ + return NULL; +} + +static void timer_mod(void *timer, int64_t expire_timer, void *opaque) +{ +} + +static void timer_free(void *timer, void *opaque) +{ +} + +static void guest_error(const char *msg, void *opaque) +{ +} + +static void register_poll_fd(int fd, void *opaque) +{ +} + +static void unregister_poll_fd(int fd, void *opaque) +{ +} + +static void notify(void *opaque) +{ +} + +static const SlirpCb slirp_cb = { + .send_packet = send_packet, + .guest_error = guest_error, + .clock_get_ns = clock_get_ns, + .timer_new = timer_new, + .timer_mod = timer_mod, + .timer_free = timer_free, + .register_poll_fd = register_poll_fd, + .unregister_poll_fd = unregister_poll_fd, + .notify = notify, +}; + +#define MAX_EVID 1024 +static int fake_events[MAX_EVID]; + +static int add_poll_cb(int fd, int events, void *opaque) +{ + g_assert(fd < G_N_ELEMENTS(fake_events)); + fake_events[fd] = events; + return fd; +} + +static int get_revents_cb(int idx, void *opaque) +{ + return fake_events[idx] & ~(SLIRP_POLL_ERR | SLIRP_POLL_HUP); +} + +// Fuzzing strategy is the following : +// LLVMFuzzerTestOneInput : +// - build a slirp instance, +// - extract the packets from the pcap one by one, +// - send the data to `slirp_input` +// - call `slirp_pollfds_fill` and `slirp_pollfds_poll` to advance slirp +// - cleanup slirp when the whole pcap has been unwrapped. +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + Slirp *slirp = NULL; + struct in_addr net = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */ + struct in_addr mask = { .s_addr = htonl(0xffffff00) }; /* 255.255.255.0 */ + struct in_addr host = { .s_addr = htonl(0x0a000202) }; /* 10.0.2.2 */ + struct in_addr fwd = { .s_addr = htonl(0x0a000205) }; /* 10.0.2.5 */ + struct in_addr dhcp = { .s_addr = htonl(0x0a00020f) }; /* 10.0.2.15 */ + struct in_addr dns = { .s_addr = htonl(0x0a000203) }; /* 10.0.2.3 */ + struct in6_addr ip6_prefix; + int ret, vprefix6_len = 64; + const char *vhostname = NULL; + const char *tftp_server_name = NULL; + const char *tftp_export = "fuzzing/tftp"; + const char *bootfile = NULL; + const char **dnssearch = NULL; + const char *vdomainname = NULL; + const pcap_hdr_t *hdr = (const void *)data; + const pcaprec_hdr_t *rec = NULL; + uint32_t timeout = 0; + + if (size < sizeof(pcap_hdr_t)) { + return 0; + } + data += sizeof(*hdr); + size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + setenv("SLIRP_FUZZING", "1", 0); + + fuzz_set_logging_func(); + + ret = inet_pton(AF_INET6, "fec0::", &ip6_prefix); + g_assert_cmpint(ret, ==, 1); + + ip6_host = ip6_prefix; + ip6_host.s6_addr[15] |= 2; + ip6_dns = ip6_prefix; + ip6_dns.s6_addr[15] |= 3; + + slirp = + slirp_init(false, true, net, mask, host, true, ip6_prefix, vprefix6_len, + ip6_host, vhostname, tftp_server_name, tftp_export, bootfile, + dhcp, dns, ip6_dns, dnssearch, vdomainname, &slirp_cb, NULL); + + slirp_add_exec(slirp, "cat", &fwd, 1234); + + + for ( ; size > sizeof(*rec); data += rec->incl_len, size -= rec->incl_len) { + rec = (const void *)data; + data += sizeof(*rec); + size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + g_debug("unsupported rec->incl_len != rec->orig_len"); + break; + } + if (rec->incl_len > size) { + break; + } + + if (rec->incl_len >= 14) { + if (data[12] == 0x08 && data[13] == 0x00) { + /* IPv4 */ + if (rec->incl_len >= 14 + 16) { + uint32_t ipsource = * (uint32_t*) (data + 14 + 12); + + // This an answer, which we will produce, so don't receive + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + } else if (data[12] == 0x86 && data[13] == 0xdd) { + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (data + 14 + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + } + } + + slirp_input(slirp, data, rec->incl_len); + slirp_pollfds_fill(slirp, &timeout, add_poll_cb, NULL); + slirp_pollfds_poll(slirp, 0, get_revents_cb, NULL); + } + + slirp_cleanup(slirp); + + return 0; +} diff --git a/app/src/main/cpp/libslirp/fuzzing/helper.h b/app/src/main/cpp/libslirp/fuzzing/helper.h new file mode 100644 index 00000000..92b5f620 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/helper.h @@ -0,0 +1,24 @@ +#ifndef _HELPER_H +#define _HELPER_H + +#ifdef _WIN32 +/* as defined in sdkddkver.h */ +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 /* Vista */ +#endif +#include +#endif + +#include +#include +#include + +#define PSEUDO_IP_SIZE (4*2 + 4) +#define PSEUDO_IPV6_SIZE (16*2 + 4) + +uint16_t compute_checksum(uint8_t *Data, size_t Size); + +extern struct in6_addr ip6_host; +extern struct in6_addr ip6_dns; + +#endif /* _HELPER_H */ diff --git a/app/src/main/cpp/libslirp/fuzzing/meson.build b/app/src/main/cpp/libslirp/fuzzing/meson.build new file mode 100644 index 00000000..c2017951 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/meson.build @@ -0,0 +1,64 @@ +extra_sources = [] +extra_cargs = [] +extra_ldargs = [] +fuzzing_engine = [] + + +extra_cargs += '-g' +if fuzzer_build + extra_cargs += '-fsanitize=fuzzer,address' + extra_cargs += '-fsanitize-coverage=edge,indirect-calls,trace-cmp' + extra_cargs += '-DCUSTOM_MUTATOR' + extra_cargs += '-fprofile-instr-generate' + extra_cargs += '-fcoverage-mapping' + + extra_ldargs += '-fsanitize=fuzzer,address' + extra_ldargs += '-fprofile-instr-generate' + extra_ldargs += '-fcoverage-mapping' +endif + +deps = [glib_dep, libslirp_dep, platform_deps] + +exes = [ + ['fuzz-arp', ['slirp_fuzz_arp.c', 'helper.c']], + ['fuzz-ip-header', ['slirp_fuzz_ip_header.c', 'helper.c']], + ['fuzz-udp', ['slirp_fuzz_udp.c', 'helper.c']], + ['fuzz-udp-h', ['slirp_fuzz_udp_header.c', 'helper.c']], + ['fuzz-udp-d', ['slirp_fuzz_udp_data.c', 'helper.c']], + ['fuzz-tftp', ['slirp_fuzz_udp_data.c', 'helper.c']], + ['fuzz-dhcp', ['slirp_fuzz_udp_data.c', 'helper.c']], + ['fuzz-tcp', ['slirp_fuzz_tcp.c', 'helper.c']], + ['fuzz-tcp-h', ['slirp_fuzz_tcp_header.c', 'helper.c']], + ['fuzz-tcp-d', ['slirp_fuzz_tcp_data.c', 'helper.c']], + ['fuzz-icmp', ['slirp_fuzz_icmp.c', 'helper.c']], + + ['fuzz-ndp', ['slirp_fuzz_icmp6.c', 'helper.c']], + ['fuzz-ip6-header', ['slirp_fuzz_ip6_header.c', 'helper.c']], + ['fuzz-udp6', ['slirp_fuzz_udp6.c', 'helper.c']], + ['fuzz-udp6-h', ['slirp_fuzz_udp6_header.c', 'helper.c']], + ['fuzz-udp6-d', ['slirp_fuzz_udp6_data.c', 'helper.c']], + ['fuzz-tftp6', ['slirp_fuzz_udp6_data.c', 'helper.c']], + ['fuzz-tcp6', ['slirp_fuzz_tcp6.c', 'helper.c']], + ['fuzz-tcp6-h', ['slirp_fuzz_tcp6_header.c', 'helper.c']], + ['fuzz-tcp6-d', ['slirp_fuzz_tcp6_data.c', 'helper.c']], + ['fuzz-icmp6', ['slirp_fuzz_icmp6.c', 'helper.c']], + ] + +if fuzzer_build + foreach exe : exes + executable( + exe[0], exe[1], + dependencies : deps, + c_args: extra_cargs, + link_args: extra_ldargs, + ) + endforeach +endif + +if fuzz_reproduce + executable(['reproducer', ['reproducer.c', 'helper.c']], + dependencies: deps, + c_args: extra_cargs, + link_args: extra_ldargs, + ) +endif diff --git a/app/src/main/cpp/libslirp/fuzzing/oss-fuzz.sh b/app/src/main/cpp/libslirp/fuzzing/oss-fuzz.sh new file mode 100755 index 00000000..0561bdba --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/oss-fuzz.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -ex + +export CC=${CC:-clang} +export CXX=${CXX:-clang++} +export WORK=${WORK:-$(pwd)} +export OUT=${OUT:-$(pwd)/out} + +build=$WORK/build +rm -rf $build +mkdir -p $build +mkdir -p $OUT + +fuzzflag="oss-fuzz=true" +if [ -z "$FUZZING_ENGINE" ]; then + fuzzflag="llvm-fuzz=true" +fi + +meson $build \ + -D$fuzzflag \ + -Db_lundef=false \ + -Ddefault_library=static \ + -Dstatic=true \ + -Dbuildtype=debugoptimized + +ninja -C $build + +zip -jqr $OUT/fuzz-arp_seed_corpus.zip "$(dirname "$0")/IN_arp" +zip -jqr $OUT/fuzz-ip-header_seed_corpus.zip "$(dirname "$0")/IN_ip-header" +zip -jqr $OUT/fuzz-udp_seed_corpus.zip "$(dirname "$0")/IN_udp" +zip -jqr $OUT/fuzz-udp-h_seed_corpus.zip "$(dirname "$0")/IN_udp-h" +zip -jqr $OUT/fuzz-tftp_seed_corpus.zip "$(dirname "$0")/IN_tftp" +zip -jqr $OUT/fuzz-dhcp_seed_corpus.zip "$(dirname "$0")/IN_dhcp" +zip -jqr $OUT/fuzz-icmp_seed_corpus.zip "$(dirname "$0")/IN_icmp" +zip -jqr $OUT/fuzz-tcp_seed_corpus.zip "$(dirname "$0")/IN_tcp" +zip -jqr $OUT/fuzz-tcp-h_seed_corpus.zip "$(dirname "$0")/IN_tcp-h" + +zip -jqr $OUT/fuzz-ndp_seed_corpus.zip "$(dirname "$0")/IN_ndp" +zip -jqr $OUT/fuzz-ip6-header_seed_corpus.zip "$(dirname "$0")/IN_ip6-header" +zip -jqr $OUT/fuzz-udp6_seed_corpus.zip "$(dirname "$0")/IN_udp6" +zip -jqr $OUT/fuzz-udp6-h_seed_corpus.zip "$(dirname "$0")/IN_udp6-h" +zip -jqr $OUT/fuzz-tftp6_seed_corpus.zip "$(dirname "$0")/IN_tftp6" +zip -jqr $OUT/fuzz-icmp6_seed_corpus.zip "$(dirname "$0")/IN_icmp6" +zip -jqr $OUT/fuzz-tcp6_seed_corpus.zip "$(dirname "$0")/IN_tcp6" + +find $build -type f -executable -name "fuzz-*" -exec mv {} $OUT \; +find $build -type f -name "*.options" -exec mv {} $OUT \; diff --git a/app/src/main/cpp/libslirp/fuzzing/reproducer.c b/app/src/main/cpp/libslirp/fuzzing/reproducer.c new file mode 100644 index 00000000..49704af7 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/reproducer.c @@ -0,0 +1,45 @@ +#ifdef _WIN32 +/* as defined in sdkddkver.h */ +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 /* Vista */ +#endif +#include +#endif + +#include +#include +#include "../src/libslirp.h" +#include "helper.h" + +#define MIN_NUMBER_OF_RUNS 1 +#define EXIT_TEST_SKIP 77 + +extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +int main(int argc, char **argv) +{ + int i, j; + + for (i = 1; i < argc; i++) { + GError *err = NULL; + char *name = argv[i]; + char *buf; + size_t size; + + if (!g_file_get_contents(name, &buf, &size, &err)) { + g_warning("Failed to read '%s': %s", name, err->message); + g_clear_error(&err); + return EXIT_FAILURE; + } + + g_print("%s...\n", name); + for (j = 0; j < MIN_NUMBER_OF_RUNS; j++) { + if (LLVMFuzzerTestOneInput((void *)buf, size) == EXIT_TEST_SKIP) { + return EXIT_TEST_SKIP; + } + } + g_free(buf); + } + + return EXIT_SUCCESS; +} diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_base_fuzz.h b/app/src/main/cpp/libslirp/fuzzing/slirp_base_fuzz.h new file mode 100644 index 00000000..05b32d66 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_base_fuzz.h @@ -0,0 +1,23 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" + +/* Structure for the fuzzers */ +typedef struct pcap_hdr_s { + guint32 magic_number; /* magic number */ + guint16 version_major; /* major version number */ + guint16 version_minor; /* minor version number */ + gint32 thiszone; /* GMT to local correction */ + guint32 sigfigs; /* accuracy of timestamps */ + guint32 snaplen; /* max length of captured packets, in octets */ + guint32 network; /* data link type */ +} pcap_hdr_t; + +typedef struct pcaprec_hdr_s { + guint32 ts_sec; /* timestamp seconds */ + guint32 ts_usec; /* timestamp microseconds */ + guint32 incl_len; /* number of octets of packet saved in file */ + guint32 orig_len; /* actual length of packet */ +} pcaprec_hdr_t; \ No newline at end of file diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_arp.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_arp.c new file mode 100644 index 00000000..c403e164 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_arp.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *arp_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + arp_data = Data_ptr + 14; + + uint8_t Data_to_mutate[MaxSize]; + uint16_t arp_size = rec->incl_len - 14; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the ip header, maybe the IPs or + // total length should be excluded ? + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, arp_data, arp_size); + + // Call to libfuzzer's mutation function. + // For now we dont want to change the header size as it would require to + // resize the `Data` array to include the new bytes inside the whole + // packet. + // This should be easy as LibFuzzer probably does it by itself or + // reserved enough space in Data beforehand, needs some research to + // confirm. + // FIXME: allow up to grow header size to 60 bytes, + // requires to update the `header length` before calculating + // checksum + LLVMFuzzerMutate(Data_to_mutate, arp_size, arp_size); + + // Copy the mutated data back to the `Data` array + memcpy(arp_data, Data_to_mutate, arp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_icmp.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_icmp.c new file mode 100644 index 00000000..8a422c4b --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_icmp.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not ICMP from the mutation strategy + if (ip_data[9] != IPPROTO_ICMP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_icmp = ip_data + ip_hl_in_bytes; + uint16_t total_length = + ntohs(*((uint16_t *)ip_data + 1)); // network order to host order + uint16_t icmp_size = + (total_length - ip_hl_in_bytes); /* total length -> is stored at the + offset 2 in the header */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (icmp_size > MaxSize || icmp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in icmp + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_icmp, icmp_size); + + // Call to libfuzzer's mutation function. + // For now we dont want to change the header size as it would require to + // resize the `Data` array to include the new bytes inside the whole + // packet. + // This should be easy as LibFuzzer probably does it by itself or + // reserved enough space in Data beforehand, needs some research to + // confirm. + // FIXME: allow up to grow header size to 60 bytes, + // requires to update the `header length` before calculating + // checksum + LLVMFuzzerMutate(Data_to_mutate, icmp_size, icmp_size); + + // Set the `checksum` field to 0 and calculate the new checksum + *(uint16_t *)(Data_to_mutate + 2) = 0; + uint16_t new_checksum = + compute_checksum(Data_to_mutate, icmp_size); + *(uint16_t *)(Data_to_mutate + 2) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_icmp, Data_to_mutate, icmp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_icmp6.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_icmp6.c new file mode 100644 index 00000000..bbe041c9 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_icmp6.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not ICMP from the mutation strategy + if (ip_data[6] != IPPROTO_ICMPV6) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IPV6_SIZE]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_icmp = ip_data + ip_hl_in_bytes; + uint16_t icmp_size = ntohs(*(uint16_t *)(ip_data + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (icmp_size > MaxSize || icmp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in icmp + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IPV6_SIZE); + memcpy(Data_to_mutate, start_of_icmp, icmp_size); + + // Call to libfuzzer's mutation function. + // For now we dont want to change the header size as it would require to + // resize the `Data` array to include the new bytes inside the whole + // packet. + // This should be easy as LibFuzzer probably does it by itself or + // reserved enough space in Data beforehand, needs some research to + // confirm. + // FIXME: allow up to grow header size to 60 bytes, + // requires to update the `header length` before calculating + // checksum + LLVMFuzzerMutate(Data_to_mutate, icmp_size, icmp_size); + + // Set the `checksum` field to 0 and calculate the new checksum + *(uint16_t *)(Data_to_mutate + 2) = 0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + icmp_size, ip_data + 8, 16*2); + + *(Data_to_mutate + icmp_size + 16*2 + 1) = IPPROTO_ICMPV6; + + *(Data_to_mutate + icmp_size + 16*2 + 2) = (uint8_t)(icmp_size / 256); + *(Data_to_mutate + icmp_size + 16*2 + 3) = (uint8_t)(icmp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, icmp_size + PSEUDO_IPV6_SIZE); + *(uint16_t *)(Data_to_mutate + 2) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_icmp, Data_to_mutate, icmp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_ip6_header.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_ip6_header.c new file mode 100644 index 00000000..4714f92d --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_ip6_header.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the ip header, maybe the IPs or + // total length should be excluded ? + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, ip_data, ip_hl_in_bytes); + + // Call to libfuzzer's mutation function. + // For now we dont want to change the header size as it would require to + // resize the `Data` array to include the new bytes inside the whole + // packet. + // This should be easy as LibFuzzer probably does it by itself or + // reserved enough space in Data beforehand, needs some research to + // confirm. + // FIXME: allow up to grow header size to 60 bytes, + // requires to update the `header length` before calculating + // checksum + LLVMFuzzerMutate(Data_to_mutate, ip_hl_in_bytes, ip_hl_in_bytes); + + // Copy the mutated data back to the `Data` array + memcpy(ip_data, Data_to_mutate, ip_hl_in_bytes); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_ip_header.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_ip_header.c new file mode 100644 index 00000000..fe07540e --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_ip_header.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > MaxSize || ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the ip header, maybe the IPs or + // total length should be excluded ? + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, ip_data, ip_hl_in_bytes); + + // Call to libfuzzer's mutation function. + // For now we dont want to change the header size as it would require to + // resize the `Data` array to include the new bytes inside the whole + // packet. + // This should be easy as LibFuzzer probably does it by itself or + // reserved enough space in Data beforehand, needs some research to + // confirm. + // FIXME: allow up to grow header size to 60 bytes, + // requires to update the `header length` before calculating + // checksum + LLVMFuzzerMutate(Data_to_mutate, ip_hl_in_bytes, ip_hl_in_bytes); + + // Set the `checksum` field to 0 and calculate the new checksum + *(uint16_t *)(Data_to_mutate + 10) = 0; + uint16_t new_checksum = + compute_checksum(Data_to_mutate, ip_hl_in_bytes); + *(uint16_t *)(Data_to_mutate + 10) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(ip_data, Data_to_mutate, ip_hl_in_bytes); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp.c new file mode 100644 index 00000000..461d4304 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not TCP from the mutation strategy + if (ip_data[9] != IPPROTO_TCP) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IP_SIZE]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_tcp = ip_data + ip_hl_in_bytes; + uint16_t total_length = ntohs(*((uint16_t *)ip_data + 1)); + uint16_t tcp_size = (total_length - (uint16_t)ip_hl_in_bytes); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use tcp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (tcp_size > MaxSize || tcp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the tcp packet + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IP_SIZE); + memcpy(Data_to_mutate, start_of_tcp, tcp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole TCP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the TCP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, tcp_size, tcp_size); + + // Set the `checksum` field to 0 to calculate the new checksum + + *(uint16_t *)(Data_to_mutate + 16) = (uint16_t)0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + tcp_size, ip_data + 12, 4*2); + + *(Data_to_mutate + tcp_size + 9) = IPPROTO_TCP; + + *(Data_to_mutate + tcp_size + 10) = (uint8_t)(tcp_size / 256); + *(Data_to_mutate + tcp_size + 11) = (uint8_t)(tcp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, tcp_size + PSEUDO_IP_SIZE); + *(uint16_t *)(Data_to_mutate + 16) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_tcp, Data_to_mutate, tcp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp6.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp6.c new file mode 100644 index 00000000..64ad9032 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp6.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not TCP from the mutation strategy + if (ip_data[6] != IPPROTO_TCP) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IPV6_SIZE]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_tcp = ip_data + ip_hl_in_bytes; + uint16_t tcp_size = ntohs(*(uint16_t *)(ip_data + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use tcp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (tcp_size > MaxSize || tcp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the tcp packet + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IPV6_SIZE); + memcpy(Data_to_mutate, start_of_tcp, tcp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole TCP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the TCP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, tcp_size, tcp_size); + + // Set the `checksum` field to 0 to calculate the new checksum + + *(uint16_t *)(Data_to_mutate + 16) = (uint16_t)0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + tcp_size, ip_data + 8, 16*2); + + *(Data_to_mutate + tcp_size + 16*2 + 1) = IPPROTO_TCP; + + *(Data_to_mutate + tcp_size + 16*2 + 2) = (uint8_t)(tcp_size / 256); + *(Data_to_mutate + tcp_size + 16*2 + 3) = (uint8_t)(tcp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, tcp_size + PSEUDO_IPV6_SIZE); + *(uint16_t *)(Data_to_mutate + 16) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_tcp, Data_to_mutate, tcp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp6_data.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp6_data.c new file mode 100644 index 00000000..87982b0c --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp6_data.c @@ -0,0 +1,137 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not TCP from the mutation strategy + if (ip_data[6] != IPPROTO_TCP) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IPV6_SIZE]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_tcp = ip_data + ip_hl_in_bytes; + uint8_t tcp_header_size = 4 * (*(start_of_tcp + 12) >> 4); + uint8_t *start_of_data = ip_data + ip_hl_in_bytes + tcp_header_size; + uint16_t tcp_size = ntohs(*(uint16_t *)(ip_data + 4)); + uint16_t tcp_data_size = (tcp_size - (uint16_t)tcp_header_size); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use tcp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (tcp_data_size > MaxSize || tcp_data_size > rec->incl_len - 14 - ip_hl_in_bytes - tcp_header_size) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the tcp packet + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IPV6_SIZE); + memcpy(Data_to_mutate, start_of_tcp, tcp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole TCP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the TCP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate + tcp_header_size, tcp_data_size, tcp_data_size); + + // Set the `checksum` field to 0 to calculate the new checksum + + *(uint16_t *)(Data_to_mutate + 16) = (uint16_t)0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + tcp_size, ip_data + 8, 16*2); + + *(Data_to_mutate + tcp_size + 16*2 + 1) = IPPROTO_TCP; + + *(Data_to_mutate + tcp_size + 16*2 + 2) = (uint8_t)(tcp_size / 256); + *(Data_to_mutate + tcp_size + 16*2 + 3) = (uint8_t)(tcp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, tcp_size + PSEUDO_IPV6_SIZE); + *(uint16_t *)(Data_to_mutate + 16) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_tcp, Data_to_mutate, tcp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp6_header.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp6_header.c new file mode 100644 index 00000000..e6c8b774 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp6_header.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not TCP from the mutation strategy + if (ip_data[6] != IPPROTO_TCP) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IPV6_SIZE]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_tcp = ip_data + ip_hl_in_bytes; + uint8_t tcp_header_size = (*(start_of_tcp + 12) >> 4) * 4; + uint16_t tcp_size = ntohs(*(uint16_t *)(ip_data + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use tcp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (tcp_size > MaxSize || tcp_size > rec->incl_len - 14 - ip_hl_in_bytes || + tcp_header_size > MaxSize || tcp_header_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the tcp packet + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IPV6_SIZE); + memcpy(Data_to_mutate, start_of_tcp, tcp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole TCP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the TCP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, tcp_header_size, tcp_header_size); + + // Set the `checksum` field to 0 to calculate the new checksum + + *(uint16_t *)(Data_to_mutate + 16) = (uint16_t)0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + tcp_size, ip_data + 8, 16*2); + + *(Data_to_mutate + tcp_size + 16*2 + 1) = IPPROTO_TCP; + + *(Data_to_mutate + tcp_size + 16*2 + 2) = (uint8_t)(tcp_size / 256); + *(Data_to_mutate + tcp_size + 16*2 + 3) = (uint8_t)(tcp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, tcp_size + PSEUDO_IPV6_SIZE); + *(uint16_t *)(Data_to_mutate + 16) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_tcp, Data_to_mutate, tcp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp_data.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp_data.c new file mode 100644 index 00000000..05971c09 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp_data.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not TCP from the mutation strategy + if (ip_data[9] != IPPROTO_TCP) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IP_SIZE]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_tcp = ip_data + ip_hl_in_bytes; + uint8_t tcp_header_size = 4 * (*(start_of_tcp + 12) >> 4); + uint8_t *start_of_data = ip_data + ip_hl_in_bytes + tcp_header_size; + uint16_t total_length = ntohs(*((uint16_t *)ip_data + 1)); + uint16_t tcp_size = (total_length - (uint16_t)ip_hl_in_bytes); + uint16_t tcp_data_size = (tcp_size - (uint16_t)tcp_header_size); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use tcp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (tcp_data_size > MaxSize || tcp_data_size > rec->incl_len - 14 - ip_hl_in_bytes - tcp_header_size) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the tcp packet + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IP_SIZE); + memcpy(Data_to_mutate, start_of_tcp, tcp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole TCP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the TCP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate + tcp_header_size, tcp_data_size, tcp_data_size); + + // Set the `checksum` field to 0 to calculate the new checksum + + *(uint16_t *)(Data_to_mutate + 16) = (uint16_t)0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + tcp_size, ip_data + 12, 4*2); + + *(Data_to_mutate + tcp_size + 9) = IPPROTO_TCP; + + *(Data_to_mutate + tcp_size + 10) = (uint8_t)(tcp_size / 256); + *(Data_to_mutate + tcp_size + 11) = (uint8_t)(tcp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, tcp_size + PSEUDO_IP_SIZE); + *(uint16_t *)(Data_to_mutate + 16) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_tcp, Data_to_mutate, tcp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp_header.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp_header.c new file mode 100644 index 00000000..96904ab2 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_tcp_header.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not TCP from the mutation strategy + if (ip_data[9] != IPPROTO_TCP) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IP_SIZE]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_tcp = ip_data + ip_hl_in_bytes; + uint8_t tcp_header_size = (*(start_of_tcp + 12) >> 4) * 4; + uint16_t total_length = ntohs(*((uint16_t *)ip_data + 1)); + uint16_t tcp_size = (total_length - (uint16_t)ip_hl_in_bytes); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use tcp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (tcp_size > MaxSize || tcp_size > rec->incl_len - 14 - ip_hl_in_bytes || + tcp_header_size > MaxSize || tcp_header_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the tcp packet + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IP_SIZE); + memcpy(Data_to_mutate, start_of_tcp, tcp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole TCP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the TCP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, tcp_header_size, tcp_header_size); + + // Set the `checksum` field to 0 to calculate the new checksum + + *(uint16_t *)(Data_to_mutate + 16) = (uint16_t)0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + tcp_size, ip_data + 12, 4*2); + + *(Data_to_mutate + tcp_size + 9) = IPPROTO_TCP; + + *(Data_to_mutate + tcp_size + 10) = (uint8_t)(tcp_size / 256); + *(Data_to_mutate + tcp_size + 11) = (uint8_t)(tcp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, tcp_size + PSEUDO_IP_SIZE); + *(uint16_t *)(Data_to_mutate + 16) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_tcp, Data_to_mutate, tcp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp.c new file mode 100644 index 00000000..272258e4 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not UDP from the mutation strategy + if (ip_data[9] != IPPROTO_UDP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_udp = ip_data + ip_hl_in_bytes; + uint16_t udp_size = ntohs(*(uint16_t *)(start_of_udp + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (udp_size > MaxSize || udp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the udp packet + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_udp, udp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole UDP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the UDP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, udp_size, udp_size); + + // Drop checksum + *(uint16_t *)(Data_to_mutate + 6) = 0; + + // Copy the mutated data back to the `Data` array + memcpy(start_of_udp, Data_to_mutate, udp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp6.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp6.c new file mode 100644 index 00000000..4b00de75 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp6.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not UDP from the mutation strategy + if (ip_data[6] != IPPROTO_UDP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_udp = ip_data + ip_hl_in_bytes; + uint16_t udp_size = ntohs(*(uint16_t *)(ip_data + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (udp_size > MaxSize || udp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the udp packet + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_udp, udp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole UDP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the UDP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, udp_size, udp_size); + + // Drop checksum + // Stricto sensu, UDPv6 makes checksums mandatory, but libslirp doesn't + // check that actually + *(uint16_t *)(Data_to_mutate + 6) = 0; + + // Copy the mutated data back to the `Data` array + memcpy(start_of_udp, Data_to_mutate, udp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp6_data.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp6_data.c new file mode 100644 index 00000000..83068557 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp6_data.c @@ -0,0 +1,122 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not UDP from the mutation strategy + if (ip_data[6] != IPPROTO_UDP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_udp = ip_data + ip_hl_in_bytes; + uint8_t udp_header_size = 8; + uint16_t udp_size = ntohs(*(uint16_t *)(ip_data + 4)); + uint16_t udp_data_size = (udp_size - (uint16_t)udp_header_size); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (udp_data_size > MaxSize || udp_data_size > rec->incl_len - 14 - ip_hl_in_bytes - udp_header_size) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the udp packet + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_udp, udp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole UDP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the UDP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate + udp_header_size, udp_data_size, udp_data_size); + + // Drop checksum + // Stricto sensu, UDPv6 makes checksums mandatory, but libslirp doesn't + // check that actually + *(uint16_t *)(Data_to_mutate + 6) = 0; + + // Copy the mutated data back to the `Data` array + memcpy(start_of_udp, Data_to_mutate, udp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp6_header.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp6_header.c new file mode 100644 index 00000000..a2734b46 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp6_header.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not UDP from the mutation strategy + if (ip_data[6] != IPPROTO_UDP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_udp = ip_data + ip_hl_in_bytes; + uint8_t udp_header_size = 8; + uint16_t udp_size = ntohs(*(uint16_t *)(ip_data + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (udp_size > MaxSize || udp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the udp packet + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_udp, udp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole UDP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the UDP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, udp_header_size, udp_header_size); + + // Drop checksum + // Stricto sensu, UDPv6 makes checksums mandatory, but libslirp doesn't + // check that actually + *(uint16_t *)(Data_to_mutate + 6) = 0; + + // Copy the mutated data back to the `Data` array + memcpy(start_of_udp, Data_to_mutate, udp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp_data.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp_data.c new file mode 100644 index 00000000..bc8930c9 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp_data.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not UDP from the mutation strategy + if (ip_data[9] != IPPROTO_UDP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_udp = ip_data + ip_hl_in_bytes; + uint8_t udp_header_size = 8; + uint16_t udp_size = ntohs(*(uint16_t *)(start_of_udp + 4)); + uint16_t udp_data_size = (udp_size - (uint16_t)udp_header_size); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (udp_data_size > MaxSize || udp_data_size > rec->incl_len - 14 - ip_hl_in_bytes - udp_header_size) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the udp packet + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_udp, udp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole UDP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the UDP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate + udp_header_size, udp_data_size, udp_data_size); + + // Drop checksum + *(uint16_t *)(Data_to_mutate + 6) = 0; + + // Copy the mutated data back to the `Data` array + memcpy(start_of_udp, Data_to_mutate, udp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp_header.c b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp_header.c new file mode 100644 index 00000000..b2dc2729 --- /dev/null +++ b/app/src/main/cpp/libslirp/fuzzing/slirp_fuzz_udp_header.c @@ -0,0 +1,122 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not UDP from the mutation strategy + if (ip_data[9] != IPPROTO_UDP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_udp = ip_data + ip_hl_in_bytes; + uint8_t udp_header_size = 8; + uint16_t udp_size = ntohs(*(uint16_t *)(start_of_udp + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (udp_size > MaxSize || udp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the udp packet + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_udp, udp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole UDP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the UDP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, udp_header_size, udp_header_size); + + // Drop checksum + *(uint16_t *)(Data_to_mutate + 6) = 0; + + // Copy the mutated data back to the `Data` array + memcpy(start_of_udp, Data_to_mutate, udp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/app/src/main/cpp/libslirp/fuzzing/tftp/toto b/app/src/main/cpp/libslirp/fuzzing/tftp/toto new file mode 100644 index 0000000000000000000000000000000000000000..0b70c57e3dbcab803a4be89b34c1cccecb69da2b GIT binary patch literal 1300 XcmZQz7zLvtFd71*Aut*Oun+(M1rz`Q literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/libslirp/meson.build b/app/src/main/cpp/libslirp/meson.build new file mode 100644 index 00000000..7a3da0e0 --- /dev/null +++ b/app/src/main/cpp/libslirp/meson.build @@ -0,0 +1,243 @@ +project('libslirp', 'c', + version : '4.7.0', + license : 'BSD-3-Clause', + default_options : ['warning_level=1', 'c_std=gnu99'], + meson_version : '>= 0.50', +) + +version = meson.project_version() +varr = version.split('.') +major_version = varr[0] +minor_version = varr[1] +micro_version = varr[2] + +conf = configuration_data() +conf.set('SLIRP_MAJOR_VERSION', major_version) +conf.set('SLIRP_MINOR_VERSION', minor_version) +conf.set('SLIRP_MICRO_VERSION', micro_version) + +want_ossfuzz = get_option('oss-fuzz') +want_libfuzzer = get_option('llvm-fuzz') +fuzz_reproduce = get_option('fuzz-reproduce') +if want_ossfuzz and want_libfuzzer + error('only one of oss-fuzz and llvm-fuzz can be specified') +endif +fuzzer_build = want_ossfuzz or want_libfuzzer +if fuzzer_build and fuzz_reproduce + error('fuzzer build and reproducer build are mutually exclusive') +endif + +cc = meson.get_compiler('c') +add_languages('cpp', required : fuzzer_build) + +if get_option('static') == true + add_global_arguments('-static', language : 'c') +endif + +if cc.get_argument_syntax() != 'msvc' + r = run_command('build-aux/git-version-gen', + '@0@/.tarball-version'.format(meson.current_source_dir()), + check : false) + + full_version = r.stdout().strip() + if r.returncode() != 0 or full_version.startswith('UNKNOWN') + full_version = meson.project_version() + elif not full_version.startswith(meson.project_version()) + error('meson.build project version @0@ does not match git-describe output @1@' + .format(meson.project_version(), full_version)) + endif +else + full_version = meson.project_version() +endif +conf.set_quoted('SLIRP_VERSION_STRING', full_version + get_option('version_suffix')) + +# libtool versioning - this applies to libslirp +# +# See http://sources.redhat.com/autobook/autobook/autobook_91.html#SEC91 for details +# +# - If interfaces have been changed or added, but binary compatibility +# has been preserved, change: +# CURRENT += 1 +# REVISION = 0 +# AGE += 1 +# - If binary compatibility has been broken (eg removed or changed +# interfaces), change: +# CURRENT += 1 +# REVISION = 0 +# AGE = 0 +# - If the interface is the same as the previous version, but bugs are +# fixed, change: +# REVISION += 1 +lt_current = 4 +lt_revision = 0 +lt_age = 4 +lt_version = '@0@.@1@.@2@'.format(lt_current - lt_age, lt_age, lt_revision) + +host_system = host_machine.system() + +glib_dep = dependency('glib-2.0', static : get_option('static')) + +add_project_arguments(cc.get_supported_arguments('-Wmissing-prototypes', '-Wstrict-prototypes', + '-Wredundant-decls', '-Wundef', '-Wwrite-strings'), + language: 'c', native: false) + +platform_deps = [] + +if host_system == 'windows' + platform_deps += [ + cc.find_library('ws2_32'), + cc.find_library('iphlpapi') + ] +elif host_system == 'darwin' + platform_deps += [ + cc.find_library('resolv') + ] +elif host_system == 'haiku' + platform_deps += [ + cc.find_library('network') + ] +endif + +cargs = [ + '-DG_LOG_DOMAIN="Slirp"', + '-DBUILDING_LIBSLIRP', +] + +if cc.check_header('valgrind/valgrind.h') + cargs += [ '-DHAVE_VALGRIND=1' ] +endif + +sources = [ + 'src/arp_table.c', + 'src/bootp.c', + 'src/cksum.c', + 'src/dhcpv6.c', + 'src/dnssearch.c', + 'src/if.c', + 'src/ip6_icmp.c', + 'src/ip6_input.c', + 'src/ip6_output.c', + 'src/ip_icmp.c', + 'src/ip_input.c', + 'src/ip_output.c', + 'src/mbuf.c', + 'src/misc.c', + 'src/ncsi.c', + 'src/ndp_table.c', + 'src/sbuf.c', + 'src/slirp.c', + 'src/socket.c', + 'src/state.c', + 'src/stream.c', + 'src/tcp_input.c', + 'src/tcp_output.c', + 'src/tcp_subr.c', + 'src/tcp_timer.c', + 'src/tftp.c', + 'src/udp.c', + 'src/udp6.c', + 'src/util.c', + 'src/version.c', + 'src/vmstate.c', +] + +mapfile = 'src/libslirp.map' +vflag = [] +vflag_test = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile) +if cc.has_link_argument(vflag_test) + vflag += vflag_test +endif + +if fuzzer_build + cargs += '-fsanitize-coverage=edge,indirect-calls,trace-cmp' + cargs += '-fsanitize=fuzzer-no-link,address' + cargs += '-fprofile-instr-generate' + cargs += '-fcoverage-mapping' + cargs += '-g' + cargs += '-DSLIRP_DEBUG' + vflag += '-fsanitize=fuzzer-no-link,address' + vflag += '-fsanitize-coverage=edge,indirect-calls,trace-cmp' + vflag += '-fprofile-instr-generate' + vflag += '-fcoverage-mapping' +endif +if fuzz_reproduce + cargs += '-DSLIRP_DEBUG' + cargs += '-g' +endif + +install_devel = not meson.is_subproject() + +configure_file( + input : 'src/libslirp-version.h.in', + output : 'libslirp-version.h', + install : install_devel, + install_dir : join_paths(get_option('includedir'), 'slirp'), + configuration : conf +) + +if fuzzer_build or fuzz_reproduce + lib = static_library('slirp', sources, + c_args : cargs, + link_args : vflag, + link_depends : mapfile, + dependencies : [glib_dep, platform_deps], + ) +else + lib = library('slirp', sources, + version : lt_version, + c_args : cargs, + link_args : vflag, + link_depends : mapfile, + dependencies : [glib_dep, platform_deps], + install : install_devel or get_option('default_library') == 'shared', + ) +endif + +pingtest = executable('pingtest', 'test/pingtest.c', + link_with: [ lib ], + c_args : cargs, + link_args : vflag, + include_directories: [ 'src' ], + dependencies : [ platform_deps ] +) + +test('ping', pingtest) + +ncsitest = executable('ncsitest', 'test/ncsitest.c', + link_with: [lib], + c_args : cargs, + link_args : vflag, + include_directories: ['src'], + dependencies: [glib_dep, platform_deps] +) + +test('ncsi', ncsitest) + +if install_devel + install_headers(['src/libslirp.h'], subdir : 'slirp') + + pkg = import('pkgconfig') + + pkg.generate( + version : version, + libraries : lib, + requires : [ + 'glib-2.0', + ], + name : 'slirp', + description : 'User-space network stack', + filebase : 'slirp', + subdirs : 'slirp', + ) +else + if get_option('default_library') == 'both' + lib = lib.get_static_lib() + endif +endif + +libslirp_dep = declare_dependency( + link_with : lib, + include_directories : [include_directories('src'), include_directories('.')], +) + +subdir('fuzzing') diff --git a/app/src/main/cpp/libslirp/meson_options.txt b/app/src/main/cpp/libslirp/meson_options.txt new file mode 100644 index 00000000..1dad4b91 --- /dev/null +++ b/app/src/main/cpp/libslirp/meson_options.txt @@ -0,0 +1,14 @@ +option('version_suffix', type: 'string', value: '', + description: 'Suffix to append to SLIRP_VERSION_STRING') + +option('oss-fuzz', type : 'boolean', value : 'false', + description : 'build against oss-fuzz') + +option('llvm-fuzz', type : 'boolean', value : 'false', + description : 'build against LLVM libFuzzer') + +option('fuzz-reproduce', type : 'boolean', value : 'false', + description : 'build a standalone executable to reproduce fuzz cases') + +option('static', type : 'boolean', value : 'false', + description : 'build static binary') diff --git a/app/src/main/cpp/libslirp/src/arp_table.c b/app/src/main/cpp/libslirp/src/arp_table.c new file mode 100644 index 00000000..3cf2ecc2 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/arp_table.c @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: MIT */ +/* + * ARP table + * + * Copyright (c) 2011 AdaCore + * + * 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 "slirp.h" + +#include + +void arp_table_add(Slirp *slirp, uint32_t ip_addr, + const uint8_t ethaddr[ETH_ALEN]) +{ + const uint32_t broadcast_addr = + ~slirp->vnetwork_mask.s_addr | slirp->vnetwork_addr.s_addr; + ArpTable *arptbl = &slirp->arp_table; + int i; + char ethaddr_str[ETH_ADDRSTRLEN]; + char addr[INET_ADDRSTRLEN]; + + DEBUG_CALL("arp_table_add"); + DEBUG_ARG("ip = %s", inet_ntop(AF_INET, &(struct in_addr){ .s_addr = ip_addr }, + addr, sizeof(addr))); + DEBUG_ARG("hw addr = %s", slirp_ether_ntoa(ethaddr, ethaddr_str, + sizeof(ethaddr_str))); + + if (ip_addr == 0 || ip_addr == 0xffffffff || ip_addr == broadcast_addr) { + /* Do not register broadcast addresses */ + return; + } + + /* Search for an entry */ + for (i = 0; i < ARP_TABLE_SIZE; i++) { + if (arptbl->table[i].ar_sip == ip_addr) { + /* Update the entry */ + memcpy(arptbl->table[i].ar_sha, ethaddr, ETH_ALEN); + return; + } + } + + /* No entry found, create a new one */ + arptbl->table[arptbl->next_victim].ar_sip = ip_addr; + memcpy(arptbl->table[arptbl->next_victim].ar_sha, ethaddr, ETH_ALEN); + arptbl->next_victim = (arptbl->next_victim + 1) % ARP_TABLE_SIZE; +} + +bool arp_table_search(Slirp *slirp, uint32_t ip_addr, + uint8_t out_ethaddr[ETH_ALEN]) +{ + const uint32_t broadcast_addr = + ~slirp->vnetwork_mask.s_addr | slirp->vnetwork_addr.s_addr; + ArpTable *arptbl = &slirp->arp_table; + int i; + char ethaddr_str[ETH_ADDRSTRLEN]; + char addr[INET_ADDRSTRLEN]; + + DEBUG_CALL("arp_table_search"); + DEBUG_ARG("ip = %s", inet_ntop(AF_INET, &(struct in_addr){ .s_addr = ip_addr }, + addr, sizeof(addr))); + + /* If broadcast address */ + if (ip_addr == 0 || ip_addr == 0xffffffff || ip_addr == broadcast_addr) { + /* return Ethernet broadcast address */ + memset(out_ethaddr, 0xff, ETH_ALEN); + return 1; + } + + for (i = 0; i < ARP_TABLE_SIZE; i++) { + if (arptbl->table[i].ar_sip == ip_addr) { + memcpy(out_ethaddr, arptbl->table[i].ar_sha, ETH_ALEN); + DEBUG_ARG("found hw addr = %s", + slirp_ether_ntoa(out_ethaddr, ethaddr_str, + sizeof(ethaddr_str))); + return 1; + } + } + + return 0; +} diff --git a/app/src/main/cpp/libslirp/src/bootp.c b/app/src/main/cpp/libslirp/src/bootp.c new file mode 100644 index 00000000..147955fc --- /dev/null +++ b/app/src/main/cpp/libslirp/src/bootp.c @@ -0,0 +1,398 @@ +/* SPDX-License-Identifier: MIT */ +/* + * QEMU BOOTP/DHCP server + * + * Copyright (c) 2004 Fabrice Bellard + * + * 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 "slirp.h" + +#if defined(_WIN32) +/* Windows ntohl() returns an u_long value. + * Add a type cast to match the format strings. */ +#define ntohl(n) ((uint32_t)ntohl(n)) +#endif + +/* XXX: only DHCP is supported */ + +#define LEASE_TIME (24 * 3600) + +#define UEFI_HTTP_VENDOR_CLASS_ID "HTTPClient" + +static const uint8_t rfc1533_cookie[] = { RFC1533_COOKIE }; + +#define DPRINTF(...) DEBUG_RAW_CALL(__VA_ARGS__) + +static BOOTPClient *get_new_addr(Slirp *slirp, struct in_addr *paddr, + const uint8_t *macaddr) +{ + BOOTPClient *bc; + int i; + + for (i = 0; i < NB_BOOTP_CLIENTS; i++) { + bc = &slirp->bootp_clients[i]; + if (!bc->allocated || !memcmp(macaddr, bc->macaddr, 6)) + goto found; + } + return NULL; +found: + bc = &slirp->bootp_clients[i]; + bc->allocated = 1; + paddr->s_addr = slirp->vdhcp_startaddr.s_addr + htonl(i); + return bc; +} + +static BOOTPClient *request_addr(Slirp *slirp, const struct in_addr *paddr, + const uint8_t *macaddr) +{ + uint32_t req_addr = ntohl(paddr->s_addr); + uint32_t dhcp_addr = ntohl(slirp->vdhcp_startaddr.s_addr); + BOOTPClient *bc; + + if (req_addr >= dhcp_addr && req_addr < (dhcp_addr + NB_BOOTP_CLIENTS)) { + bc = &slirp->bootp_clients[req_addr - dhcp_addr]; + if (!bc->allocated || !memcmp(macaddr, bc->macaddr, 6)) { + bc->allocated = 1; + return bc; + } + } + return NULL; +} + +static BOOTPClient *find_addr(Slirp *slirp, struct in_addr *paddr, + const uint8_t *macaddr) +{ + BOOTPClient *bc; + int i; + + for (i = 0; i < NB_BOOTP_CLIENTS; i++) { + if (!memcmp(macaddr, slirp->bootp_clients[i].macaddr, 6)) + goto found; + } + return NULL; +found: + bc = &slirp->bootp_clients[i]; + bc->allocated = 1; + paddr->s_addr = slirp->vdhcp_startaddr.s_addr + htonl(i); + return bc; +} + +static void dhcp_decode(const struct bootp_t *bp, + const uint8_t *bp_end, + int *pmsg_type, + struct in_addr *preq_addr) +{ + const uint8_t *p; + int len, tag; + + *pmsg_type = 0; + preq_addr->s_addr = htonl(0L); + + p = bp->bp_vend; + if (memcmp(p, rfc1533_cookie, 4) != 0) + return; + p += 4; + while (p < bp_end) { + tag = p[0]; + if (tag == RFC1533_PAD) { + p++; + } else if (tag == RFC1533_END) { + break; + } else { + p++; + if (p >= bp_end) + break; + len = *p++; + if (p + len > bp_end) { + break; + } + DPRINTF("dhcp: tag=%d len=%d\n", tag, len); + + switch (tag) { + case RFC2132_MSG_TYPE: + if (len >= 1) + *pmsg_type = p[0]; + break; + case RFC2132_REQ_ADDR: + if (len >= 4) { + memcpy(&(preq_addr->s_addr), p, 4); + } + break; + default: + break; + } + p += len; + } + } + if (*pmsg_type == DHCPREQUEST && preq_addr->s_addr == htonl(0L) && + bp->bp_ciaddr.s_addr) { + memcpy(&(preq_addr->s_addr), &bp->bp_ciaddr, 4); + } +} + +static void bootp_reply(Slirp *slirp, + const struct bootp_t *bp, + const uint8_t *bp_end) +{ + BOOTPClient *bc = NULL; + struct mbuf *m; + struct bootp_t *rbp; + struct sockaddr_in saddr, daddr; + struct in_addr preq_addr; + int dhcp_msg_type, val; + uint8_t *q; + uint8_t *end; + uint8_t client_ethaddr[ETH_ALEN]; + + /* extract exact DHCP msg type */ + dhcp_decode(bp, bp_end, &dhcp_msg_type, &preq_addr); + DPRINTF("bootp packet op=%d msgtype=%d", bp->bp_op, dhcp_msg_type); + if (preq_addr.s_addr != htonl(0L)) + DPRINTF(" req_addr=%08" PRIx32 "\n", ntohl(preq_addr.s_addr)); + else { + DPRINTF("\n"); + } + + if (dhcp_msg_type == 0) + dhcp_msg_type = DHCPREQUEST; /* Force reply for old BOOTP clients */ + + if (dhcp_msg_type != DHCPDISCOVER && dhcp_msg_type != DHCPREQUEST) + return; + + /* Get client's hardware address from bootp request */ + memcpy(client_ethaddr, bp->bp_hwaddr, ETH_ALEN); + + m = m_get(slirp); + if (!m) { + return; + } + m->m_data += IF_MAXLINKHDR; + m_inc(m, sizeof(struct bootp_t) + DHCP_OPT_LEN); + rbp = (struct bootp_t *)m->m_data; + m->m_data += sizeof(struct udpiphdr); + memset(rbp, 0, sizeof(struct bootp_t) + DHCP_OPT_LEN); + + if (dhcp_msg_type == DHCPDISCOVER) { + if (preq_addr.s_addr != htonl(0L)) { + bc = request_addr(slirp, &preq_addr, client_ethaddr); + if (bc) { + daddr.sin_addr = preq_addr; + } + } + if (!bc) { + new_addr: + bc = get_new_addr(slirp, &daddr.sin_addr, client_ethaddr); + if (!bc) { + DPRINTF("no address left\n"); + return; + } + } + memcpy(bc->macaddr, client_ethaddr, ETH_ALEN); + } else if (preq_addr.s_addr != htonl(0L)) { + bc = request_addr(slirp, &preq_addr, client_ethaddr); + if (bc) { + daddr.sin_addr = preq_addr; + memcpy(bc->macaddr, client_ethaddr, ETH_ALEN); + } else { + /* DHCPNAKs should be sent to broadcast */ + daddr.sin_addr.s_addr = 0xffffffff; + } + } else { + bc = find_addr(slirp, &daddr.sin_addr, bp->bp_hwaddr); + if (!bc) { + /* if never assigned, behaves as if it was already + assigned (windows fix because it remembers its address) */ + goto new_addr; + } + } + + /* Update ARP table for this IP address */ + arp_table_add(slirp, daddr.sin_addr.s_addr, client_ethaddr); + + saddr.sin_addr = slirp->vhost_addr; + saddr.sin_port = htons(BOOTP_SERVER); + + daddr.sin_port = htons(BOOTP_CLIENT); + + rbp->bp_op = BOOTP_REPLY; + rbp->bp_xid = bp->bp_xid; + rbp->bp_htype = 1; + rbp->bp_hlen = 6; + memcpy(rbp->bp_hwaddr, bp->bp_hwaddr, ETH_ALEN); + + rbp->bp_yiaddr = daddr.sin_addr; /* Client IP address */ + rbp->bp_siaddr = saddr.sin_addr; /* Server IP address */ + + q = rbp->bp_vend; + end = rbp->bp_vend + DHCP_OPT_LEN; + memcpy(q, rfc1533_cookie, 4); + q += 4; + + if (bc) { + DPRINTF("%s addr=%08" PRIx32 "\n", + (dhcp_msg_type == DHCPDISCOVER) ? "offered" : "ack'ed", + ntohl(daddr.sin_addr.s_addr)); + + if (dhcp_msg_type == DHCPDISCOVER) { + *q++ = RFC2132_MSG_TYPE; + *q++ = 1; + *q++ = DHCPOFFER; + } else /* DHCPREQUEST */ { + *q++ = RFC2132_MSG_TYPE; + *q++ = 1; + *q++ = DHCPACK; + } + + if (slirp->bootp_filename) { + g_assert(strlen(slirp->bootp_filename) < sizeof(rbp->bp_file)); + strcpy(rbp->bp_file, slirp->bootp_filename); + } + + *q++ = RFC2132_SRV_ID; + *q++ = 4; + memcpy(q, &saddr.sin_addr, 4); + q += 4; + + *q++ = RFC1533_NETMASK; + *q++ = 4; + memcpy(q, &slirp->vnetwork_mask, 4); + q += 4; + + if (!slirp->restricted) { + *q++ = RFC1533_GATEWAY; + *q++ = 4; + memcpy(q, &saddr.sin_addr, 4); + q += 4; + + *q++ = RFC1533_DNS; + *q++ = 4; + memcpy(q, &slirp->vnameserver_addr, 4); + q += 4; + } + + *q++ = RFC2132_LEASE_TIME; + *q++ = 4; + val = htonl(LEASE_TIME); + memcpy(q, &val, 4); + q += 4; + + if (*slirp->client_hostname) { + val = strlen(slirp->client_hostname); + if (q + val + 2 >= end) { + g_warning("DHCP packet size exceeded, " + "omitting host name option."); + } else { + *q++ = RFC1533_HOSTNAME; + *q++ = val; + memcpy(q, slirp->client_hostname, val); + q += val; + } + } + + if (slirp->vdomainname) { + val = strlen(slirp->vdomainname); + if (q + val + 2 >= end) { + g_warning("DHCP packet size exceeded, " + "omitting domain name option."); + } else { + *q++ = RFC1533_DOMAINNAME; + *q++ = val; + memcpy(q, slirp->vdomainname, val); + q += val; + } + } + + if (slirp->tftp_server_name) { + val = strlen(slirp->tftp_server_name); + if (q + val + 2 >= end) { + g_warning("DHCP packet size exceeded, " + "omitting tftp-server-name option."); + } else { + *q++ = RFC2132_TFTP_SERVER_NAME; + *q++ = val; + memcpy(q, slirp->tftp_server_name, val); + q += val; + } + } + + if (slirp->vdnssearch) { + val = slirp->vdnssearch_len; + if (q + val >= end) { + g_warning("DHCP packet size exceeded, " + "omitting domain-search option."); + } else { + memcpy(q, slirp->vdnssearch, val); + q += val; + } + } + + /* this allows to support UEFI HTTP boot: according to the UEFI + specification, DHCP server must send vendor class identifier option + set to "HTTPClient" string, when responding to DHCP requests as part + of the UEFI HTTP boot + + we assume that, if the bootfile parameter was configured as an http + URL, the user intends to perform UEFI HTTP boot, so send this option + automatically */ + if (slirp->bootp_filename && g_str_has_prefix(slirp->bootp_filename, "http://")) { + val = strlen(UEFI_HTTP_VENDOR_CLASS_ID); + if (q + val + 2 >= end) { + g_warning("DHCP packet size exceeded, " + "omitting vendor class id option."); + } else { + *q++ = RFC2132_VENDOR_CLASS_ID; + *q++ = val; + memcpy(q, UEFI_HTTP_VENDOR_CLASS_ID, val); + q += val; + } + } + } else { + static const char nak_msg[] = "requested address not available"; + + DPRINTF("nak'ed addr=%08" PRIx32 "\n", ntohl(preq_addr.s_addr)); + + *q++ = RFC2132_MSG_TYPE; + *q++ = 1; + *q++ = DHCPNAK; + + *q++ = RFC2132_MESSAGE; + *q++ = sizeof(nak_msg) - 1; + memcpy(q, nak_msg, sizeof(nak_msg) - 1); + q += sizeof(nak_msg) - 1; + } + assert(q < end); + *q++ = RFC1533_END; + + daddr.sin_addr.s_addr = 0xffffffffu; + + assert(q <= end); + + m->m_len = sizeof(struct bootp_t) + (end - rbp->bp_vend) - sizeof(struct ip) - sizeof(struct udphdr); + udp_output(NULL, m, &saddr, &daddr, IPTOS_LOWDELAY); +} + +void bootp_input(struct mbuf *m) +{ + struct bootp_t *bp = mtod_check(m, sizeof(struct bootp_t)); + + if (!m->slirp->disable_dhcp && bp && bp->bp_op == BOOTP_REQUEST) { + bootp_reply(m->slirp, bp, m_end(m)); + } +} diff --git a/app/src/main/cpp/libslirp/src/bootp.h b/app/src/main/cpp/libslirp/src/bootp.h new file mode 100644 index 00000000..c5109602 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/bootp.h @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* bootp/dhcp defines */ + +#ifndef SLIRP_BOOTP_H +#define SLIRP_BOOTP_H + +#define BOOTP_SERVER 67 +#define BOOTP_CLIENT 68 + +#define BOOTP_REQUEST 1 +#define BOOTP_REPLY 2 + +#define RFC1533_COOKIE 99, 130, 83, 99 +#define RFC1533_PAD 0 +#define RFC1533_NETMASK 1 +#define RFC1533_TIMEOFFSET 2 +#define RFC1533_GATEWAY 3 +#define RFC1533_TIMESERVER 4 +#define RFC1533_IEN116NS 5 +#define RFC1533_DNS 6 +#define RFC1533_LOGSERVER 7 +#define RFC1533_COOKIESERVER 8 +#define RFC1533_LPRSERVER 9 +#define RFC1533_IMPRESSSERVER 10 +#define RFC1533_RESOURCESERVER 11 +#define RFC1533_HOSTNAME 12 +#define RFC1533_BOOTFILESIZE 13 +#define RFC1533_MERITDUMPFILE 14 +#define RFC1533_DOMAINNAME 15 +#define RFC1533_SWAPSERVER 16 +#define RFC1533_ROOTPATH 17 +#define RFC1533_EXTENSIONPATH 18 +#define RFC1533_IPFORWARDING 19 +#define RFC1533_IPSOURCEROUTING 20 +#define RFC1533_IPPOLICYFILTER 21 +#define RFC1533_IPMAXREASSEMBLY 22 +#define RFC1533_IPTTL 23 +#define RFC1533_IPMTU 24 +#define RFC1533_IPMTUPLATEAU 25 +#define RFC1533_INTMTU 26 +#define RFC1533_INTLOCALSUBNETS 27 +#define RFC1533_INTBROADCAST 28 +#define RFC1533_INTICMPDISCOVER 29 +#define RFC1533_INTICMPRESPOND 30 +#define RFC1533_INTROUTEDISCOVER 31 +#define RFC1533_INTROUTESOLICIT 32 +#define RFC1533_INTSTATICROUTES 33 +#define RFC1533_LLTRAILERENCAP 34 +#define RFC1533_LLARPCACHETMO 35 +#define RFC1533_LLETHERNETENCAP 36 +#define RFC1533_TCPTTL 37 +#define RFC1533_TCPKEEPALIVETMO 38 +#define RFC1533_TCPKEEPALIVEGB 39 +#define RFC1533_NISDOMAIN 40 +#define RFC1533_NISSERVER 41 +#define RFC1533_NTPSERVER 42 +#define RFC1533_VENDOR 43 +#define RFC1533_NBNS 44 +#define RFC1533_NBDD 45 +#define RFC1533_NBNT 46 +#define RFC1533_NBSCOPE 47 +#define RFC1533_XFS 48 +#define RFC1533_XDM 49 + +#define RFC2132_REQ_ADDR 50 +#define RFC2132_LEASE_TIME 51 +#define RFC2132_MSG_TYPE 53 +#define RFC2132_SRV_ID 54 +#define RFC2132_PARAM_LIST 55 +#define RFC2132_MESSAGE 56 +#define RFC2132_MAX_SIZE 57 +#define RFC2132_RENEWAL_TIME 58 +#define RFC2132_REBIND_TIME 59 +#define RFC2132_VENDOR_CLASS_ID 60 +#define RFC2132_TFTP_SERVER_NAME 66 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPACK 5 +#define DHCPNAK 6 + +#define RFC1533_VENDOR_MAJOR 0 +#define RFC1533_VENDOR_MINOR 0 + +#define RFC1533_VENDOR_MAGIC 128 +#define RFC1533_VENDOR_ADDPARM 129 +#define RFC1533_VENDOR_ETHDEV 130 +#define RFC1533_VENDOR_HOWTO 132 +#define RFC1533_VENDOR_MNUOPTS 160 +#define RFC1533_VENDOR_SELECTION 176 +#define RFC1533_VENDOR_MOTD 184 +#define RFC1533_VENDOR_NUMOFMOTD 8 +#define RFC1533_VENDOR_IMG 192 +#define RFC1533_VENDOR_NUMOFIMG 16 + +#define RFC1533_END 255 +#define BOOTP_VENDOR_LEN 64 +#define DHCP_OPT_LEN 312 + +struct bootp_t { + struct ip ip; + struct udphdr udp; + uint8_t bp_op; + uint8_t bp_htype; + uint8_t bp_hlen; + uint8_t bp_hops; + uint32_t bp_xid; + uint16_t bp_secs; + uint16_t unused; + struct in_addr bp_ciaddr; + struct in_addr bp_yiaddr; + struct in_addr bp_siaddr; + struct in_addr bp_giaddr; + uint8_t bp_hwaddr[16]; + uint8_t bp_sname[64]; + char bp_file[128]; + uint8_t bp_vend[]; +}; + +typedef struct { + uint16_t allocated; + uint8_t macaddr[6]; +} BOOTPClient; + +#define NB_BOOTP_CLIENTS 16 + +/* Process a bootp packet from the guest */ +void bootp_input(struct mbuf *m); + +#endif diff --git a/app/src/main/cpp/libslirp/src/cksum.c b/app/src/main/cpp/libslirp/src/cksum.c new file mode 100644 index 00000000..b1cb97b7 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/cksum.c @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1988, 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)in_cksum.c 8.1 (Berkeley) 6/10/93 + * in_cksum.c,v 1.2 1994/08/02 07:48:16 davidg Exp + */ + +#include "slirp.h" + +/* + * Checksum routine for Internet Protocol family headers (Portable Version). + * + * This routine is very heavily used in the network + * code and should be modified for each CPU to be as fast as possible. + * + * XXX Since we will never span more than 1 mbuf, we can optimise this + */ + +#define ADDCARRY(x) (x > 65535 ? x -= 65535 : x) +#define REDUCE \ + { \ + l_util.l = sum; \ + sum = l_util.s[0] + l_util.s[1]; \ + ADDCARRY(sum); \ + } + +int cksum(struct mbuf *m, int len) +{ + register uint16_t *w; + register int sum = 0; + register int mlen = 0; + int byte_swapped = 0; + + union { + uint8_t c[2]; + uint16_t s; + } s_util; + union { + uint16_t s[2]; + uint32_t l; + } l_util; + + if (m->m_len == 0) + goto cont; + w = mtod(m, uint16_t *); + + mlen = m->m_len; + + if (len < mlen) + mlen = len; + len -= mlen; + /* + * Force to even boundary. + */ + if ((1 & (uintptr_t)w) && (mlen > 0)) { + REDUCE; + sum <<= 8; + s_util.c[0] = *(uint8_t *)w; + w = (uint16_t *)((int8_t *)w + 1); + mlen--; + byte_swapped = 1; + } + /* + * Unroll the loop to make overhead from + * branches &c small. + */ + while ((mlen -= 32) >= 0) { + sum += w[0]; + sum += w[1]; + sum += w[2]; + sum += w[3]; + sum += w[4]; + sum += w[5]; + sum += w[6]; + sum += w[7]; + sum += w[8]; + sum += w[9]; + sum += w[10]; + sum += w[11]; + sum += w[12]; + sum += w[13]; + sum += w[14]; + sum += w[15]; + w += 16; + } + mlen += 32; + while ((mlen -= 8) >= 0) { + sum += w[0]; + sum += w[1]; + sum += w[2]; + sum += w[3]; + w += 4; + } + mlen += 8; + if (mlen == 0 && byte_swapped == 0) + goto cont; + REDUCE; + while ((mlen -= 2) >= 0) { + sum += *w++; + } + + if (byte_swapped) { + REDUCE; + sum <<= 8; + if (mlen == -1) { + s_util.c[1] = *(uint8_t *)w; + sum += s_util.s; + mlen = 0; + } else + + mlen = -1; + } else if (mlen == -1) + s_util.c[0] = *(uint8_t *)w; + +cont: + if (len) { + DEBUG_ERROR("cksum: out of data"); + DEBUG_ERROR(" len = %d", len); + } + if (mlen == -1) { + /* The last mbuf has odd # of bytes. Follow the + standard (the odd byte may be shifted left by 8 bits + or not as determined by endian-ness of the machine) */ + s_util.c[1] = 0; + sum += s_util.s; + } + REDUCE; + return (~sum & 0xffff); +} + +int ip6_cksum(struct mbuf *m) +{ + /* TODO: Optimize this by being able to pass the ip6_pseudohdr to cksum + * separately from the mbuf */ + struct ip6 save_ip, *ip = mtod(m, struct ip6 *); + struct ip6_pseudohdr *ih = mtod(m, struct ip6_pseudohdr *); + int sum; + + save_ip = *ip; + + ih->ih_src = save_ip.ip_src; + ih->ih_dst = save_ip.ip_dst; + ih->ih_pl = htonl((uint32_t)ntohs(save_ip.ip_pl)); + ih->ih_zero_hi = 0; + ih->ih_zero_lo = 0; + ih->ih_nh = save_ip.ip_nh; + + sum = cksum(m, ((int)sizeof(struct ip6_pseudohdr)) + ntohl(ih->ih_pl)); + + *ip = save_ip; + + return sum; +} diff --git a/app/src/main/cpp/libslirp/src/debug.h b/app/src/main/cpp/libslirp/src/debug.h new file mode 100644 index 00000000..f4da00cf --- /dev/null +++ b/app/src/main/cpp/libslirp/src/debug.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#ifndef DEBUG_H_ +#define DEBUG_H_ + +#define DBG_CALL (1 << 0) +#define DBG_MISC (1 << 1) +#define DBG_ERROR (1 << 2) +#define DBG_TFTP (1 << 3) +#define DBG_VERBOSE_CALL (1 << 4) + +extern int slirp_debug; + +#define DEBUG_CALL(name) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_CALL)) { \ + g_debug(name "..."); \ + } \ + } while (0) + +#define DEBUG_VERBOSE_CALL(name) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_VERBOSE_CALL)) { \ + g_debug(name "..."); \ + } \ + } while (0) + +#define DEBUG_RAW_CALL(...) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_CALL)) { \ + g_debug(__VA_ARGS__); \ + } \ + } while (0) + +#define DEBUG_ARG(...) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_CALL)) { \ + g_debug(" " __VA_ARGS__); \ + } \ + } while (0) + +#define DEBUG_MISC(...) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_MISC)) { \ + g_debug(__VA_ARGS__); \ + } \ + } while (0) + +#define DEBUG_ERROR(...) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_ERROR)) { \ + g_debug(__VA_ARGS__); \ + } \ + } while (0) + +#define DEBUG_TFTP(...) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_TFTP)) { \ + g_debug(__VA_ARGS__); \ + } \ + } while (0) + +#endif /* DEBUG_H_ */ diff --git a/app/src/main/cpp/libslirp/src/dhcpv6.c b/app/src/main/cpp/libslirp/src/dhcpv6.c new file mode 100644 index 00000000..77b451b9 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/dhcpv6.c @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * SLIRP stateless DHCPv6 + * + * We only support stateless DHCPv6, e.g. for network booting. + * See RFC 3315, RFC 3736, RFC 3646 and RFC 5970 for details. + * + * Copyright 2016 Thomas Huth, Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "slirp.h" +#include "dhcpv6.h" + +/* DHCPv6 message types */ +#define MSGTYPE_REPLY 7 +#define MSGTYPE_INFO_REQUEST 11 + +/* DHCPv6 option types */ +#define OPTION_CLIENTID 1 +#define OPTION_IAADDR 5 +#define OPTION_ORO 6 +#define OPTION_DNS_SERVERS 23 +#define OPTION_BOOTFILE_URL 59 + +struct requested_infos { + uint8_t *client_id; + int client_id_len; + bool want_dns; + bool want_boot_url; +}; + +/** + * Analyze the info request message sent by the client to see what data it + * provided and what it wants to have. The information is gathered in the + * "requested_infos" struct. Note that client_id (if provided) points into + * the odata region, thus the caller must keep odata valid as long as it + * needs to access the requested_infos struct. + */ +static int dhcpv6_parse_info_request(Slirp *slirp, uint8_t *odata, int olen, + struct requested_infos *ri) +{ + int i, req_opt; + + while (olen > 4) { + /* Parse one option */ + int option = odata[0] << 8 | odata[1]; + int len = odata[2] << 8 | odata[3]; + + if (len + 4 > olen) { + slirp->cb->guest_error("Guest sent bad DHCPv6 packet!", + slirp->opaque); + return -E2BIG; + } + + switch (option) { + case OPTION_IAADDR: + /* According to RFC3315, we must discard requests with IA option */ + return -EINVAL; + case OPTION_CLIENTID: + if (len > 256) { + /* Avoid very long IDs which could cause problems later */ + return -E2BIG; + } + ri->client_id = odata + 4; + ri->client_id_len = len; + break; + case OPTION_ORO: /* Option request option */ + if (len & 1) { + return -EINVAL; + } + /* Check which options the client wants to have */ + for (i = 0; i < len; i += 2) { + req_opt = odata[4 + i] << 8 | odata[4 + i + 1]; + switch (req_opt) { + case OPTION_DNS_SERVERS: + ri->want_dns = true; + break; + case OPTION_BOOTFILE_URL: + ri->want_boot_url = true; + break; + default: + DEBUG_MISC("dhcpv6: Unsupported option request %d", + req_opt); + } + } + break; + default: + DEBUG_MISC("dhcpv6 info req: Unsupported option %d, len=%d", option, + len); + } + + odata += len + 4; + olen -= len + 4; + } + + return 0; +} + + +/** + * Handle information request messages + */ +static void dhcpv6_info_request(Slirp *slirp, struct sockaddr_in6 *srcsas, + uint32_t xid, uint8_t *odata, int olen) +{ + struct requested_infos ri = { NULL }; + struct sockaddr_in6 sa6, da6; + struct mbuf *m; + uint8_t *resp; + + if (dhcpv6_parse_info_request(slirp, odata, olen, &ri) < 0) { + return; + } + + m = m_get(slirp); + if (!m) { + return; + } + memset(m->m_data, 0, m->m_size); + m->m_data += IF_MAXLINKHDR; + resp = (uint8_t *)m->m_data + sizeof(struct ip6) + sizeof(struct udphdr); + + /* Fill in response */ + *resp++ = MSGTYPE_REPLY; + *resp++ = (uint8_t)(xid >> 16); + *resp++ = (uint8_t)(xid >> 8); + *resp++ = (uint8_t)xid; + + if (ri.client_id) { + *resp++ = OPTION_CLIENTID >> 8; /* option-code high byte */ + *resp++ = OPTION_CLIENTID; /* option-code low byte */ + *resp++ = ri.client_id_len >> 8; /* option-len high byte */ + *resp++ = ri.client_id_len; /* option-len low byte */ + memcpy(resp, ri.client_id, ri.client_id_len); + resp += ri.client_id_len; + } + if (ri.want_dns) { + *resp++ = OPTION_DNS_SERVERS >> 8; /* option-code high byte */ + *resp++ = OPTION_DNS_SERVERS; /* option-code low byte */ + *resp++ = 0; /* option-len high byte */ + *resp++ = 16; /* option-len low byte */ + memcpy(resp, &slirp->vnameserver_addr6, 16); + resp += 16; + } + if (ri.want_boot_url) { + uint8_t *sa = slirp->vhost_addr6.s6_addr; + int slen, smaxlen; + + *resp++ = OPTION_BOOTFILE_URL >> 8; /* option-code high byte */ + *resp++ = OPTION_BOOTFILE_URL; /* option-code low byte */ + smaxlen = (uint8_t *)m->m_data + slirp->if_mtu - (resp + 2); + slen = slirp_fmt((char *)resp + 2, smaxlen, + "tftp://[%02x%02x:%02x%02x:%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:%02x%02x:%02x%02x]/%s", + sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7], + sa[8], sa[9], sa[10], sa[11], sa[12], sa[13], sa[14], + sa[15], slirp->bootp_filename); + *resp++ = slen >> 8; /* option-len high byte */ + *resp++ = slen; /* option-len low byte */ + resp += slen; + } + + sa6.sin6_addr = slirp->vhost_addr6; + sa6.sin6_port = DHCPV6_SERVER_PORT; + da6.sin6_addr = srcsas->sin6_addr; + da6.sin6_port = srcsas->sin6_port; + m->m_data += sizeof(struct ip6) + sizeof(struct udphdr); + m->m_len = resp - (uint8_t *)m->m_data; + udp6_output(NULL, m, &sa6, &da6); +} + +/** + * Handle DHCPv6 messages sent by the client + */ +void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m) +{ + uint8_t *data = (uint8_t *)m->m_data + sizeof(struct udphdr); + int data_len = m->m_len - sizeof(struct udphdr); + uint32_t xid; + + if (data_len < 4) { + return; + } + + xid = ntohl(*(uint32_t *)data) & 0xffffff; + + switch (data[0]) { + case MSGTYPE_INFO_REQUEST: + dhcpv6_info_request(m->slirp, srcsas, xid, &data[4], data_len - 4); + break; + default: + DEBUG_MISC("dhcpv6_input: Unsupported message type 0x%x", data[0]); + } +} diff --git a/app/src/main/cpp/libslirp/src/dhcpv6.h b/app/src/main/cpp/libslirp/src/dhcpv6.h new file mode 100644 index 00000000..c0b4a248 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/dhcpv6.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Definitions and prototypes for SLIRP stateless DHCPv6 + * + * Copyright 2016 Thomas Huth, Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef SLIRP_DHCPV6_H +#define SLIRP_DHCPV6_H + +#define DHCPV6_SERVER_PORT 547 + +#define ALLDHCP_MULTICAST \ + { \ + .s6_addr = { \ + 0xff, \ + 0x02, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x01, \ + 0x00, \ + 0x02 \ + } \ + } + +#define in6_dhcp_multicast(a) in6_equal(a, &(struct in6_addr)ALLDHCP_MULTICAST) + +/* Process a DHCPv6 packet from the guest */ +void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m); + +#endif diff --git a/app/src/main/cpp/libslirp/src/dnssearch.c b/app/src/main/cpp/libslirp/src/dnssearch.c new file mode 100644 index 00000000..cbd1a197 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/dnssearch.c @@ -0,0 +1,306 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Domain search option for DHCP (RFC 3397) + * + * Copyright (c) 2012 Klaus Stengel + * + * 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 "slirp.h" + +static const uint8_t RFC3397_OPT_DOMAIN_SEARCH = 119; +static const uint8_t MAX_OPT_LEN = 255; +static const uint8_t OPT_HEADER_LEN = 2; +static const uint8_t REFERENCE_LEN = 2; + +struct compact_domain; + +typedef struct compact_domain { + struct compact_domain *self; + struct compact_domain *refdom; + uint8_t *labels; + size_t len; + size_t common_octets; +} CompactDomain; + +static size_t domain_suffix_diffoff(const CompactDomain *a, + const CompactDomain *b) +{ + size_t la = a->len, lb = b->len; + uint8_t *da = a->labels + la, *db = b->labels + lb; + size_t i, lm = (la < lb) ? la : lb; + + for (i = 0; i < lm; i++) { + da--; + db--; + if (*da != *db) { + break; + } + } + return i; +} + +static int domain_suffix_ord(const void *cva, const void *cvb) +{ + const CompactDomain *a = cva, *b = cvb; + size_t la = a->len, lb = b->len; + size_t doff = domain_suffix_diffoff(a, b); + uint8_t ca = a->labels[la - doff]; + uint8_t cb = b->labels[lb - doff]; + + if (ca < cb) { + return -1; + } + if (ca > cb) { + return 1; + } + if (la < lb) { + return -1; + } + if (la > lb) { + return 1; + } + return 0; +} + +static size_t domain_common_label(CompactDomain *a, CompactDomain *b) +{ + size_t res, doff = domain_suffix_diffoff(a, b); + uint8_t *first_eq_pos = a->labels + (a->len - doff); + uint8_t *label = a->labels; + + while (*label && label < first_eq_pos) { + label += *label + 1; + } + res = a->len - (label - a->labels); + /* only report if it can help to reduce the packet size */ + return (res > REFERENCE_LEN) ? res : 0; +} + +static void domain_fixup_order(CompactDomain *cd, size_t n) +{ + size_t i; + + for (i = 0; i < n; i++) { + CompactDomain *cur = cd + i, *next = cd[i].self; + + while (!cur->common_octets) { + CompactDomain *tmp = next->self; /* backup target value */ + + next->self = cur; + cur->common_octets++; + + cur = next; + next = tmp; + } + } +} + +static void domain_mklabels(CompactDomain *cd, const char *input) +{ + uint8_t *len_marker = cd->labels; + uint8_t *output = len_marker; /* pre-incremented */ + const char *in = input; + char cur_chr; + size_t len = 0; + + if (cd->len == 0) { + goto fail; + } + cd->len++; + + do { + cur_chr = *in++; + if (cur_chr == '.' || cur_chr == '\0') { + len = output - len_marker; + if ((len == 0 && cur_chr == '.') || len >= 64) { + goto fail; + } + *len_marker = len; + + output++; + len_marker = output; + } else { + output++; + *output = cur_chr; + } + } while (cur_chr != '\0'); + + /* ensure proper zero-termination */ + if (len != 0) { + *len_marker = 0; + cd->len++; + } + return; + +fail: + g_warning("failed to parse domain name '%s'\n", input); + cd->len = 0; +} + +static void domain_mkxrefs(CompactDomain *doms, CompactDomain *last, + size_t depth) +{ + CompactDomain *i = doms, *target = doms; + + do { + if (i->labels < target->labels) { + target = i; + } + } while (i++ != last); + + for (i = doms; i != last; i++) { + CompactDomain *group_last; + size_t next_depth; + + if (i->common_octets == depth) { + continue; + } + + next_depth = -1; + for (group_last = i; group_last != last; group_last++) { + size_t co = group_last->common_octets; + if (co <= depth) { + break; + } + if (co < next_depth) { + next_depth = co; + } + } + domain_mkxrefs(i, group_last, next_depth); + + i = group_last; + if (i == last) { + break; + } + } + + if (depth == 0) { + return; + } + + i = doms; + do { + if (i != target && i->refdom == NULL) { + i->refdom = target; + i->common_octets = depth; + } + } while (i++ != last); +} + +static size_t domain_compactify(CompactDomain *domains, size_t n) +{ + uint8_t *start = domains->self->labels, *outptr = start; + size_t i; + + for (i = 0; i < n; i++) { + CompactDomain *cd = domains[i].self; + CompactDomain *rd = cd->refdom; + + if (rd != NULL) { + size_t moff = (rd->labels - start) + (rd->len - cd->common_octets); + if (moff < 0x3FFFu) { + cd->len -= cd->common_octets - 2; + cd->labels[cd->len - 1] = moff & 0xFFu; + cd->labels[cd->len - 2] = 0xC0u | (moff >> 8); + } + } + + if (cd->labels != outptr) { + memmove(outptr, cd->labels, cd->len); + cd->labels = outptr; + } + outptr += cd->len; + } + return outptr - start; +} + +int translate_dnssearch(Slirp *s, const char **names) +{ + size_t blocks, bsrc_start, bsrc_end, bdst_start; + size_t i, num_domains, memreq = 0; + uint8_t *result = NULL, *outptr; + CompactDomain *domains = NULL; + + num_domains = g_strv_length((GStrv)(void *)names); + if (num_domains == 0) { + return -2; + } + + domains = g_malloc(num_domains * sizeof(*domains)); + + for (i = 0; i < num_domains; i++) { + size_t nlen = strlen(names[i]); + memreq += nlen + 2; /* 1 zero octet + 1 label length octet */ + domains[i].self = domains + i; + domains[i].len = nlen; + domains[i].common_octets = 0; + domains[i].refdom = NULL; + } + + /* reserve extra 2 header bytes for each 255 bytes of output */ + memreq += DIV_ROUND_UP(memreq, MAX_OPT_LEN) * OPT_HEADER_LEN; + result = g_malloc(memreq * sizeof(*result)); + + outptr = result; + for (i = 0; i < num_domains; i++) { + domains[i].labels = outptr; + domain_mklabels(domains + i, names[i]); + if (domains[i].len == 0) { + /* Bogus entry, reject it all */ + g_free(domains); + g_free(result); + return -1; + } + outptr += domains[i].len; + } + + qsort(domains, num_domains, sizeof(*domains), domain_suffix_ord); + domain_fixup_order(domains, num_domains); + + for (i = 1; i < num_domains; i++) { + size_t cl = domain_common_label(domains + i - 1, domains + i); + domains[i - 1].common_octets = cl; + } + + domain_mkxrefs(domains, domains + num_domains - 1, 0); + memreq = domain_compactify(domains, num_domains); + + blocks = DIV_ROUND_UP(memreq, MAX_OPT_LEN); + bsrc_end = memreq; + bsrc_start = (blocks - 1) * MAX_OPT_LEN; + bdst_start = bsrc_start + blocks * OPT_HEADER_LEN; + memreq += blocks * OPT_HEADER_LEN; + + while (blocks--) { + size_t len = bsrc_end - bsrc_start; + memmove(result + bdst_start, result + bsrc_start, len); + result[bdst_start - 2] = RFC3397_OPT_DOMAIN_SEARCH; + result[bdst_start - 1] = len; + bsrc_end = bsrc_start; + bsrc_start -= MAX_OPT_LEN; + bdst_start -= MAX_OPT_LEN + OPT_HEADER_LEN; + } + + g_free(domains); + s->vdnssearch = result; + s->vdnssearch_len = memreq; + return 0; +} diff --git a/app/src/main/cpp/libslirp/src/if.c b/app/src/main/cpp/libslirp/src/if.c new file mode 100644 index 00000000..c49a64ce --- /dev/null +++ b/app/src/main/cpp/libslirp/src/if.c @@ -0,0 +1,201 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" + +static void ifs_insque(struct mbuf *ifm, struct mbuf *ifmhead) +{ + ifm->m_nextpkt = ifmhead->m_nextpkt; + ifmhead->m_nextpkt = ifm; + ifm->m_prevpkt = ifmhead; + ifm->m_nextpkt->m_prevpkt = ifm; +} + +void if_init(Slirp *slirp) +{ + slirp->if_fastq.qh_link = slirp->if_fastq.qh_rlink = &slirp->if_fastq; + slirp->if_batchq.qh_link = slirp->if_batchq.qh_rlink = &slirp->if_batchq; +} + +/* + * if_output: Queue packet into an output queue. + * There are 2 output queue's, if_fastq and if_batchq. + * Each output queue is a doubly linked list of double linked lists + * of mbufs, each list belonging to one "session" (socket). This + * way, we can output packets fairly by sending one packet from each + * session, instead of all the packets from one session, then all packets + * from the next session, etc. Packets on the if_fastq get absolute + * priority, but if one session hogs the link, it gets "downgraded" + * to the batchq until it runs out of packets, then it'll return + * to the fastq (eg. if the user does an ls -alR in a telnet session, + * it'll temporarily get downgraded to the batchq) + */ +void if_output(struct socket *so, struct mbuf *ifm) +{ + Slirp *slirp = ifm->slirp; + M_DUP_DEBUG(slirp, ifm, 0, 0); + + struct mbuf *ifq; + int on_fastq = 1; + + DEBUG_CALL("if_output"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("ifm = %p", ifm); + + /* + * First remove the mbuf from m_usedlist, + * since we're gonna use m_next and m_prev ourselves + * XXX Shouldn't need this, gotta change dtom() etc. + */ + if (ifm->m_flags & M_USEDLIST) { + slirp_remque(ifm); + ifm->m_flags &= ~M_USEDLIST; + } + + /* + * See if there's already a batchq list for this session. + * This can include an interactive session, which should go on fastq, + * but gets too greedy... hence it'll be downgraded from fastq to batchq. + * We mustn't put this packet back on the fastq (or we'll send it out of + * order) + * XXX add cache here? + */ + if (so) { + for (ifq = (struct mbuf *)slirp->if_batchq.qh_rlink; + (struct slirp_quehead *)ifq != &slirp->if_batchq; + ifq = ifq->m_prev) { + if (so == ifq->m_so) { + /* A match! */ + ifm->m_so = so; + ifs_insque(ifm, ifq->m_prevpkt); + goto diddit; + } + } + } + + /* No match, check which queue to put it on */ + if (so && (so->so_iptos & IPTOS_LOWDELAY)) { + ifq = (struct mbuf *)slirp->if_fastq.qh_rlink; + on_fastq = 1; + /* + * Check if this packet is a part of the last + * packet's session + */ + if (ifq->m_so == so) { + ifm->m_so = so; + ifs_insque(ifm, ifq->m_prevpkt); + goto diddit; + } + } else { + ifq = (struct mbuf *)slirp->if_batchq.qh_rlink; + } + + /* Create a new doubly linked list for this session */ + ifm->m_so = so; + ifs_init(ifm); + slirp_insque(ifm, ifq); + +diddit: + if (so) { + /* Update *_queued */ + so->so_queued++; + so->so_nqueued++; + /* + * Check if the interactive session should be downgraded to + * the batchq. A session is downgraded if it has queued 6 + * packets without pausing, and at least 3 of those packets + * have been sent over the link + * (XXX These are arbitrary numbers, probably not optimal..) + */ + if (on_fastq && + ((so->so_nqueued >= 6) && (so->so_nqueued - so->so_queued) >= 3)) { + /* Remove from current queue... */ + slirp_remque(ifm->m_nextpkt); + + /* ...And insert in the new. That'll teach ya! */ + slirp_insque(ifm->m_nextpkt, &slirp->if_batchq); + } + } + + /* + * This prevents us from malloc()ing too many mbufs + */ + if_start(ifm->slirp); +} + +void if_start(Slirp *slirp) +{ + uint64_t now = slirp->cb->clock_get_ns(slirp->opaque); + bool from_batchq = false; + struct mbuf *ifm, *ifm_next, *ifqt; + + DEBUG_VERBOSE_CALL("if_start"); + + if (slirp->if_start_busy) { + return; + } + slirp->if_start_busy = true; + + struct mbuf *batch_head = NULL; + if (slirp->if_batchq.qh_link != &slirp->if_batchq) { + batch_head = (struct mbuf *)slirp->if_batchq.qh_link; + } + + if (slirp->if_fastq.qh_link != &slirp->if_fastq) { + ifm_next = (struct mbuf *)slirp->if_fastq.qh_link; + } else if (batch_head) { + /* Nothing on fastq, pick up from batchq */ + ifm_next = batch_head; + from_batchq = true; + } else { + ifm_next = NULL; + } + + while (ifm_next) { + ifm = ifm_next; + + ifm_next = ifm->m_next; + if ((struct slirp_quehead *)ifm_next == &slirp->if_fastq) { + /* No more packets in fastq, switch to batchq */ + ifm_next = batch_head; + from_batchq = true; + } + if ((struct slirp_quehead *)ifm_next == &slirp->if_batchq) { + /* end of batchq */ + ifm_next = NULL; + } + + /* Try to send packet unless it already expired */ + if (ifm->expiration_date >= now && !if_encap(slirp, ifm)) { + /* Packet is delayed due to pending ARP or NDP resolution */ + continue; + } + + /* Remove it from the queue */ + ifqt = ifm->m_prev; + slirp_remque(ifm); + + /* If there are more packets for this session, re-queue them */ + if (ifm->m_nextpkt != ifm) { + struct mbuf *next = ifm->m_nextpkt; + + slirp_insque(next, ifqt); + ifs_remque(ifm); + if (!from_batchq) { + ifm_next = next; + } + } + + /* Update so_queued */ + if (ifm->m_so && --ifm->m_so->so_queued == 0) { + /* If there's no more queued, reset nqueued */ + ifm->m_so->so_nqueued = 0; + } + + m_free(ifm); + } + + slirp->if_start_busy = false; +} diff --git a/app/src/main/cpp/libslirp/src/if.h b/app/src/main/cpp/libslirp/src/if.h new file mode 100644 index 00000000..7cf9d275 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/if.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#ifndef IF_H +#define IF_H + +#define IF_COMPRESS 0x01 /* We want compression */ +#define IF_NOCOMPRESS 0x02 /* Do not do compression */ +#define IF_AUTOCOMP 0x04 /* Autodetect (default) */ +#define IF_NOCIDCOMP 0x08 /* CID compression */ + +#define IF_MTU_DEFAULT 1500 +#define IF_MTU_MIN 68 +#define IF_MTU_MAX 65521 +#define IF_MRU_DEFAULT 1500 +#define IF_MRU_MIN 68 +#define IF_MRU_MAX 65521 +#define IF_COMP IF_AUTOCOMP /* Flags for compression */ + +/* 2 for alignment, 14 for ethernet */ +#define IF_MAXLINKHDR (2 + ETH_HLEN) + +#endif diff --git a/app/src/main/cpp/libslirp/src/ip.h b/app/src/main/cpp/libslirp/src/ip.h new file mode 100644 index 00000000..1387a2fa --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ip.h @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip.h 8.1 (Berkeley) 6/10/93 + * ip.h,v 1.3 1994/08/21 05:27:30 paul Exp + */ + +#ifndef IP_H +#define IP_H + +//#include + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#undef NTOHL +#undef NTOHS +#undef HTONL +#undef HTONS +#define NTOHL(d) +#define NTOHS(d) +#define HTONL(d) +#define HTONS(d) +#else +#ifndef NTOHL +#define NTOHL(d) ((d) = ntohl((d))) +#endif +#ifndef NTOHS +#define NTOHS(d) ((d) = ntohs((uint16_t)(d))) +#endif +#ifndef HTONL +#define HTONL(d) ((d) = htonl((d))) +#endif +#ifndef HTONS +#define HTONS(d) ((d) = htons((uint16_t)(d))) +#endif +#endif + +typedef uint32_t n_long; /* long as received from the net */ + +/* + * Definitions for internet protocol version 4. + * Per RFC 791, September 1981. + */ +#define IPVERSION 4 + +/* + * Structure of an internet header, naked of options. + */ +SLIRP_PACKED_BEGIN +struct ip { +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t ip_v : 4, /* version */ + ip_hl : 4; /* header length */ +#else + uint8_t ip_hl : 4, /* header length */ + ip_v : 4; /* version */ +#endif + uint8_t ip_tos; /* type of service */ + uint16_t ip_len; /* total length */ + uint16_t ip_id; /* identification */ + uint16_t ip_off; /* fragment offset field */ +#define IP_DF 0x4000 /* don't fragment flag */ +#define IP_MF 0x2000 /* more fragments flag */ +#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ + uint8_t ip_ttl; /* time to live */ + uint8_t ip_p; /* protocol */ + uint16_t ip_sum; /* checksum */ + struct in_addr ip_src, ip_dst; /* source and dest address */ +} SLIRP_PACKED_END; + +#define IP_MAXPACKET 65535 /* maximum packet size */ + +/* + * Definitions for IP type of service (ip_tos) + */ +#define IPTOS_LOWDELAY 0x10 +#define IPTOS_THROUGHPUT 0x08 +#define IPTOS_RELIABILITY 0x04 + +/* + * Definitions for options. + */ +#define IPOPT_COPIED(o) ((o)&0x80) +#define IPOPT_CLASS(o) ((o)&0x60) +#define IPOPT_NUMBER(o) ((o)&0x1f) + +#define IPOPT_CONTROL 0x00 +#define IPOPT_RESERVED1 0x20 +#define IPOPT_DEBMEAS 0x40 +#define IPOPT_RESERVED2 0x60 + +#define IPOPT_EOL 0 /* end of option list */ +#define IPOPT_NOP 1 /* no operation */ + +#define IPOPT_RR 7 /* record packet route */ +#define IPOPT_TS 68 /* timestamp */ +#define IPOPT_SECURITY 130 /* provide s,c,h,tcc */ +#define IPOPT_LSRR 131 /* loose source route */ +#define IPOPT_SATID 136 /* satnet id */ +#define IPOPT_SSRR 137 /* strict source route */ + +/* + * Offsets to fields in options other than EOL and NOP. + */ +#define IPOPT_OPTVAL 0 /* option ID */ +#define IPOPT_OLEN 1 /* option length */ +#define IPOPT_OFFSET 2 /* offset within option */ +#define IPOPT_MINOFF 4 /* min value of above */ + +/* + * Time stamp option structure. + */ +SLIRP_PACKED_BEGIN +struct ip_timestamp { + uint8_t ipt_code; /* IPOPT_TS */ + uint8_t ipt_len; /* size of structure (variable) */ + uint8_t ipt_ptr; /* index of current entry */ +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t ipt_oflw : 4, /* overflow counter */ + ipt_flg : 4; /* flags, see below */ +#else + uint8_t ipt_flg : 4, /* flags, see below */ + ipt_oflw : 4; /* overflow counter */ +#endif + union ipt_timestamp { + n_long ipt_time[1]; + struct ipt_ta { + struct in_addr ipt_addr; + n_long ipt_time; + } ipt_ta[1]; + } ipt_timestamp; +} SLIRP_PACKED_END; + +/* flag bits for ipt_flg */ +#define IPOPT_TS_TSONLY 0 /* timestamps only */ +#define IPOPT_TS_TSANDADDR 1 /* timestamps and addresses */ +#define IPOPT_TS_PRESPEC 3 /* specified modules only */ + +/* bits for security (not byte swapped) */ +#define IPOPT_SECUR_UNCLASS 0x0000 +#define IPOPT_SECUR_CONFID 0xf135 +#define IPOPT_SECUR_EFTO 0x789a +#define IPOPT_SECUR_MMMM 0xbc4d +#define IPOPT_SECUR_RESTR 0xaf13 +#define IPOPT_SECUR_SECRET 0xd788 +#define IPOPT_SECUR_TOPSECRET 0x6bc5 + +/* + * Internet implementation parameters. + */ +#define MAXTTL 255 /* maximum time to live (seconds) */ +#define IPDEFTTL 64 /* default ttl, from RFC 1340 */ +#define IPFRAGTTL 60 /* time to live for frags, slowhz */ +#define IPTTLDEC 1 /* subtracted when forwarding */ + +#define IP_MSS 576 /* default maximum segment size */ + +#if GLIB_SIZEOF_VOID_P == 4 +SLIRP_PACKED_BEGIN +struct mbuf_ptr { + struct mbuf *mptr; + uint32_t dummy; +} SLIRP_PACKED_END; +#else +SLIRP_PACKED_BEGIN +struct mbuf_ptr { + struct mbuf *mptr; +} SLIRP_PACKED_END; +#endif +struct qlink { + void *next, *prev; +}; + +/* + * Overlay for ip header used by other protocols (tcp, udp). + */ +SLIRP_PACKED_BEGIN +struct ipovly { + struct mbuf_ptr ih_mbuf; /* backpointer to mbuf */ + uint8_t ih_x1; /* (unused) */ + uint8_t ih_pr; /* protocol */ + uint16_t ih_len; /* protocol length */ + struct in_addr ih_src; /* source internet address */ + struct in_addr ih_dst; /* destination internet address */ +} SLIRP_PACKED_END; + +/* + * Ip reassembly queue structure. Each fragment + * being reassembled is attached to one of these structures. + * They are timed out after ipq_ttl drops to 0, and may also + * be reclaimed if memory becomes tight. + */ +struct ipq { + struct qlink ip_link; /* to other reass headers */ + uint8_t ipq_ttl; /* time for reass q to live */ + uint8_t ipq_p; /* protocol of this fragment */ + uint16_t ipq_id; /* sequence id for reassembly */ + struct in_addr ipq_src, ipq_dst; +}; + +struct ipas { + struct qlink link; + union { + struct ipq ipq; + struct ip ipf_ip; + }; +}; + +#define ipf_off ipf_ip.ip_off +#define ipf_tos ipf_ip.ip_tos +#define ipf_len ipf_ip.ip_len + +#endif diff --git a/app/src/main/cpp/libslirp/src/ip6.h b/app/src/main/cpp/libslirp/src/ip6.h new file mode 100644 index 00000000..b8877579 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ip6.h @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#ifndef SLIRP_IP6_H +#define SLIRP_IP6_H + +//#include +#include + +#include "util.h" + +#define ALLNODES_MULTICAST \ + { \ + .s6_addr = { \ + 0xff, \ + 0x02, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x01 \ + } \ + } + +#define SOLICITED_NODE_PREFIX \ + { \ + .s6_addr = { \ + 0xff, \ + 0x02, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x01, \ + 0xff, \ + 0x00, \ + 0x00, \ + 0x00 \ + } \ + } + +#define LINKLOCAL_ADDR \ + { \ + .s6_addr = { \ + 0xfe, \ + 0x80, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x02 \ + } \ + } + +#define ZERO_ADDR \ + { \ + .s6_addr = { \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00 \ + } \ + } + +/* Check that two IPv6 addresses are equal */ +static inline bool in6_equal(const struct in6_addr *a, const struct in6_addr *b) +{ + return memcmp(a, b, sizeof(*a)) == 0; +} + +/* Check that two IPv6 addresses are equal in their network part */ +static inline bool in6_equal_net(const struct in6_addr *a, + const struct in6_addr *b, int prefix_len) +{ + if (memcmp(a, b, prefix_len / 8) != 0) { + return 0; + } + + if (prefix_len % 8 == 0) { + return 1; + } + + return a->s6_addr[prefix_len / 8] >> (8 - (prefix_len % 8)) == + b->s6_addr[prefix_len / 8] >> (8 - (prefix_len % 8)); +} + +/* Check that two IPv6 addresses are equal in their machine part */ +static inline bool in6_equal_mach(const struct in6_addr *a, + const struct in6_addr *b, int prefix_len) +{ + if (memcmp(&(a->s6_addr[DIV_ROUND_UP(prefix_len, 8)]), + &(b->s6_addr[DIV_ROUND_UP(prefix_len, 8)]), + 16 - DIV_ROUND_UP(prefix_len, 8)) != 0) { + return 0; + } + + if (prefix_len % 8 == 0) { + return 1; + } + + return (a->s6_addr[prefix_len / 8] & + ((1U << (8 - (prefix_len % 8))) - 1)) == + (b->s6_addr[prefix_len / 8] & ((1U << (8 - (prefix_len % 8))) - 1)); +} + +/* Check that the IPv6 is equal to the virtual router */ +#define in6_equal_router(a) \ + ((in6_equal_net(a, &slirp->vprefix_addr6, slirp->vprefix_len) && \ + in6_equal_mach(a, &slirp->vhost_addr6, slirp->vprefix_len)) || \ + (in6_equal_net(a, &(struct in6_addr)LINKLOCAL_ADDR, 64) && \ + in6_equal_mach(a, &slirp->vhost_addr6, 64))) + +/* Check that the IPv6 is equal to the virtual DNS server */ +#define in6_equal_dns(a) \ + ((in6_equal_net(a, &slirp->vprefix_addr6, slirp->vprefix_len) && \ + in6_equal_mach(a, &slirp->vnameserver_addr6, slirp->vprefix_len)) || \ + (in6_equal_net(a, &(struct in6_addr)LINKLOCAL_ADDR, 64) && \ + in6_equal_mach(a, &slirp->vnameserver_addr6, 64))) + +/* Check that the IPv6 is equal to the host */ +#define in6_equal_host(a) (in6_equal_router(a) || in6_equal_dns(a)) + +/* Check that the IPv6 is within the sollicited node multicast network */ +#define in6_solicitednode_multicast(a) \ + (in6_equal_net(a, &(struct in6_addr)SOLICITED_NODE_PREFIX, 104)) + +/* Check that the IPv6 is zero */ +#define in6_zero(a) (in6_equal(a, &(struct in6_addr)ZERO_ADDR)) + +/* Compute emulated host MAC address from its ipv6 address */ +static inline void in6_compute_ethaddr(struct in6_addr ip, + uint8_t eth[ETH_ALEN]) +{ + eth[0] = 0x52; + eth[1] = 0x56; + memcpy(ð[2], &ip.s6_addr[16 - (ETH_ALEN - 2)], ETH_ALEN - 2); +} + +/* + * Definitions for internet protocol version 6. + * Per RFC 2460, December 1998. + */ +#define IP6VERSION 6 +#define IP6_HOP_LIMIT 255 + +/* + * Structure of an internet header, naked of options. + */ +struct ip6 { +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t ip_v : 4, /* version */ + ip_tc_hi : 4; /* traffic class */ + uint8_t ip_tc_lo : 4, ip_fl_hi : 4; /* flow label */ +#else + uint8_t ip_tc_hi : 4, ip_v : 4; + uint8_t ip_fl_hi : 4, ip_tc_lo : 4; +#endif + uint16_t ip_fl_lo; + uint16_t ip_pl; /* payload length */ + uint8_t ip_nh; /* next header */ + uint8_t ip_hl; /* hop limit */ + struct in6_addr ip_src, ip_dst; /* source and dest address */ +}; + +/* + * IPv6 pseudo-header used by upper-layer protocols + */ +struct ip6_pseudohdr { + struct in6_addr ih_src; /* source internet address */ + struct in6_addr ih_dst; /* destination internet address */ + uint32_t ih_pl; /* upper-layer packet length */ + uint16_t ih_zero_hi; /* zero */ + uint8_t ih_zero_lo; /* zero */ + uint8_t ih_nh; /* next header */ +}; + +/* + * We don't want to mark these ip6 structs as packed as they are naturally + * correctly aligned; instead assert that there is no stray padding. + * If we marked the struct as packed then we would be unable to take + * the address of any of the fields in it. + */ +G_STATIC_ASSERT(sizeof(struct ip6) == 40); +G_STATIC_ASSERT(sizeof(struct ip6_pseudohdr) == 40); + +#endif diff --git a/app/src/main/cpp/libslirp/src/ip6_icmp.c b/app/src/main/cpp/libslirp/src/ip6_icmp.c new file mode 100644 index 00000000..3a5878fd --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ip6_icmp.c @@ -0,0 +1,652 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#include "slirp.h" +#include "ip6_icmp.h" + +#define NDP_Interval \ + g_rand_int_range(slirp->grand, NDP_MinRtrAdvInterval, NDP_MaxRtrAdvInterval) + +/* The message sent when emulating PING */ +/* Be nice and tell them it's just a pseudo-ping packet */ +static const char icmp6_ping_msg[] = + "This is a pseudo-PING packet used by Slirp to emulate ICMPV6 ECHO-REQUEST " + "packets.\n"; + +void icmp6_post_init(Slirp *slirp) +{ + if (!slirp->in6_enabled) { + return; + } + + slirp->ra_timer = + slirp_timer_new(slirp, SLIRP_TIMER_RA, NULL); + slirp->cb->timer_mod(slirp->ra_timer, + slirp->cb->clock_get_ns(slirp->opaque) / SCALE_MS + + NDP_Interval, + slirp->opaque); +} + +void icmp6_cleanup(Slirp *slirp) +{ + if (!slirp->in6_enabled) { + return; + } + + slirp->cb->timer_free(slirp->ra_timer, slirp->opaque); +} + +/* Send ICMP packet to the Internet, and save it to so_m */ +static int icmp6_send(struct socket *so, struct mbuf *m, int hlen) +{ + Slirp *slirp = m->slirp; + + struct sockaddr_in6 addr; + + /* + * The behavior of reading SOCK_DGRAM+IPPROTO_ICMP sockets is inconsistent + * between host OSes. On Linux, only the ICMP header and payload is + * included. On macOS/Darwin, the socket acts like a raw socket and + * includes the IP header as well. On other BSDs, SOCK_DGRAM+IPPROTO_ICMP + * sockets aren't supported at all, so we treat them like raw sockets. It + * isn't possible to detect this difference at runtime, so we must use an + * #ifdef to determine if we need to remove the IP header. + */ +#if defined(BSD) && !defined(__GNU__) + so->so_type = IPPROTO_IPV6; +#else + so->so_type = IPPROTO_ICMPV6; +#endif + + so->s = slirp_socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); + if (so->s == -1) { + if (errno == EAFNOSUPPORT + || errno == EPROTONOSUPPORT + || errno == EACCES) { + /* Kernel doesn't support or allow ping sockets. */ + so->so_type = IPPROTO_IPV6; + so->s = slirp_socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + } + } + if (so->s == -1) { + return -1; + } + so->slirp->cb->register_poll_fd(so->s, so->slirp->opaque); + + if (slirp_bind_outbound(so, AF_INET6) != 0) { + // bind failed - close socket + closesocket(so->s); + so->s = -1; + return -1; + } + + M_DUP_DEBUG(slirp, m, 0, 0); + struct ip6 *ip = mtod(m, struct ip6 *); + + so->so_m = m; + so->so_faddr6 = ip->ip_dst; + so->so_laddr6 = ip->ip_src; + so->so_state = SS_ISFCONNECTED; + so->so_expire = curtime + SO_EXPIRE; + + addr.sin6_family = AF_INET6; + addr.sin6_addr = so->so_faddr6; + + slirp_insque(so, &so->slirp->icmp); + + if (sendto(so->s, m->m_data + hlen, m->m_len - hlen, 0, + (struct sockaddr *)&addr, sizeof(addr)) == -1) { + DEBUG_MISC("icmp6_input icmp sendto tx errno = %d-%s", errno, + strerror(errno)); + icmp6_send_error(m, ICMP6_UNREACH, ICMP6_UNREACH_NO_ROUTE); + icmp_detach(so); + } + + return 0; +} + +static void icmp6_send_echoreply(struct mbuf *m, Slirp *slirp, struct ip6 *ip, + struct icmp6 *icmp) +{ + struct mbuf *t = m_get(slirp); + t->m_len = sizeof(struct ip6) + ntohs(ip->ip_pl); + memcpy(t->m_data, m->m_data, t->m_len); + + /* IPv6 Packet */ + struct ip6 *rip = mtod(t, struct ip6 *); + rip->ip_dst = ip->ip_src; + rip->ip_src = ip->ip_dst; + + /* ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = ICMP6_ECHO_REPLY; + ricmp->icmp6_cksum = 0; + + /* Checksum */ + t->m_data -= sizeof(struct ip6); + ricmp->icmp6_cksum = ip6_cksum(t); + + ip6_output(NULL, t, 0); +} + +void icmp6_forward_error(struct mbuf *m, uint8_t type, uint8_t code, struct in6_addr *src) +{ + Slirp *slirp = m->slirp; + struct mbuf *t; + struct ip6 *ip = mtod(m, struct ip6 *); + char addrstr[INET6_ADDRSTRLEN]; + + DEBUG_CALL("icmp6_send_error"); + DEBUG_ARG("type = %d, code = %d", type, code); + + if (IN6_IS_ADDR_MULTICAST(&ip->ip_src) || in6_zero(&ip->ip_src)) { + /* TODO icmp error? */ + return; + } + + t = m_get(slirp); + + /* IPv6 packet */ + struct ip6 *rip = mtod(t, struct ip6 *); + rip->ip_src = *src; + rip->ip_dst = ip->ip_src; + inet_ntop(AF_INET6, &rip->ip_dst, addrstr, INET6_ADDRSTRLEN); + DEBUG_ARG("target = %s", addrstr); + + rip->ip_nh = IPPROTO_ICMPV6; + const int error_data_len = MIN( + m->m_len, slirp->if_mtu - (sizeof(struct ip6) + ICMP6_ERROR_MINLEN)); + rip->ip_pl = htons(ICMP6_ERROR_MINLEN + error_data_len); + t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl); + + /* ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = type; + ricmp->icmp6_code = code; + ricmp->icmp6_cksum = 0; + + switch (type) { + case ICMP6_UNREACH: + case ICMP6_TIMXCEED: + ricmp->icmp6_err.unused = 0; + break; + case ICMP6_TOOBIG: + ricmp->icmp6_err.mtu = htonl(slirp->if_mtu); + break; + case ICMP6_PARAMPROB: + /* TODO: Handle this case */ + break; + default: + g_assert_not_reached(); + } + t->m_data += ICMP6_ERROR_MINLEN; + memcpy(t->m_data, m->m_data, error_data_len); + + /* Checksum */ + t->m_data -= ICMP6_ERROR_MINLEN; + t->m_data -= sizeof(struct ip6); + ricmp->icmp6_cksum = ip6_cksum(t); + + ip6_output(NULL, t, 0); +} + +void icmp6_send_error(struct mbuf *m, uint8_t type, uint8_t code) +{ + struct in6_addr src = LINKLOCAL_ADDR; + icmp6_forward_error(m, type, code, &src); +} + +/* + * Reflect the ip packet back to the source + */ +void icmp6_reflect(struct mbuf *m) +{ + register struct ip6 *ip = mtod(m, struct ip6 *); + int hlen = sizeof(struct ip6); + register struct icmp6 *icp; + + /* + * Send an icmp packet back to the ip level, + * after supplying a checksum. + */ + m->m_data += hlen; + m->m_len -= hlen; + icp = mtod(m, struct icmp6 *); + + icp->icmp6_type = ICMP6_ECHO_REPLY; + + m->m_data -= hlen; + m->m_len += hlen; + + icp->icmp6_cksum = 0; + icp->icmp6_cksum = ip6_cksum(m); + + ip->ip_hl = MAXTTL; + { /* swap */ + struct in6_addr icmp_dst; + icmp_dst = ip->ip_dst; + ip->ip_dst = ip->ip_src; + ip->ip_src = icmp_dst; + } + + ip6_output((struct socket *)NULL, m, 0); +} + +void icmp6_receive(struct socket *so) +{ + struct mbuf *m = so->so_m; + int hlen = sizeof(struct ip6); + uint8_t error_code; + struct icmp6 *icp; + int id, seq, len; + + m->m_data += hlen; + m->m_len -= hlen; + icp = mtod(m, struct icmp6 *); + + id = icp->icmp6_id; + seq = icp->icmp6_seq; + len = recv(so->s, icp, M_ROOM(m), 0); + + icp->icmp6_id = id; + icp->icmp6_seq = seq; + + m->m_data -= hlen; + m->m_len += hlen; + + if (len == -1 || len == 0) { + if (errno == ENETUNREACH) { + error_code = ICMP6_UNREACH_NO_ROUTE; + } else { + error_code = ICMP6_UNREACH_ADDRESS; + } + DEBUG_MISC(" udp icmp rx errno = %d-%s", errno, strerror(errno)); + icmp6_send_error(so->so_m, ICMP_UNREACH, error_code); + } else { + icmp6_reflect(so->so_m); + so->so_m = NULL; /* Don't m_free() it again! */ + } + icmp_detach(so); +} + +/* + * Send NDP Router Advertisement + */ +static void ndp_send_ra(Slirp *slirp) +{ + DEBUG_CALL("ndp_send_ra"); + + /* Build IPv6 packet */ + struct mbuf *t = m_get(slirp); + struct ip6 *rip = mtod(t, struct ip6 *); + size_t pl_size = 0; + struct in6_addr addr; + uint32_t scope_id; + + rip->ip_src = (struct in6_addr)LINKLOCAL_ADDR; + rip->ip_dst = (struct in6_addr)ALLNODES_MULTICAST; + rip->ip_nh = IPPROTO_ICMPV6; + + /* Build ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = ICMP6_NDP_RA; + ricmp->icmp6_code = 0; + ricmp->icmp6_cksum = 0; + + /* NDP */ + ricmp->icmp6_nra.chl = NDP_AdvCurHopLimit; + ricmp->icmp6_nra.M = NDP_AdvManagedFlag; + ricmp->icmp6_nra.O = NDP_AdvOtherConfigFlag; + ricmp->icmp6_nra.reserved = 0; + ricmp->icmp6_nra.lifetime = htons(NDP_AdvDefaultLifetime); + ricmp->icmp6_nra.reach_time = htonl(NDP_AdvReachableTime); + ricmp->icmp6_nra.retrans_time = htonl(NDP_AdvRetransTime); + t->m_data += ICMP6_NDP_RA_MINLEN; + pl_size += ICMP6_NDP_RA_MINLEN; + + /* Source link-layer address (NDP option) */ + struct ndpopt *opt = mtod(t, struct ndpopt *); + opt->ndpopt_type = NDPOPT_LINKLAYER_SOURCE; + opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8; + in6_compute_ethaddr(rip->ip_src, opt->ndpopt_linklayer); + t->m_data += NDPOPT_LINKLAYER_LEN; + pl_size += NDPOPT_LINKLAYER_LEN; + + /* Prefix information (NDP option) */ + struct ndpopt *opt2 = mtod(t, struct ndpopt *); + opt2->ndpopt_type = NDPOPT_PREFIX_INFO; + opt2->ndpopt_len = NDPOPT_PREFIXINFO_LEN / 8; + opt2->ndpopt_prefixinfo.prefix_length = slirp->vprefix_len; + opt2->ndpopt_prefixinfo.L = 1; + opt2->ndpopt_prefixinfo.A = 1; + opt2->ndpopt_prefixinfo.reserved1 = 0; + opt2->ndpopt_prefixinfo.valid_lt = htonl(NDP_AdvValidLifetime); + opt2->ndpopt_prefixinfo.pref_lt = htonl(NDP_AdvPrefLifetime); + opt2->ndpopt_prefixinfo.reserved2 = 0; + opt2->ndpopt_prefixinfo.prefix = slirp->vprefix_addr6; + t->m_data += NDPOPT_PREFIXINFO_LEN; + pl_size += NDPOPT_PREFIXINFO_LEN; + + /* Prefix information (NDP option) */ + if (get_dns6_addr(&addr, &scope_id) >= 0) { + /* Host system does have an IPv6 DNS server, announce our proxy. */ + struct ndpopt *opt3 = mtod(t, struct ndpopt *); + opt3->ndpopt_type = NDPOPT_RDNSS; + opt3->ndpopt_len = NDPOPT_RDNSS_LEN / 8; + opt3->ndpopt_rdnss.reserved = 0; + opt3->ndpopt_rdnss.lifetime = htonl(2 * NDP_MaxRtrAdvInterval); + opt3->ndpopt_rdnss.addr = slirp->vnameserver_addr6; + t->m_data += NDPOPT_RDNSS_LEN; + pl_size += NDPOPT_RDNSS_LEN; + } + + rip->ip_pl = htons(pl_size); + t->m_data -= sizeof(struct ip6) + pl_size; + t->m_len = sizeof(struct ip6) + pl_size; + + /* ICMPv6 Checksum */ + ricmp->icmp6_cksum = ip6_cksum(t); + + ip6_output(NULL, t, 0); +} + +void ra_timer_handler(Slirp *slirp, void *unused) +{ + slirp->cb->timer_mod(slirp->ra_timer, + slirp->cb->clock_get_ns(slirp->opaque) / SCALE_MS + + NDP_Interval, + slirp->opaque); + ndp_send_ra(slirp); +} + +/* + * Send NDP Neighbor Solitication + */ +void ndp_send_ns(Slirp *slirp, struct in6_addr addr) +{ + char addrstr[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, &addr, addrstr, INET6_ADDRSTRLEN); + + DEBUG_CALL("ndp_send_ns"); + DEBUG_ARG("target = %s", addrstr); + + /* Build IPv6 packet */ + struct mbuf *t = m_get(slirp); + struct ip6 *rip = mtod(t, struct ip6 *); + rip->ip_src = slirp->vhost_addr6; + rip->ip_dst = (struct in6_addr)SOLICITED_NODE_PREFIX; + memcpy(&rip->ip_dst.s6_addr[13], &addr.s6_addr[13], 3); + rip->ip_nh = IPPROTO_ICMPV6; + rip->ip_pl = htons(ICMP6_NDP_NS_MINLEN + NDPOPT_LINKLAYER_LEN); + t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl); + + /* Build ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = ICMP6_NDP_NS; + ricmp->icmp6_code = 0; + ricmp->icmp6_cksum = 0; + + /* NDP */ + ricmp->icmp6_nns.reserved = 0; + ricmp->icmp6_nns.target = addr; + + /* Build NDP option */ + t->m_data += ICMP6_NDP_NS_MINLEN; + struct ndpopt *opt = mtod(t, struct ndpopt *); + opt->ndpopt_type = NDPOPT_LINKLAYER_SOURCE; + opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8; + in6_compute_ethaddr(slirp->vhost_addr6, opt->ndpopt_linklayer); + + /* ICMPv6 Checksum */ + t->m_data -= ICMP6_NDP_NA_MINLEN; + t->m_data -= sizeof(struct ip6); + ricmp->icmp6_cksum = ip6_cksum(t); + + ip6_output(NULL, t, 1); +} + +/* + * Send NDP Neighbor Advertisement + */ +static void ndp_send_na(Slirp *slirp, struct ip6 *ip, struct icmp6 *icmp) +{ + /* Build IPv6 packet */ + struct mbuf *t = m_get(slirp); + struct ip6 *rip = mtod(t, struct ip6 *); + rip->ip_src = icmp->icmp6_nns.target; + if (in6_zero(&ip->ip_src)) { + rip->ip_dst = (struct in6_addr)ALLNODES_MULTICAST; + } else { + rip->ip_dst = ip->ip_src; + } + rip->ip_nh = IPPROTO_ICMPV6; + rip->ip_pl = htons(ICMP6_NDP_NA_MINLEN + NDPOPT_LINKLAYER_LEN); + t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl); + + /* Build ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = ICMP6_NDP_NA; + ricmp->icmp6_code = 0; + ricmp->icmp6_cksum = 0; + + /* NDP */ + ricmp->icmp6_nna.R = NDP_IsRouter; + ricmp->icmp6_nna.S = !IN6_IS_ADDR_MULTICAST(&rip->ip_dst); + ricmp->icmp6_nna.O = 1; + ricmp->icmp6_nna.reserved_1 = 0; + ricmp->icmp6_nna.reserved_2 = 0; + ricmp->icmp6_nna.reserved_3 = 0; + ricmp->icmp6_nna.target = icmp->icmp6_nns.target; + + /* Build NDP option */ + t->m_data += ICMP6_NDP_NA_MINLEN; + struct ndpopt *opt = mtod(t, struct ndpopt *); + opt->ndpopt_type = NDPOPT_LINKLAYER_TARGET; + opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8; + in6_compute_ethaddr(ricmp->icmp6_nna.target, opt->ndpopt_linklayer); + + /* ICMPv6 Checksum */ + t->m_data -= ICMP6_NDP_NA_MINLEN; + t->m_data -= sizeof(struct ip6); + ricmp->icmp6_cksum = ip6_cksum(t); + + ip6_output(NULL, t, 0); +} + +/* + * Process a NDP message + */ +static void ndp_input(struct mbuf *m, Slirp *slirp, struct ip6 *ip, + struct icmp6 *icmp) +{ + g_assert(M_ROOMBEFORE(m) >= ETH_HLEN); + + m->m_len += ETH_HLEN; + m->m_data -= ETH_HLEN; + struct ethhdr *eth = mtod(m, struct ethhdr *); + m->m_len -= ETH_HLEN; + m->m_data += ETH_HLEN; + + switch (icmp->icmp6_type) { + case ICMP6_NDP_RS: + DEBUG_CALL(" type = Router Solicitation"); + if (ip->ip_hl == 255 && icmp->icmp6_code == 0 && + ntohs(ip->ip_pl) >= ICMP6_NDP_RS_MINLEN) { + /* Gratuitous NDP */ + ndp_table_add(slirp, ip->ip_src, eth->h_source); + + ndp_send_ra(slirp); + } + break; + + case ICMP6_NDP_RA: + DEBUG_CALL(" type = Router Advertisement"); + slirp->cb->guest_error("Warning: guest sent NDP RA, but shouldn't", + slirp->opaque); + break; + + case ICMP6_NDP_NS: + DEBUG_CALL(" type = Neighbor Solicitation"); + if (ip->ip_hl == 255 && icmp->icmp6_code == 0 && + !IN6_IS_ADDR_MULTICAST(&icmp->icmp6_nns.target) && + ntohs(ip->ip_pl) >= ICMP6_NDP_NS_MINLEN && + (!in6_zero(&ip->ip_src) || + in6_solicitednode_multicast(&ip->ip_dst))) { + if (in6_equal_host(&icmp->icmp6_nns.target)) { + /* Gratuitous NDP */ + ndp_table_add(slirp, ip->ip_src, eth->h_source); + ndp_send_na(slirp, ip, icmp); + } + } + break; + + case ICMP6_NDP_NA: + DEBUG_CALL(" type = Neighbor Advertisement"); + if (ip->ip_hl == 255 && icmp->icmp6_code == 0 && + ntohs(ip->ip_pl) >= ICMP6_NDP_NA_MINLEN && + !IN6_IS_ADDR_MULTICAST(&icmp->icmp6_nna.target) && + (!IN6_IS_ADDR_MULTICAST(&ip->ip_dst) || icmp->icmp6_nna.S == 0)) { + ndp_table_add(slirp, icmp->icmp6_nna.target, eth->h_source); + } + break; + + case ICMP6_NDP_REDIRECT: + DEBUG_CALL(" type = Redirect"); + slirp->cb->guest_error( + "Warning: guest sent NDP REDIRECT, but shouldn't", slirp->opaque); + break; + } +} + +/* + * Process a received ICMPv6 message. + */ +void icmp6_input(struct mbuf *m) +{ + Slirp *slirp = m->slirp; + /* NDP reads the ethernet header for gratuitous NDP */ + M_DUP_DEBUG(slirp, m, 1, ETH_HLEN); + + struct icmp6 *icmp; + struct ip6 *ip = mtod(m, struct ip6 *); + int hlen = sizeof(struct ip6); + + DEBUG_CALL("icmp6_input"); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("m_len = %d", m->m_len); + + if (ntohs(ip->ip_pl) < ICMP6_MINLEN) { + freeit: + m_free(m); + goto end_error; + } + + if (ip6_cksum(m)) { + goto freeit; + } + + m->m_len -= hlen; + m->m_data += hlen; + icmp = mtod(m, struct icmp6 *); + m->m_len += hlen; + m->m_data -= hlen; + + DEBUG_ARG("icmp6_type = %d", icmp->icmp6_type); + switch (icmp->icmp6_type) { + case ICMP6_ECHO_REQUEST: + if (in6_equal_host(&ip->ip_dst)) { + icmp6_send_echoreply(m, slirp, ip, icmp); + } else if (slirp->restricted) { + goto freeit; + } else { + struct socket *so; + struct sockaddr_storage addr; + int ttl; + + so = socreate(slirp, IPPROTO_ICMPV6); + if (icmp6_send(so, m, hlen) == 0) { + /* We could send this as ICMP, good! */ + return; + } + + /* We could not send this as ICMP, try to send it on UDP echo + * service (7), wishfully hoping that it is open there. */ + + if (udp_attach(so, AF_INET6) == -1) { + DEBUG_MISC("icmp6_input udp_attach errno = %d-%s", errno, + strerror(errno)); + sofree(so); + m_free(m); + goto end_error; + } + so->so_m = m; + so->so_ffamily = AF_INET6; + so->so_faddr6 = ip->ip_dst; + so->so_fport = htons(7); + so->so_lfamily = AF_INET6; + so->so_laddr6 = ip->ip_src; + so->so_lport = htons(9); + so->so_state = SS_ISFCONNECTED; + + /* Send the packet */ + addr = so->fhost.ss; + if (sotranslate_out(so, &addr) < 0) { + icmp6_send_error(m, ICMP6_UNREACH, ICMP6_UNREACH_NO_ROUTE); + udp_detach(so); + return; + } + + /* + * Check for TTL + */ + ttl = ip->ip_hl-1; + if (ttl <= 0) { + DEBUG_MISC("udp ttl exceeded"); + icmp6_send_error(m, ICMP6_TIMXCEED, ICMP6_TIMXCEED_INTRANS); + udp_detach(so); + break; + } + setsockopt(so->s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + + if (sendto(so->s, icmp6_ping_msg, strlen(icmp6_ping_msg), 0, + (struct sockaddr *)&addr, sockaddr_size(&addr)) == -1) { + DEBUG_MISC("icmp6_input udp sendto tx errno = %d-%s", errno, + strerror(errno)); + icmp6_send_error(m, ICMP6_UNREACH, ICMP6_UNREACH_NO_ROUTE); + udp_detach(so); + } + } /* if (in6_equal_host(&ip->ip_dst)) */ + break; + + case ICMP6_NDP_RS: + case ICMP6_NDP_RA: + case ICMP6_NDP_NS: + case ICMP6_NDP_NA: + case ICMP6_NDP_REDIRECT: + ndp_input(m, slirp, ip, icmp); + m_free(m); + break; + + case ICMP6_UNREACH: + case ICMP6_TOOBIG: + case ICMP6_TIMXCEED: + case ICMP6_PARAMPROB: + /* XXX? report error? close socket? */ + default: + m_free(m); + break; + } + +end_error: + /* m is m_free()'d xor put in a socket xor or given to ip_send */ + return; +} diff --git a/app/src/main/cpp/libslirp/src/ip6_icmp.h b/app/src/main/cpp/libslirp/src/ip6_icmp.h new file mode 100644 index 00000000..7f8bc60b --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ip6_icmp.h @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#ifndef SLIRP_IP6_ICMP_H +#define SLIRP_IP6_ICMP_H + +/* + * Interface Control Message Protocol version 6 Definitions. + * Per RFC 4443, March 2006. + * + * Network Discover Protocol Definitions. + * Per RFC 4861, September 2007. + */ + +struct icmp6_echo { /* Echo Messages */ + uint16_t id; + uint16_t seq_num; +}; + +union icmp6_error_body { + uint32_t unused; + uint32_t pointer; + uint32_t mtu; +}; + +/* + * NDP Messages + */ +struct ndp_rs { /* Router Solicitation Message */ + uint32_t reserved; +}; + +struct ndp_ra { /* Router Advertisement Message */ + uint8_t chl; /* Cur Hop Limit */ +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t M : 1, O : 1, reserved : 6; +#else + uint8_t reserved : 6, O : 1, M : 1; +#endif + uint16_t lifetime; /* Router Lifetime */ + uint32_t reach_time; /* Reachable Time */ + uint32_t retrans_time; /* Retrans Timer */ +}; + +G_STATIC_ASSERT(sizeof(struct ndp_ra) == 12); + +struct ndp_ns { /* Neighbor Solicitation Message */ + uint32_t reserved; + struct in6_addr target; /* Target Address */ +}; + +G_STATIC_ASSERT(sizeof(struct ndp_ns) == 20); + +struct ndp_na { /* Neighbor Advertisement Message */ +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t R : 1, /* Router Flag */ + S : 1, /* Solicited Flag */ + O : 1, /* Override Flag */ + reserved_1 : 5; +#else + uint8_t reserved_1 : 5, O : 1, S : 1, R : 1; +#endif + uint8_t reserved_2; + uint16_t reserved_3; + struct in6_addr target; /* Target Address */ +}; + +G_STATIC_ASSERT(sizeof(struct ndp_na) == 20); + +struct ndp_redirect { + uint32_t reserved; + struct in6_addr target; /* Target Address */ + struct in6_addr dest; /* Destination Address */ +}; + +G_STATIC_ASSERT(sizeof(struct ndp_redirect) == 36); + +/* + * Structure of an icmpv6 header. + */ +struct icmp6 { + uint8_t icmp6_type; /* type of message, see below */ + uint8_t icmp6_code; /* type sub code */ + uint16_t icmp6_cksum; /* ones complement cksum of struct */ + union { + union icmp6_error_body error_body; + struct icmp6_echo echo; + struct ndp_rs ndp_rs; + struct ndp_ra ndp_ra; + struct ndp_ns ndp_ns; + struct ndp_na ndp_na; + struct ndp_redirect ndp_redirect; + } icmp6_body; +#define icmp6_err icmp6_body.error_body +#define icmp6_echo icmp6_body.echo +#define icmp6_id icmp6_body.echo.id +#define icmp6_seq icmp6_body.echo.seq_num +#define icmp6_nrs icmp6_body.ndp_rs +#define icmp6_nra icmp6_body.ndp_ra +#define icmp6_nns icmp6_body.ndp_ns +#define icmp6_nna icmp6_body.ndp_na +#define icmp6_redirect icmp6_body.ndp_redirect +}; + +G_STATIC_ASSERT(sizeof(struct icmp6) == 40); + +#define ICMP6_MINLEN 4 +#define ICMP6_ERROR_MINLEN 8 +#define ICMP6_ECHO_MINLEN 8 +#define ICMP6_NDP_RS_MINLEN 8 +#define ICMP6_NDP_RA_MINLEN 16 +#define ICMP6_NDP_NS_MINLEN 24 +#define ICMP6_NDP_NA_MINLEN 24 +#define ICMP6_NDP_REDIRECT_MINLEN 40 + +/* + * NDP Options + */ +SLIRP_PACKED_BEGIN +struct ndpopt { + uint8_t ndpopt_type; /* Option type */ + uint8_t ndpopt_len; /* /!\ In units of 8 octets */ + union { + unsigned char linklayer_addr[6]; /* Source/Target Link-layer */ +#define ndpopt_linklayer ndpopt_body.linklayer_addr + SLIRP_PACKED_BEGIN + struct prefixinfo { /* Prefix Information */ + uint8_t prefix_length; +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t L : 1, A : 1, reserved1 : 6; +#else + uint8_t reserved1 : 6, A : 1, L : 1; +#endif + uint32_t valid_lt; /* Valid Lifetime */ + uint32_t pref_lt; /* Preferred Lifetime */ + uint32_t reserved2; + struct in6_addr prefix; + } SLIRP_PACKED_END prefixinfo; +#define ndpopt_prefixinfo ndpopt_body.prefixinfo + SLIRP_PACKED_BEGIN + struct rdnss { + uint16_t reserved; + uint32_t lifetime; + struct in6_addr addr; + } SLIRP_PACKED_END rdnss; +#define ndpopt_rdnss ndpopt_body.rdnss + } ndpopt_body; +} SLIRP_PACKED_END; + +/* NDP options type */ +#define NDPOPT_LINKLAYER_SOURCE 1 /* Source Link-Layer Address */ +#define NDPOPT_LINKLAYER_TARGET 2 /* Target Link-Layer Address */ +#define NDPOPT_PREFIX_INFO 3 /* Prefix Information */ +#define NDPOPT_RDNSS 25 /* Recursive DNS Server Address */ + +/* NDP options size, in octets. */ +#define NDPOPT_LINKLAYER_LEN 8 +#define NDPOPT_PREFIXINFO_LEN 32 +#define NDPOPT_RDNSS_LEN 24 + +/* + * Definition of type and code field values. + * Per https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xml + * Last Updated 2012-11-12 + */ + +/* Errors */ +#define ICMP6_UNREACH 1 /* Destination Unreachable */ +#define ICMP6_UNREACH_NO_ROUTE 0 /* no route to dest */ +#define ICMP6_UNREACH_DEST_PROHIB 1 /* com with dest prohibited */ +#define ICMP6_UNREACH_SCOPE 2 /* beyond scope of src addr */ +#define ICMP6_UNREACH_ADDRESS 3 /* address unreachable */ +#define ICMP6_UNREACH_PORT 4 /* port unreachable */ +#define ICMP6_UNREACH_SRC_FAIL 5 /* src addr failed */ +#define ICMP6_UNREACH_REJECT_ROUTE 6 /* reject route to dest */ +#define ICMP6_UNREACH_SRC_HDR_ERROR 7 /* error in src routing header */ +#define ICMP6_TOOBIG 2 /* Packet Too Big */ +#define ICMP6_TIMXCEED 3 /* Time Exceeded */ +#define ICMP6_TIMXCEED_INTRANS 0 /* hop limit exceeded in transit */ +#define ICMP6_TIMXCEED_REASS 1 /* ttl=0 in reass */ +#define ICMP6_PARAMPROB 4 /* Parameter Problem */ +#define ICMP6_PARAMPROB_HDR_FIELD 0 /* err header field */ +#define ICMP6_PARAMPROB_NXTHDR_TYPE 1 /* unrecognized Next Header type */ +#define ICMP6_PARAMPROB_IPV6_OPT 2 /* unrecognized IPv6 option */ + +/* Informational Messages */ +#define ICMP6_ECHO_REQUEST 128 /* Echo Request */ +#define ICMP6_ECHO_REPLY 129 /* Echo Reply */ +#define ICMP6_NDP_RS 133 /* Router Solicitation (NDP) */ +#define ICMP6_NDP_RA 134 /* Router Advertisement (NDP) */ +#define ICMP6_NDP_NS 135 /* Neighbor Solicitation (NDP) */ +#define ICMP6_NDP_NA 136 /* Neighbor Advertisement (NDP) */ +#define ICMP6_NDP_REDIRECT 137 /* Redirect Message (NDP) */ + +/* + * Router Configuration Variables (rfc4861#section-6) + */ +#define NDP_IsRouter 1 +#define NDP_AdvSendAdvertisements 1 +#define NDP_MaxRtrAdvInterval 600000 +#define NDP_MinRtrAdvInterval \ + ((NDP_MaxRtrAdvInterval >= 9) ? NDP_MaxRtrAdvInterval / 3 : \ + NDP_MaxRtrAdvInterval) +#define NDP_AdvManagedFlag 0 +#define NDP_AdvOtherConfigFlag 0 +#define NDP_AdvLinkMTU 0 +#define NDP_AdvReachableTime 0 +#define NDP_AdvRetransTime 0 +#define NDP_AdvCurHopLimit 64 +#define NDP_AdvDefaultLifetime ((3 * NDP_MaxRtrAdvInterval) / 1000) +#define NDP_AdvValidLifetime 86400 +#define NDP_AdvOnLinkFlag 1 +#define NDP_AdvPrefLifetime 14400 +#define NDP_AdvAutonomousFlag 1 + +/* Called from slirp_new, but after other initialization */ +void icmp6_post_init(Slirp *slirp); + +/* Called from slirp_cleanup */ +void icmp6_cleanup(Slirp *slirp); + +/* Process an ICMPv6 packet from the guest */ +void icmp6_input(struct mbuf *); + +/* Send an ICMPv6 error related to the given packet, using the given ICMPv6 type and code, using the given source */ +void icmp6_forward_error(struct mbuf *m, uint8_t type, uint8_t code, struct in6_addr *src); + +/* Similar to icmp6_forward_error, but use the link-local address as source */ +void icmp6_send_error(struct mbuf *m, uint8_t type, uint8_t code); + +/* Forward the ICMP packet to the guest (probably a ping reply) */ +void icmp6_reflect(struct mbuf *); + +/* Handle ICMP data from the ICMP socket, and forward it to the guest (using so_m as reference) */ +void icmp6_receive(struct socket *so); + +/* Send a neighbour sollicitation, to resolve the given IPV6 address */ +void ndp_send_ns(Slirp *slirp, struct in6_addr addr); + +/* Timer handler for router advertisement, to send it and reschedule the timer */ +void ra_timer_handler(Slirp *slirp, void *unused); + +#endif diff --git a/app/src/main/cpp/libslirp/src/ip6_input.c b/app/src/main/cpp/libslirp/src/ip6_input.c new file mode 100644 index 00000000..4aca0828 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ip6_input.c @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#include "slirp.h" +#include "ip6_icmp.h" + +/* + * IP initialization: fill in IP protocol switch table. + * All protocols not implemented in kernel go to raw IP protocol handler. + */ +void ip6_post_init(Slirp *slirp) +{ + icmp6_post_init(slirp); +} + +void ip6_cleanup(Slirp *slirp) +{ + icmp6_cleanup(slirp); +} + +void ip6_input(struct mbuf *m) +{ + Slirp *slirp = m->slirp; + /* NDP reads the ethernet header for gratuitous NDP */ + M_DUP_DEBUG(slirp, m, 1, TCPIPHDR_DELTA + 2 + ETH_HLEN); + + struct ip6 *ip6; + + if (!slirp->in6_enabled) { + goto bad; + } + + DEBUG_CALL("ip6_input"); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("m_len = %d", m->m_len); + + if (m->m_len < sizeof(struct ip6)) { + goto bad; + } + + ip6 = mtod(m, struct ip6 *); + + if (ip6->ip_v != IP6VERSION) { + goto bad; + } + + if (ntohs(ip6->ip_pl) + sizeof(struct ip6) > slirp->if_mtu) { + icmp6_send_error(m, ICMP6_TOOBIG, 0); + goto bad; + } + + // Check if the message size is big enough to hold what's + // set in the payload length header. If not this is an invalid + // packet + if (m->m_len < ntohs(ip6->ip_pl) + sizeof(struct ip6)) { + goto bad; + } + + /* check ip_ttl for a correct ICMP reply */ + if (ip6->ip_hl == 0) { + icmp6_send_error(m, ICMP6_TIMXCEED, ICMP6_TIMXCEED_INTRANS); + goto bad; + } + + /* + * Switch out to protocol's input routine. + */ + switch (ip6->ip_nh) { + case IPPROTO_TCP: + NTOHS(ip6->ip_pl); + tcp_input(m, sizeof(struct ip6), (struct socket *)NULL, AF_INET6); + break; + case IPPROTO_UDP: + udp6_input(m); + break; + case IPPROTO_ICMPV6: + icmp6_input(m); + break; + default: + m_free(m); + } + return; +bad: + m_free(m); +} diff --git a/app/src/main/cpp/libslirp/src/ip6_output.c b/app/src/main/cpp/libslirp/src/ip6_output.c new file mode 100644 index 00000000..834f1c0a --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ip6_output.c @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#include "slirp.h" + +/* Number of packets queued before we start sending + * (to prevent allocing too many mbufs) */ +#define IF6_THRESH 10 + +/* + * IPv6 output. The packet in mbuf chain m contains a IP header + */ +int ip6_output(struct socket *so, struct mbuf *m, int fast) +{ + Slirp *slirp = m->slirp; + M_DUP_DEBUG(slirp, m, 0, 0); + + struct ip6 *ip = mtod(m, struct ip6 *); + + DEBUG_CALL("ip6_output"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m = %p", m); + + /* Fill IPv6 header */ + ip->ip_v = IP6VERSION; + ip->ip_hl = IP6_HOP_LIMIT; + ip->ip_tc_hi = 0; + ip->ip_tc_lo = 0; + ip->ip_fl_hi = 0; + ip->ip_fl_lo = 0; + + if (fast) { + /* We cannot fast-send non-multicast, we'd need a NDP NS */ + assert(IN6_IS_ADDR_MULTICAST(&ip->ip_dst)); + if_encap(m->slirp, m); + m_free(m); + } else { + if_output(so, m); + } + + return 0; +} diff --git a/app/src/main/cpp/libslirp/src/ip_icmp.c b/app/src/main/cpp/libslirp/src/ip_icmp.c new file mode 100644 index 00000000..74524fd3 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ip_icmp.c @@ -0,0 +1,547 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_icmp.c 8.2 (Berkeley) 1/4/94 + * ip_icmp.c,v 1.7 1995/05/30 08:09:42 rgrimes Exp + */ + +#include "slirp.h" +#include "ip_icmp.h" + +#ifndef _WIN32 +#include +#endif + +#ifndef WITH_ICMP_ERROR_MSG +#define WITH_ICMP_ERROR_MSG 0 +#endif + +/* The message sent when emulating PING */ +/* Be nice and tell them it's just a pseudo-ping packet */ +static const char icmp_ping_msg[] = + "This is a pseudo-PING packet used by Slirp to emulate ICMP ECHO-REQUEST " + "packets.\n"; + +/* list of actions for icmp_send_error() on RX of an icmp message */ +static const int icmp_flush[19] = { + /* ECHO REPLY (0) */ 0, + 1, + 1, + /* DEST UNREACH (3) */ 1, + /* SOURCE QUENCH (4)*/ 1, + /* REDIRECT (5) */ 1, + 1, + 1, + /* ECHO (8) */ 0, + /* ROUTERADVERT (9) */ 1, + /* ROUTERSOLICIT (10) */ 1, + /* TIME EXCEEDED (11) */ 1, + /* PARAMETER PROBLEM (12) */ 1, + /* TIMESTAMP (13) */ 0, + /* TIMESTAMP REPLY (14) */ 0, + /* INFO (15) */ 0, + /* INFO REPLY (16) */ 0, + /* ADDR MASK (17) */ 0, + /* ADDR MASK REPLY (18) */ 0 +}; + +void icmp_init(Slirp *slirp) +{ + slirp->icmp.so_next = slirp->icmp.so_prev = &slirp->icmp; + slirp->icmp_last_so = &slirp->icmp; +} + +void icmp_cleanup(Slirp *slirp) +{ + struct socket *so, *so_next; + + for (so = slirp->icmp.so_next; so != &slirp->icmp; so = so_next) { + so_next = so->so_next; + icmp_detach(so); + } +} + +/* Send ICMP packet to the Internet, and save it to so_m */ +static int icmp_send(struct socket *so, struct mbuf *m, int hlen) +{ + Slirp *slirp = m->slirp; + + struct sockaddr_in addr; + + /* + * The behavior of reading SOCK_DGRAM+IPPROTO_ICMP sockets is inconsistent + * between host OSes. On Linux, only the ICMP header and payload is + * included. On macOS/Darwin, the socket acts like a raw socket and + * includes the IP header as well. On other BSDs, SOCK_DGRAM+IPPROTO_ICMP + * sockets aren't supported at all, so we treat them like raw sockets. It + * isn't possible to detect this difference at runtime, so we must use an + * #ifdef to determine if we need to remove the IP header. + */ +#if defined(BSD) && !defined(__GNU__) + so->so_type = IPPROTO_IP; +#else + so->so_type = IPPROTO_ICMP; +#endif + + so->s = slirp_socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); + if (so->s == -1) { + if (errno == EAFNOSUPPORT + || errno == EPROTONOSUPPORT + || errno == EACCES) { + /* Kernel doesn't support or allow ping sockets. */ + so->so_type = IPPROTO_IP; + so->s = slirp_socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + } + } + if (so->s == -1) { + return -1; + } + so->slirp->cb->register_poll_fd(so->s, so->slirp->opaque); + + if (slirp_bind_outbound(so, AF_INET) != 0) { + // bind failed - close socket + closesocket(so->s); + so->s = -1; + return -1; + } + + M_DUP_DEBUG(slirp, m, 0, 0); + struct ip *ip = mtod(m, struct ip *); + + so->so_m = m; + so->so_faddr = ip->ip_dst; + so->so_laddr = ip->ip_src; + so->so_iptos = ip->ip_tos; + so->so_state = SS_ISFCONNECTED; + so->so_expire = curtime + SO_EXPIRE; + + addr.sin_family = AF_INET; + addr.sin_addr = so->so_faddr; + + slirp_insque(so, &so->slirp->icmp); + + if (sendto(so->s, m->m_data + hlen, m->m_len - hlen, 0, + (struct sockaddr *)&addr, sizeof(addr)) == -1) { + DEBUG_MISC("icmp_input icmp sendto tx errno = %d-%s", errno, + strerror(errno)); + icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, strerror(errno)); + icmp_detach(so); + } + + return 0; +} + +void icmp_detach(struct socket *so) +{ + so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); + closesocket(so->s); + sofree(so); +} + +/* + * Process a received ICMP message. + */ +void icmp_input(struct mbuf *m, int hlen) +{ + Slirp *slirp = m->slirp; + M_DUP_DEBUG(slirp, m, 0, 0); + + register struct icmp *icp; + register struct ip *ip = mtod(m, struct ip *); + int icmplen = ip->ip_len; + + DEBUG_CALL("icmp_input"); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("m_len = %d", m->m_len); + + /* + * Locate icmp structure in mbuf, and check + * that its not corrupted and of at least minimum length. + */ + if (icmplen < ICMP_MINLEN) { /* min 8 bytes payload */ + freeit: + m_free(m); + goto end_error; + } + + m->m_len -= hlen; + m->m_data += hlen; + icp = mtod(m, struct icmp *); + if (cksum(m, icmplen)) { + goto freeit; + } + m->m_len += hlen; + m->m_data -= hlen; + + DEBUG_ARG("icmp_type = %d", icp->icmp_type); + switch (icp->icmp_type) { + case ICMP_ECHO: + ip->ip_len += hlen; /* since ip_input subtracts this */ + if (ip->ip_dst.s_addr == slirp->vhost_addr.s_addr || + ip->ip_dst.s_addr == slirp->vnameserver_addr.s_addr) { + icmp_reflect(m); + } else if (slirp->restricted) { + goto freeit; + } else { + struct socket *so; + struct sockaddr_storage addr; + int ttl; + + so = socreate(slirp, IPPROTO_ICMP); + if (icmp_send(so, m, hlen) == 0) { + /* We could send this as ICMP, good! */ + return; + } + + /* We could not send this as ICMP, try to send it on UDP echo + * service (7), wishfully hoping that it is open there. */ + + if (udp_attach(so, AF_INET) == -1) { + DEBUG_MISC("icmp_input udp_attach errno = %d-%s", errno, + strerror(errno)); + sofree(so); + m_free(m); + goto end_error; + } + so->so_m = m; + so->so_ffamily = AF_INET; + so->so_faddr = ip->ip_dst; + so->so_fport = htons(7); + so->so_lfamily = AF_INET; + so->so_laddr = ip->ip_src; + so->so_lport = htons(9); + so->so_iptos = ip->ip_tos; + so->so_state = SS_ISFCONNECTED; + + /* Send the packet */ + addr = so->fhost.ss; + if (sotranslate_out(so, &addr) < 0) { + icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, + strerror(errno)); + udp_detach(so); + return; + } + + /* + * Check for TTL + */ + ttl = ip->ip_ttl-1; + if (ttl <= 0) { + DEBUG_MISC("udp ttl exceeded"); + icmp_send_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, + NULL); + udp_detach(so); + break; + } + setsockopt(so->s, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + + if (sendto(so->s, icmp_ping_msg, strlen(icmp_ping_msg), 0, + (struct sockaddr *)&addr, sockaddr_size(&addr)) == -1) { + DEBUG_MISC("icmp_input udp sendto tx errno = %d-%s", errno, + strerror(errno)); + icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, + strerror(errno)); + udp_detach(so); + } + } /* if ip->ip_dst.s_addr == alias_addr.s_addr */ + break; + case ICMP_UNREACH: + /* XXX? report error? close socket? */ + case ICMP_TIMXCEED: + case ICMP_PARAMPROB: + case ICMP_SOURCEQUENCH: + case ICMP_TSTAMP: + case ICMP_MASKREQ: + case ICMP_REDIRECT: + m_free(m); + break; + + default: + m_free(m); + } /* switch */ + +end_error: + /* m is m_free()'d xor put in a socket xor or given to ip_send */ + return; +} + + +/* + * Send an ICMP message in response to a situation + * + * RFC 1122: 3.2.2 MUST send at least the IP header and 8 bytes of header. + *MAY send more (we do). MUST NOT change this header information. MUST NOT reply + *to a multicast/broadcast IP address. MUST NOT reply to a multicast/broadcast + *MAC address. MUST reply to only the first fragment. + */ +/* + * Send ICMP_UNREACH back to the source regarding msrc. + * mbuf *msrc is used as a template, but is NOT m_free()'d. + * It is reported as the bad ip packet. The header should + * be fully correct and in host byte order. + * ICMP fragmentation is illegal. All machines must accept 576 bytes in one + * packet. The maximum payload is 576-20(ip hdr)-8(icmp hdr)=548 + */ + +#define ICMP_MAXDATALEN (IP_MSS - 28) +void icmp_forward_error(struct mbuf *msrc, uint8_t type, uint8_t code, int minsize, + const char *message, struct in_addr *src) +{ + unsigned hlen, shlen, s_ip_len; + register struct ip *ip; + register struct icmp *icp; + register struct mbuf *m; + + DEBUG_CALL("icmp_send_error"); + DEBUG_ARG("msrc = %p", msrc); + DEBUG_ARG("msrc_len = %d", msrc->m_len); + + if (type != ICMP_UNREACH && type != ICMP_TIMXCEED) + goto end_error; + + /* check msrc */ + if (!msrc) + goto end_error; + ip = mtod(msrc, struct ip *); + if (slirp_debug & DBG_MISC) { + char addr_src[INET_ADDRSTRLEN]; + char addr_dst[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, &ip->ip_src, addr_src, sizeof(addr_src)); + inet_ntop(AF_INET, &ip->ip_dst, addr_dst, sizeof(addr_dst)); + DEBUG_MISC(" %.16s to %.16s", addr_src, addr_dst); + } + if (ip->ip_off & IP_OFFMASK) + goto end_error; /* Only reply to fragment 0 */ + + /* Do not reply to source-only IPs */ + if ((ip->ip_src.s_addr & htonl(~(0xf << 28))) == 0) { + goto end_error; + } + + shlen = ip->ip_hl << 2; + s_ip_len = ip->ip_len; + if (ip->ip_p == IPPROTO_ICMP) { + icp = (struct icmp *)((char *)ip + shlen); + /* + * Assume any unknown ICMP type is an error. This isn't + * specified by the RFC, but think about it.. + */ + if (icp->icmp_type > 18 || icmp_flush[icp->icmp_type]) + goto end_error; + } + + /* make a copy */ + m = m_get(msrc->slirp); + if (!m) { + goto end_error; + } + + { + int new_m_size; + new_m_size = + sizeof(struct ip) + ICMP_MINLEN + msrc->m_len + ICMP_MAXDATALEN; + if (new_m_size > m->m_size) + m_inc(m, new_m_size); + } + memcpy(m->m_data, msrc->m_data, msrc->m_len); + m->m_len = msrc->m_len; /* copy msrc to m */ + + /* make the header of the reply packet */ + ip = mtod(m, struct ip *); + hlen = sizeof(struct ip); /* no options in reply */ + + /* fill in icmp */ + m->m_data += hlen; + m->m_len -= hlen; + + icp = mtod(m, struct icmp *); + + if (minsize) + s_ip_len = shlen + ICMP_MINLEN; /* return header+8b only */ + else if (s_ip_len > ICMP_MAXDATALEN) /* maximum size */ + s_ip_len = ICMP_MAXDATALEN; + + m->m_len = ICMP_MINLEN + s_ip_len; /* 8 bytes ICMP header */ + + /* min. size = 8+sizeof(struct ip)+8 */ + + icp->icmp_type = type; + icp->icmp_code = code; + icp->icmp_id = 0; + icp->icmp_seq = 0; + + memcpy(&icp->icmp_ip, msrc->m_data, s_ip_len); /* report the ip packet */ + HTONS(icp->icmp_ip.ip_len); + HTONS(icp->icmp_ip.ip_id); + HTONS(icp->icmp_ip.ip_off); + + if (message && WITH_ICMP_ERROR_MSG) { /* append message to ICMP packet */ + int message_len; + char *cpnt; + message_len = strlen(message); + if (message_len > ICMP_MAXDATALEN) + message_len = ICMP_MAXDATALEN; + cpnt = (char *)m->m_data + m->m_len; + memcpy(cpnt, message, message_len); + m->m_len += message_len; + } + + icp->icmp_cksum = 0; + icp->icmp_cksum = cksum(m, m->m_len); + + m->m_data -= hlen; + m->m_len += hlen; + + /* fill in ip */ + ip->ip_hl = hlen >> 2; + ip->ip_len = m->m_len; + + ip->ip_tos = ((ip->ip_tos & 0x1E) | 0xC0); /* high priority for errors */ + + ip->ip_ttl = MAXTTL; + ip->ip_p = IPPROTO_ICMP; + ip->ip_dst = ip->ip_src; /* ip addresses */ + ip->ip_src = *src; + + ip_output((struct socket *)NULL, m); + +end_error: + return; +} +#undef ICMP_MAXDATALEN + +void icmp_send_error(struct mbuf *msrc, uint8_t type, uint8_t code, int minsize, + const char *message) +{ + icmp_forward_error(msrc, type, code, minsize, message, &msrc->slirp->vhost_addr); +} + +/* + * Reflect the ip packet back to the source + */ +void icmp_reflect(struct mbuf *m) +{ + register struct ip *ip = mtod(m, struct ip *); + int hlen = ip->ip_hl << 2; + int optlen = hlen - sizeof(struct ip); + register struct icmp *icp; + + /* + * Send an icmp packet back to the ip level, + * after supplying a checksum. + */ + m->m_data += hlen; + m->m_len -= hlen; + icp = mtod(m, struct icmp *); + + icp->icmp_type = ICMP_ECHOREPLY; + icp->icmp_cksum = 0; + icp->icmp_cksum = cksum(m, ip->ip_len - hlen); + + m->m_data -= hlen; + m->m_len += hlen; + + /* fill in ip */ + if (optlen > 0) { + /* + * Strip out original options by copying rest of first + * mbuf's data back, and adjust the IP length. + */ + memmove((char *)(ip + 1), (char *)ip + hlen, + (unsigned)(m->m_len - hlen)); + hlen -= optlen; + ip->ip_hl = hlen >> 2; + ip->ip_len -= optlen; + m->m_len -= optlen; + } + + ip->ip_ttl = MAXTTL; + { /* swap */ + struct in_addr icmp_dst; + icmp_dst = ip->ip_dst; + ip->ip_dst = ip->ip_src; + ip->ip_src = icmp_dst; + } + + ip_output((struct socket *)NULL, m); +} + +void icmp_receive(struct socket *so) +{ + struct mbuf *m = so->so_m; + struct ip *ip = mtod(m, struct ip *); + int hlen = ip->ip_hl << 2; + uint8_t error_code; + struct icmp *icp; + int id, len; + + m->m_data += hlen; + m->m_len -= hlen; + icp = mtod(m, struct icmp *); + + id = icp->icmp_id; + len = recv(so->s, icp, M_ROOM(m), 0); + + if (so->so_type == IPPROTO_IP) { + if (len >= sizeof(struct ip)) { + struct ip *inner_ip = mtod(m, struct ip *); + int inner_hlen = inner_ip->ip_hl << 2; + if (inner_hlen > len) { + len = -1; + errno = -EINVAL; + } else { + len -= inner_hlen; + memmove(icp, (unsigned char *)icp + inner_hlen, len); + } + } else { + len = -1; + errno = -EINVAL; + } + } + + icp->icmp_id = id; + + m->m_data -= hlen; + m->m_len += hlen; + + if (len == -1 || len == 0) { + if (errno == ENETUNREACH) { + error_code = ICMP_UNREACH_NET; + } else { + error_code = ICMP_UNREACH_HOST; + } + DEBUG_MISC(" udp icmp rx errno = %d-%s", errno, strerror(errno)); + icmp_send_error(so->so_m, ICMP_UNREACH, error_code, 0, strerror(errno)); + } else { + icmp_reflect(so->so_m); + so->so_m = NULL; /* Don't m_free() it again! */ + } + icmp_detach(so); +} diff --git a/app/src/main/cpp/libslirp/src/ip_icmp.h b/app/src/main/cpp/libslirp/src/ip_icmp.h new file mode 100644 index 00000000..aad04165 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ip_icmp.h @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_icmp.h 8.1 (Berkeley) 6/10/93 + * ip_icmp.h,v 1.4 1995/05/30 08:09:43 rgrimes Exp + */ + +#ifndef NETINET_IP_ICMP_H +#define NETINET_IP_ICMP_H + +/* + * Interface Control Message Protocol Definitions. + * Per RFC 792, September 1981. + */ + +typedef uint32_t n_time; + +/* + * Structure of an icmp header. + */ +struct icmp { + uint8_t icmp_type; /* type of message, see below */ + uint8_t icmp_code; /* type sub code */ + uint16_t icmp_cksum; /* ones complement cksum of struct */ + union { + uint8_t ih_pptr; /* ICMP_PARAMPROB */ + struct in_addr ih_gwaddr; /* ICMP_REDIRECT */ + struct ih_idseq { + uint16_t icd_id; + uint16_t icd_seq; + } ih_idseq; + int ih_void; + + /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */ + struct ih_pmtu { + uint16_t ipm_void; + uint16_t ipm_nextmtu; + } ih_pmtu; + } icmp_hun; +#define icmp_pptr icmp_hun.ih_pptr +#define icmp_gwaddr icmp_hun.ih_gwaddr +#define icmp_id icmp_hun.ih_idseq.icd_id +#define icmp_seq icmp_hun.ih_idseq.icd_seq +#define icmp_void icmp_hun.ih_void +#define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void +#define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu + union { + struct id_ts { + n_time its_otime; + n_time its_rtime; + n_time its_ttime; + } id_ts; + struct id_ip { + struct ip idi_ip; + /* options and then 64 bits of data */ + } id_ip; + uint32_t id_mask; + char id_data[1]; + } icmp_dun; +#define icmp_otime icmp_dun.id_ts.its_otime +#define icmp_rtime icmp_dun.id_ts.its_rtime +#define icmp_ttime icmp_dun.id_ts.its_ttime +#define icmp_ip icmp_dun.id_ip.idi_ip +#define icmp_mask icmp_dun.id_mask +#define icmp_data icmp_dun.id_data +}; + +/* + * Lower bounds on packet lengths for various types. + * For the error advice packets must first ensure that the + * packet is large enough to contain the returned ip header. + * Only then can we do the check to see if 64 bits of packet + * data have been returned, since we need to check the returned + * ip header length. + */ +#define ICMP_MINLEN 8 /* abs minimum */ +#define ICMP_TSLEN (8 + 3 * sizeof(n_time)) /* timestamp */ +#define ICMP_MASKLEN 12 /* address mask */ +#define ICMP_ADVLENMIN (8 + sizeof(struct ip) + 8) /* min */ +#define ICMP_ADVLEN(p) (8 + ((p)->icmp_ip.ip_hl << 2) + 8) +/* N.B.: must separately check that ip_hl >= 5 */ + +/* + * Definition of type and code field values. + */ +#define ICMP_ECHOREPLY 0 /* echo reply */ +#define ICMP_UNREACH 3 /* dest unreachable, codes: */ +#define ICMP_UNREACH_NET 0 /* bad net */ +#define ICMP_UNREACH_HOST 1 /* bad host */ +#define ICMP_UNREACH_PROTOCOL 2 /* bad protocol */ +#define ICMP_UNREACH_PORT 3 /* bad port */ +#define ICMP_UNREACH_NEEDFRAG 4 /* IP_DF caused drop */ +#define ICMP_UNREACH_SRCFAIL 5 /* src route failed */ +#define ICMP_UNREACH_NET_UNKNOWN 6 /* unknown net */ +#define ICMP_UNREACH_HOST_UNKNOWN 7 /* unknown host */ +#define ICMP_UNREACH_ISOLATED 8 /* src host isolated */ +#define ICMP_UNREACH_NET_PROHIB 9 /* prohibited access */ +#define ICMP_UNREACH_HOST_PROHIB 10 /* ditto */ +#define ICMP_UNREACH_TOSNET 11 /* bad tos for net */ +#define ICMP_UNREACH_TOSHOST 12 /* bad tos for host */ +#define ICMP_SOURCEQUENCH 4 /* packet lost, slow down */ +#define ICMP_REDIRECT 5 /* shorter route, codes: */ +#define ICMP_REDIRECT_NET 0 /* for network */ +#define ICMP_REDIRECT_HOST 1 /* for host */ +#define ICMP_REDIRECT_TOSNET 2 /* for tos and net */ +#define ICMP_REDIRECT_TOSHOST 3 /* for tos and host */ +#define ICMP_ECHO 8 /* echo service */ +#define ICMP_ROUTERADVERT 9 /* router advertisement */ +#define ICMP_ROUTERSOLICIT 10 /* router solicitation */ +#define ICMP_TIMXCEED 11 /* time exceeded, code: */ +#define ICMP_TIMXCEED_INTRANS 0 /* ttl==0 in transit */ +#define ICMP_TIMXCEED_REASS 1 /* ttl==0 in reass */ +#define ICMP_PARAMPROB 12 /* ip header bad */ +#define ICMP_PARAMPROB_OPTABSENT 1 /* req. opt. absent */ +#define ICMP_TSTAMP 13 /* timestamp request */ +#define ICMP_TSTAMPREPLY 14 /* timestamp reply */ +#define ICMP_IREQ 15 /* information request */ +#define ICMP_IREQREPLY 16 /* information reply */ +#define ICMP_MASKREQ 17 /* address mask request */ +#define ICMP_MASKREPLY 18 /* address mask reply */ + +#define ICMP_MAXTYPE 18 + +#define ICMP_INFOTYPE(type) \ + ((type) == ICMP_ECHOREPLY || (type) == ICMP_ECHO || \ + (type) == ICMP_ROUTERADVERT || (type) == ICMP_ROUTERSOLICIT || \ + (type) == ICMP_TSTAMP || (type) == ICMP_TSTAMPREPLY || \ + (type) == ICMP_IREQ || (type) == ICMP_IREQREPLY || \ + (type) == ICMP_MASKREQ || (type) == ICMP_MASKREPLY) + +/* Called from slirp_new */ +void icmp_init(Slirp *slirp); + +/* Called from slirp_cleanup */ +void icmp_cleanup(Slirp *slirp); + +/* Process an ICMP packet from the guest */ +void icmp_input(struct mbuf *, int); + +/* Send an ICMP error related to the given packet, using the given ICMP type and code, appending the given message (if enabled at compilation), and using the given source. If minsize is sent, send only header + 8B of the given packet, otherwise send it all */ +void icmp_forward_error(struct mbuf *msrc, uint8_t type, uint8_t code, int minsize, + const char *message, struct in_addr *src); + +/* Similar to icmp_forward_error, but use the virtual host address as source */ +void icmp_send_error(struct mbuf *msrc, uint8_t type, uint8_t code, int minsize, + const char *message); + +/* Forward the ICMP packet to the guest (probably a ping reply) */ +void icmp_reflect(struct mbuf *); + +/* Handle ICMP data from the ICMP socket, and forward it to the guest (using so_m as reference) */ +void icmp_receive(struct socket *so); + +/* Forget about this pending ICMP request */ +void icmp_detach(struct socket *so); + +#endif diff --git a/app/src/main/cpp/libslirp/src/ip_input.c b/app/src/main/cpp/libslirp/src/ip_input.c new file mode 100644 index 00000000..0a4b008a --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ip_input.c @@ -0,0 +1,450 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_input.c 8.2 (Berkeley) 1/4/94 + * ip_input.c,v 1.11 1994/11/16 10:17:08 jkh Exp + */ + +/* + * Changes and additions relating to SLiRP are + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" +#include "ip_icmp.h" + +static struct ip *ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp); +static void ip_freef(Slirp *slirp, struct ipq *fp); +static void ip_enq(register struct ipas *p, register struct ipas *prev); +static void ip_deq(register struct ipas *p); + +/* + * IP initialization: fill in IP protocol switch table. + * All protocols not implemented in kernel go to raw IP protocol handler. + */ +void ip_init(Slirp *slirp) +{ + slirp->ipq.ip_link.next = slirp->ipq.ip_link.prev = &slirp->ipq.ip_link; + udp_init(slirp); + tcp_init(slirp); + icmp_init(slirp); +} + +void ip_cleanup(Slirp *slirp) +{ + udp_cleanup(slirp); + tcp_cleanup(slirp); + icmp_cleanup(slirp); +} + +/* + * Ip input routine. Checksum and byte swap header. If fragmented + * try to reassemble. Process options. Pass to next level. + */ +void ip_input(struct mbuf *m) +{ + Slirp *slirp = m->slirp; + M_DUP_DEBUG(slirp, m, 0, TCPIPHDR_DELTA); + + register struct ip *ip; + int hlen; + + if (!slirp->in_enabled) { + goto bad; + } + + DEBUG_CALL("ip_input"); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("m_len = %d", m->m_len); + + if (m->m_len < sizeof(struct ip)) { + goto bad; + } + + ip = mtod(m, struct ip *); + + if (ip->ip_v != IPVERSION) { + goto bad; + } + + hlen = ip->ip_hl << 2; + if (hlen < sizeof(struct ip) || hlen > m->m_len) { /* min header length */ + goto bad; /* or packet too short */ + } + + /* keep ip header intact for ICMP reply + * ip->ip_sum = cksum(m, hlen); + * if (ip->ip_sum) { + */ + if (cksum(m, hlen)) { + goto bad; + } + + /* + * Convert fields to host representation. + */ + NTOHS(ip->ip_len); + if (ip->ip_len < hlen) { + goto bad; + } + NTOHS(ip->ip_id); + NTOHS(ip->ip_off); + + /* + * Check that the amount of data in the buffers + * is as at least much as the IP header would have us expect. + * Trim mbufs if longer than we expect. + * Drop packet if shorter than we expect. + */ + if (m->m_len < ip->ip_len) { + goto bad; + } + + /* Should drop packet if mbuf too long? hmmm... */ + if (m->m_len > ip->ip_len) + m_adj(m, ip->ip_len - m->m_len); + + /* check ip_ttl for a correct ICMP reply */ + if (ip->ip_ttl == 0) { + icmp_send_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, "ttl"); + goto bad; + } + + /* + * If offset or IP_MF are set, must reassemble. + * Otherwise, nothing need be done. + * (We could look in the reassembly queue to see + * if the packet was previously fragmented, + * but it's not worth the time; just let them time out.) + * + * XXX This should fail, don't fragment yet + */ + if (ip->ip_off & ~IP_DF) { + register struct ipq *q; + struct qlink *l; + /* + * Look for queue of fragments + * of this datagram. + */ + for (l = slirp->ipq.ip_link.next; l != &slirp->ipq.ip_link; + l = l->next) { + q = container_of(l, struct ipq, ip_link); + if (ip->ip_id == q->ipq_id && + ip->ip_src.s_addr == q->ipq_src.s_addr && + ip->ip_dst.s_addr == q->ipq_dst.s_addr && + ip->ip_p == q->ipq_p) + goto found; + } + q = NULL; + found: + + /* + * Adjust ip_len to not reflect header, + * set ip_mff if more fragments are expected, + * convert offset of this to bytes. + */ + ip->ip_len -= hlen; + if (ip->ip_off & IP_MF) + ip->ip_tos |= 1; + else + ip->ip_tos &= ~1; + + ip->ip_off <<= 3; + + /* + * If datagram marked as having more fragments + * or if this is not the first fragment, + * attempt reassembly; if it succeeds, proceed. + */ + if (ip->ip_tos & 1 || ip->ip_off) { + ip = ip_reass(slirp, ip, q); + if (ip == NULL) + return; + m = dtom(slirp, ip); + } else if (q) + ip_freef(slirp, q); + + } else + ip->ip_len -= hlen; + + /* + * Switch out to protocol's input routine. + */ + switch (ip->ip_p) { + case IPPROTO_TCP: + tcp_input(m, hlen, (struct socket *)NULL, AF_INET); + break; + case IPPROTO_UDP: + udp_input(m, hlen); + break; + case IPPROTO_ICMP: + icmp_input(m, hlen); + break; + default: + m_free(m); + } + return; +bad: + m_free(m); +} + +#define iptoas(P) container_of((P), struct ipas, ipf_ip) +#define astoip(P) (&(P)->ipf_ip) +/* + * Take incoming datagram fragment and try to + * reassemble it into whole datagram. If a chain for + * reassembly of this datagram already exists, then it + * is given as q; otherwise have to make a chain. + */ +static struct ip *ip_reass(Slirp *slirp, struct ip *ip, struct ipq *q) +{ + register struct mbuf *m = dtom(slirp, ip); + struct ipas *first = container_of(q, struct ipas, ipq); + register struct ipas *cursor; + int hlen = ip->ip_hl << 2; + int i, next; + + DEBUG_CALL("ip_reass"); + DEBUG_ARG("ip = %p", ip); + DEBUG_ARG("q = %p", q); + DEBUG_ARG("m = %p", m); + + /* + * Presence of header sizes in mbufs + * would confuse code below. + * Fragment m_data is concatenated. + */ + m->m_data += hlen; + m->m_len -= hlen; + + /* + * If first fragment to arrive, create a reassembly queue. + */ + if (q == NULL) { + struct mbuf *t = m_get(slirp); + + if (t == NULL) { + goto dropfrag; + } + first = mtod(t, struct ipas *); + q = &first->ipq; + slirp_insque(&q->ip_link, &slirp->ipq.ip_link); + q->ipq_ttl = IPFRAGTTL; + q->ipq_p = ip->ip_p; + q->ipq_id = ip->ip_id; + first->link.next = first->link.prev = first; + q->ipq_src = ip->ip_src; + q->ipq_dst = ip->ip_dst; + cursor = first; + goto insert; + } + + /* + * Find a segment which begins after this one does. + */ + for (cursor = first->link.next; cursor != first; cursor = cursor->link.next) + if (cursor->ipf_off > ip->ip_off) + break; + + /* + * If there is a preceding segment, it may provide some of + * our data already. If so, drop the data from the incoming + * segment. If it provides all of our data, drop us. + */ + if (cursor->link.prev != first) { + struct ipas *pq = cursor->link.prev; + i = pq->ipf_off + pq->ipf_len - ip->ip_off; + if (i > 0) { + if (i >= ip->ip_len) + goto dropfrag; + m_adj(dtom(slirp, ip), i); + ip->ip_off += i; + ip->ip_len -= i; + } + } + + /* + * While we overlap succeeding segments trim them or, + * if they are completely covered, dequeue them. + */ + while (cursor != first && ip->ip_off + ip->ip_len > cursor->ipf_off) { + struct ipas *prev; + i = (ip->ip_off + ip->ip_len) - cursor->ipf_off; + if (i < cursor->ipf_len) { + cursor->ipf_len -= i; + cursor->ipf_off += i; + m_adj(dtom(slirp, cursor), i); + break; + } + prev = cursor; + cursor = cursor->link.next; + ip_deq(prev); + m_free(dtom(slirp, prev)); + } + +insert: + /* + * Stick new segment in its place; + * check for complete reassembly. + */ + ip_enq(iptoas(ip), cursor->link.prev); + next = 0; + for (cursor = first->link.next; cursor != first; cursor = cursor->link.next) { + if (cursor->ipf_off != next) + return NULL; + next += cursor->ipf_len; + } + if (((struct ipas *)(cursor->link.prev))->ipf_tos & 1) + return NULL; + + /* + * Reassembly is complete; concatenate fragments. + */ + cursor = first->link.next; + m = dtom(slirp, cursor); + int delta = (char *)cursor - (m->m_flags & M_EXT ? m->m_ext : m->m_dat); + + cursor = cursor->link.next; + while (cursor != first) { + struct mbuf *t = dtom(slirp, cursor); + cursor = cursor->link.next; + m_cat(m, t); + } + + /* + * Create header for new ip packet by + * modifying header of first packet; + * dequeue and discard fragment reassembly header. + * Make header visible. + */ + cursor = first->link.next; + + /* + * If the fragments concatenated to an mbuf that's bigger than the total + * size of the fragment and the mbuf was not already using an m_ext buffer, + * then an m_ext buffer was allocated. But q->ipq_next points to the old + * buffer (in the mbuf), so we must point ip into the new buffer. + */ + if (m->m_flags & M_EXT) { + cursor = (struct ipas *)(m->m_ext + delta); + } + + ip = astoip(cursor); + ip->ip_len = next; + ip->ip_tos &= ~1; + ip->ip_src = q->ipq_src; + ip->ip_dst = q->ipq_dst; + slirp_remque(&q->ip_link); + m_free(dtom(slirp, q)); + m->m_len += (ip->ip_hl << 2); + m->m_data -= (ip->ip_hl << 2); + + return ip; + +dropfrag: + m_free(m); + return NULL; +} + +/* + * Free a fragment reassembly header and all + * associated datagrams. + */ +static void ip_freef(Slirp *slirp, struct ipq *q) +{ + struct ipas *first = container_of(q, struct ipas, ipq); + register struct ipas *cursor, *next; + + for (cursor = first->link.next; cursor != first; cursor = next) { + next = cursor->link.next; + ip_deq(cursor); + m_free(dtom(slirp, cursor)); + } + slirp_remque(&q->ip_link); + m_free(dtom(slirp, q)); +} + +/* + * Put an ip fragment on a reassembly chain. + * Like slirp_insque, but pointers in middle of structure. + */ +static void ip_enq(register struct ipas *p, register struct ipas *prev) +{ + DEBUG_CALL("ip_enq"); + DEBUG_ARG("prev = %p", prev); + p->link.prev = prev; + p->link.next = prev->link.next; + ((struct ipas *)(prev->link.next))->link.prev = p; + prev->link.next = p; +} + +/* + * To ip_enq as slirp_remque is to slirp_insque. + */ +static void ip_deq(register struct ipas *p) +{ + ((struct ipas *)(p->link.prev))->link.next = p->link.next; + ((struct ipas *)(p->link.next))->link.prev = p->link.prev; +} + +void ip_slowtimo(Slirp *slirp) +{ + struct qlink *l; + + DEBUG_CALL("ip_slowtimo"); + + l = slirp->ipq.ip_link.next; + + if (l == NULL) + return; + + while (l != &slirp->ipq.ip_link) { + struct ipq *q = container_of(l, struct ipq, ip_link); + l = l->next; + if (--q->ipq_ttl == 0) { + ip_freef(slirp, q); + } + } +} + +void ip_stripoptions(register struct mbuf *m) +{ + register int i; + struct ip *ip = mtod(m, struct ip *); + register char *opts; + int olen; + + olen = (ip->ip_hl << 2) - sizeof(struct ip); + opts = (char *)(ip + 1); + i = m->m_len - (sizeof(struct ip) + olen); + memmove(opts, opts + olen, (unsigned)i); + m->m_len -= olen; + + ip->ip_hl = sizeof(struct ip) >> 2; +} diff --git a/app/src/main/cpp/libslirp/src/ip_output.c b/app/src/main/cpp/libslirp/src/ip_output.c new file mode 100644 index 00000000..4f626059 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ip_output.c @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_output.c 8.3 (Berkeley) 1/21/94 + * ip_output.c,v 1.9 1994/11/16 10:17:10 jkh Exp + */ + +/* + * Changes and additions relating to SLiRP are + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" + +/* Number of packets queued before we start sending + * (to prevent allocing too many mbufs) */ +#define IF_THRESH 10 + +/* + * IP output. The packet in mbuf chain m contains a skeletal IP + * header (with len, off, ttl, proto, tos, src, dst). + * The mbuf chain containing the packet will be freed. + * The mbuf opt, if present, will not be freed. + */ +int ip_output(struct socket *so, struct mbuf *m0) +{ + Slirp *slirp = m0->slirp; + M_DUP_DEBUG(slirp, m0, 0, 0); + + register struct ip *ip; + register struct mbuf *m = m0; + register int hlen = sizeof(struct ip); + int len, off, error = 0; + + DEBUG_CALL("ip_output"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m0 = %p", m0); + + ip = mtod(m, struct ip *); + /* + * Fill in IP header. + */ + ip->ip_v = IPVERSION; + ip->ip_off &= IP_DF; + ip->ip_id = htons(slirp->ip_id++); + ip->ip_hl = hlen >> 2; + + /* + * If small enough for interface, can just send directly. + */ + if ((uint16_t)ip->ip_len <= slirp->if_mtu) { + ip->ip_len = htons((uint16_t)ip->ip_len); + ip->ip_off = htons((uint16_t)ip->ip_off); + ip->ip_sum = 0; + ip->ip_sum = cksum(m, hlen); + + if_output(so, m); + goto done; + } + + /* + * Too large for interface; fragment if possible. + * Must be able to put at least 8 bytes per fragment. + */ + if (ip->ip_off & IP_DF) { + error = -1; + goto bad; + } + + len = (slirp->if_mtu - hlen) & ~7; /* ip databytes per packet */ + if (len < 8) { + error = -1; + goto bad; + } + + { + int mhlen, firstlen = len; + struct mbuf **mnext = &m->m_nextpkt; + + /* + * Loop through length of segment after first fragment, + * make new header and copy data of each part and link onto chain. + */ + m0 = m; + mhlen = sizeof(struct ip); + for (off = hlen + len; off < (uint16_t)ip->ip_len; off += len) { + register struct ip *mhip; + m = m_get(slirp); + if (m == NULL) { + error = -1; + goto sendorfree; + } + m->m_data += IF_MAXLINKHDR; + mhip = mtod(m, struct ip *); + *mhip = *ip; + + m->m_len = mhlen; + mhip->ip_off = ((off - hlen) >> 3) + (ip->ip_off & ~IP_MF); + if (ip->ip_off & IP_MF) + mhip->ip_off |= IP_MF; + if (off + len >= (uint16_t)ip->ip_len) + len = (uint16_t)ip->ip_len - off; + else + mhip->ip_off |= IP_MF; + mhip->ip_len = htons((uint16_t)(len + mhlen)); + + if (m_copy(m, m0, off, len) < 0) { + error = -1; + goto sendorfree; + } + + mhip->ip_off = htons((uint16_t)mhip->ip_off); + mhip->ip_sum = 0; + mhip->ip_sum = cksum(m, mhlen); + *mnext = m; + mnext = &m->m_nextpkt; + } + /* + * Update first fragment by trimming what's been copied out + * and updating header, then send each fragment (in order). + */ + m = m0; + m_adj(m, hlen + firstlen - (uint16_t)ip->ip_len); + ip->ip_len = htons((uint16_t)m->m_len); + ip->ip_off = htons((uint16_t)(ip->ip_off | IP_MF)); + ip->ip_sum = 0; + ip->ip_sum = cksum(m, hlen); + sendorfree: + for (m = m0; m; m = m0) { + m0 = m->m_nextpkt; + m->m_nextpkt = NULL; + if (error == 0) + if_output(so, m); + else + m_free(m); + } + } + +done: + return (error); + +bad: + m_free(m0); + goto done; +} diff --git a/app/src/main/cpp/libslirp/src/libslirp.h b/app/src/main/cpp/libslirp/src/libslirp.h new file mode 100644 index 00000000..3caa6f18 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/libslirp.h @@ -0,0 +1,342 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef LIBSLIRP_H +#define LIBSLIRP_H + +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#include +typedef SSIZE_T slirp_ssize_t; +#ifdef BUILDING_LIBSLIRP +# define SLIRP_EXPORT __declspec(dllexport) +#else +# define SLIRP_EXPORT __declspec(dllimport) +#endif +#else +#include +typedef ssize_t slirp_ssize_t; +#include +#include +#define SLIRP_EXPORT +#endif + +#include "libslirp-version.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Opaque structure containing the slirp state */ +typedef struct Slirp Slirp; + +/* Flags passed to SlirpAddPollCb and to be returned by SlirpGetREventsCb. */ +enum { + SLIRP_POLL_IN = 1 << 0, + SLIRP_POLL_OUT = 1 << 1, + SLIRP_POLL_PRI = 1 << 2, + SLIRP_POLL_ERR = 1 << 3, + SLIRP_POLL_HUP = 1 << 4, +}; + +/* Callback for application to get data from the guest */ +typedef slirp_ssize_t (*SlirpReadCb)(void *buf, size_t len, void *opaque); +/* Callback for application to send data to the guest */ +typedef slirp_ssize_t (*SlirpWriteCb)(const void *buf, size_t len, void *opaque); +/* Timer callback */ +typedef void (*SlirpTimerCb)(void *opaque); +/* Callback for libslirp to register polling callbacks */ +typedef int (*SlirpAddPollCb)(int fd, int events, void *opaque); +/* Callback for libslirp to get polling result */ +typedef int (*SlirpGetREventsCb)(int idx, void *opaque); + +/* For now libslirp creates only a timer for the IPv6 RA */ +typedef enum SlirpTimerId { + SLIRP_TIMER_RA, + SLIRP_TIMER_NUM, +} SlirpTimerId; + +/* + * Callbacks from slirp, to be set by the application. + * + * The opaque parameter is set to the opaque pointer given in the slirp_new / + * slirp_init call. + */ +typedef struct SlirpCb { + /* + * Send an ethernet frame to the guest network. The opaque parameter is the + * one given to slirp_init(). If the guest is not ready to receive a frame, + * the function can just drop the data. TCP will then handle retransmissions + * at a lower pace. + * <0 reports an IO error. + */ + SlirpWriteCb send_packet; + /* Print a message for an error due to guest misbehavior. */ + void (*guest_error)(const char *msg, void *opaque); + /* Return the virtual clock value in nanoseconds */ + int64_t (*clock_get_ns)(void *opaque); + /* Create a new timer with the given callback and opaque data. Not + * needed if timer_new_opaque is provided. */ + void *(*timer_new)(SlirpTimerCb cb, void *cb_opaque, void *opaque); + /* Remove and free a timer */ + void (*timer_free)(void *timer, void *opaque); + /* Modify a timer to expire at @expire_time (ms) */ + void (*timer_mod)(void *timer, int64_t expire_time, void *opaque); + /* Register a fd for future polling */ + void (*register_poll_fd)(int fd, void *opaque); + /* Unregister a fd */ + void (*unregister_poll_fd)(int fd, void *opaque); + /* Kick the io-thread, to signal that new events may be processed because some TCP buffer + * can now receive more data, i.e. slirp_socket_can_recv will return 1. */ + void (*notify)(void *opaque); + + /* + * Fields introduced in SlirpConfig version 4 begin + */ + + /* Initialization has completed and a Slirp* has been created. */ + void (*init_completed)(Slirp *slirp, void *opaque); + /* Create a new timer. When the timer fires, the application passes + * the SlirpTimerId and cb_opaque to slirp_handle_timer. */ + void *(*timer_new_opaque)(SlirpTimerId id, void *cb_opaque, void *opaque); +} SlirpCb; + +#define SLIRP_CONFIG_VERSION_MIN 1 +#define SLIRP_CONFIG_VERSION_MAX 5 + +typedef struct SlirpConfig { + /* Version must be provided */ + uint32_t version; + /* + * Fields introduced in SlirpConfig version 1 begin + */ + /* Whether to prevent the guest from accessing the Internet */ + int restricted; + /* Whether IPv4 is enabled */ + bool in_enabled; + /* Virtual network for the guest */ + struct in_addr vnetwork; + /* Mask for the virtual network for the guest */ + struct in_addr vnetmask; + /* Virtual address for the host exposed to the guest */ + struct in_addr vhost; + /* Whether IPv6 is enabled */ + bool in6_enabled; + /* Virtual IPv6 network for the guest */ + struct in6_addr vprefix_addr6; + /* Len of the virtual IPv6 network for the guest */ + uint8_t vprefix_len; + /* Virtual address for the host exposed to the guest */ + struct in6_addr vhost6; + /* Hostname exposed to the guest in DHCP hostname option */ + const char *vhostname; + /* Hostname exposed to the guest in the DHCP TFTP server name option */ + const char *tftp_server_name; + /* Path of the files served by TFTP */ + const char *tftp_path; + /* Boot file name exposed to the guest via DHCP */ + const char *bootfile; + /* Start of the DHCP range */ + struct in_addr vdhcp_start; + /* Virtual address for the DNS server exposed to the guest */ + struct in_addr vnameserver; + /* Virtual IPv6 address for the DNS server exposed to the guest */ + struct in6_addr vnameserver6; + /* DNS search names exposed to the guest via DHCP */ + const char **vdnssearch; + /* Domain name exposed to the guest via DHCP */ + const char *vdomainname; + /* MTU when sending packets to the guest */ + /* Default: IF_MTU_DEFAULT */ + size_t if_mtu; + /* MRU when receiving packets from the guest */ + /* Default: IF_MRU_DEFAULT */ + size_t if_mru; + /* Prohibit connecting to 127.0.0.1:* */ + bool disable_host_loopback; + /* + * Enable emulation code (*warning*: this code isn't safe, it is not + * recommended to enable it) + */ + bool enable_emu; + + /* + * Fields introduced in SlirpConfig version 2 begin + */ + /* Address to be used when sending data to the Internet */ + struct sockaddr_in *outbound_addr; + /* IPv6 Address to be used when sending data to the Internet */ + struct sockaddr_in6 *outbound_addr6; + + /* + * Fields introduced in SlirpConfig version 3 begin + */ + /* slirp will not redirect/serve any DNS packet */ + bool disable_dns; + + /* + * Fields introduced in SlirpConfig version 4 begin + */ + /* slirp will not reply to any DHCP requests */ + bool disable_dhcp; + + /* + * Fields introduced in SlirpConfig version 5 begin + */ + /* Manufacturer ID (IANA Private Enterprise number) */ + uint32_t mfr_id; + /* + * MAC address allocated for an out-of-band management controller, to be + * retrieved through NC-SI. + */ + uint8_t oob_eth_addr[6]; +} SlirpConfig; + +/* Create a new instance of a slirp stack */ +SLIRP_EXPORT +Slirp *slirp_new(const SlirpConfig *cfg, const SlirpCb *callbacks, + void *opaque); +/* slirp_init is deprecated in favor of slirp_new */ +SLIRP_EXPORT +Slirp *slirp_init(int restricted, bool in_enabled, struct in_addr vnetwork, + struct in_addr vnetmask, struct in_addr vhost, + bool in6_enabled, struct in6_addr vprefix_addr6, + uint8_t vprefix_len, struct in6_addr vhost6, + const char *vhostname, const char *tftp_server_name, + const char *tftp_path, const char *bootfile, + struct in_addr vdhcp_start, struct in_addr vnameserver, + struct in6_addr vnameserver6, const char **vdnssearch, + const char *vdomainname, const SlirpCb *callbacks, + void *opaque); +/* Shut down an instance of a slirp stack */ +SLIRP_EXPORT +void slirp_cleanup(Slirp *slirp); + +/* This is called by the application when it is about to sleep through poll(). + * *timeout is set to the amount of virtual time (in ms) that the application intends to + * wait (UINT32_MAX if infinite). slirp_pollfds_fill updates it according to + * e.g. TCP timers, so the application knows it should sleep a smaller amount of + * time. slirp_pollfds_fill calls add_poll for each file descriptor + * that should be monitored along the sleep. The opaque pointer is passed as + * such to add_poll, and add_poll returns an index. */ +SLIRP_EXPORT +void slirp_pollfds_fill(Slirp *slirp, uint32_t *timeout, + SlirpAddPollCb add_poll, void *opaque); + +/* This is called by the application after sleeping, to report which file + * descriptors are available. slirp_pollfds_poll calls get_revents on each file + * descriptor, giving it the index that add_poll returned during the + * slirp_pollfds_fill call, to know whether the descriptor is available for + * read/write/etc. (SLIRP_POLL_*) + * select_error should be passed 1 if poll() returned an error. */ +SLIRP_EXPORT +void slirp_pollfds_poll(Slirp *slirp, int select_error, + SlirpGetREventsCb get_revents, void *opaque); + +/* This is called by the application when the guest emits a packet on the + * guest network, to be interpreted by slirp. */ +SLIRP_EXPORT +void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len); + +/* This is called by the application when a timer expires, if it provides + * the timer_new_opaque callback. It is not needed if the application only + * uses timer_new. */ +SLIRP_EXPORT +void slirp_handle_timer(Slirp *slirp, SlirpTimerId id, void *cb_opaque); + +/* These set up / remove port forwarding between a host port in the real world + * and the guest network. + * Note: guest_addr must be in network order, while guest_port must be in host + * order. + */ +SLIRP_EXPORT +int slirp_add_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, + int host_port, struct in_addr guest_addr, int guest_port); +SLIRP_EXPORT +int slirp_remove_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, + int host_port); + +#define SLIRP_HOSTFWD_UDP 1 +#define SLIRP_HOSTFWD_V6ONLY 2 +SLIRP_EXPORT +int slirp_add_hostxfwd(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *gaddr, socklen_t gaddrlen, + int flags); +SLIRP_EXPORT +int slirp_remove_hostxfwd(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + int flags); + +/* Set up port forwarding between a port in the guest network and a + * command running on the host */ +SLIRP_EXPORT +int slirp_add_exec(Slirp *slirp, const char *cmdline, + struct in_addr *guest_addr, int guest_port); +/* Set up port forwarding between a port in the guest network and a + * Unix port on the host */ +SLIRP_EXPORT +int slirp_add_unix(Slirp *slirp, const char *unixsock, + struct in_addr *guest_addr, int guest_port); +/* Set up port forwarding between a port in the guest network and a + * callback that will receive the data coming from the port */ +SLIRP_EXPORT +int slirp_add_guestfwd(Slirp *slirp, SlirpWriteCb write_cb, void *opaque, + struct in_addr *guest_addr, int guest_port); + +/* TODO: rather identify a guestfwd through an opaque pointer instead of through + * the guest_addr */ + +/* This is called by the application for a guestfwd, to determine how much data + * can be received by the forwarded port through a call to slirp_socket_recv. */ +SLIRP_EXPORT +size_t slirp_socket_can_recv(Slirp *slirp, struct in_addr guest_addr, + int guest_port); +/* This is called by the application for a guestfwd, to provide the data to be + * sent on the forwarded port */ +SLIRP_EXPORT +void slirp_socket_recv(Slirp *slirp, struct in_addr guest_addr, int guest_port, + const uint8_t *buf, int size); + +/* Remove entries added by slirp_add_exec, slirp_add_unix or slirp_add_guestfwd */ +SLIRP_EXPORT +int slirp_remove_guestfwd(Slirp *slirp, struct in_addr guest_addr, + int guest_port); + +/* Return a human-readable state of the slirp stack */ +SLIRP_EXPORT +char *slirp_connection_info(Slirp *slirp); + +/* Return a human-readable state of the NDP/ARP tables */ +SLIRP_EXPORT +char *slirp_neighbor_info(Slirp *slirp); + +/* Save the slirp state through the write_cb. The opaque pointer is passed as + * such to the write_cb. */ +SLIRP_EXPORT +int slirp_state_save(Slirp *s, SlirpWriteCb write_cb, void *opaque); + +/* Returns the version of the slirp state, to be saved along the state */ +SLIRP_EXPORT +int slirp_state_version(void); + +/* Load the slirp state through the read_cb. The opaque pointer is passed as + * such to the read_cb. The version should be given as it was obtained from + * slirp_state_version when slirp_state_save was called. */ +SLIRP_EXPORT +int slirp_state_load(Slirp *s, int version_id, SlirpReadCb read_cb, + void *opaque); + +/* Return the version of the slirp implementation */ +SLIRP_EXPORT +const char *slirp_version_string(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LIBSLIRP_H */ diff --git a/app/src/main/cpp/libslirp/src/libslirp.map b/app/src/main/cpp/libslirp/src/libslirp.map new file mode 100644 index 00000000..3921f8a1 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/libslirp.map @@ -0,0 +1,40 @@ +SLIRP_4.0 { +global: + slirp_add_exec; + slirp_add_guestfwd; + slirp_add_hostfwd; + slirp_cleanup; + slirp_connection_info; + slirp_init; + slirp_input; + slirp_pollfds_fill; + slirp_pollfds_poll; + slirp_remove_hostfwd; + slirp_socket_can_recv; + slirp_socket_recv; + slirp_state_load; + slirp_state_save; + slirp_state_version; + slirp_version_string; +local: + *; +}; + +SLIRP_4.1 { + slirp_new; +} SLIRP_4.0; + +SLIRP_4.2 { + slirp_add_unix; + slirp_remove_guestfwd; +} SLIRP_4.1; + +SLIRP_4.5 { + slirp_add_hostxfwd; + slirp_remove_hostxfwd; + slirp_neighbor_info; +} SLIRP_4.2; + +SLIRP_4.7 { + slirp_handle_timer; +} SLIRP_4.5; diff --git a/app/src/main/cpp/libslirp/src/libvdeslirp.c b/app/src/main/cpp/libslirp/src/libvdeslirp.c new file mode 100644 index 00000000..efe5811b --- /dev/null +++ b/app/src/main/cpp/libslirp/src/libvdeslirp.c @@ -0,0 +1,525 @@ +/* + * VDE - libvdeslirp: libslirp made easy peasy for Linux + * Copyright (C) 2019 Renzo Davoli VirtualSquare + * thanks to Simone Fabbri for early prototype testing + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HAS_ADD_EXEC 1 +#define HAS_ADD_UNIX 1 +#define HAS_REMOVE_GUESTFWD 1 + +#include "libvdeslirp.h" + +#include + +#include "libslirp.h" + +//#define FUTURE_SLIRP_fWD_FEATURES +#define JUMBOMTU 9014 + +#define LIBSLIRP_POLLFD_SIZE_INCREASE 16 +#define APPSIDE 0 +#define DAEMONSIDE 1 + +struct vdeslirp_timer { + struct vdeslirp_timer *next; + uint64_t expire_time; + SlirpTimerCb handler; + void *opaque; +}; + +struct vdeslirp { + Slirp *slirp; + pthread_t daemon; + int channel[2]; + int pfd_len; + int pfd_size; + struct pollfd *pfd; + struct vdeslirp_timer *timer_head; +}; + +static ssize_t vdeslirp_output(const void *buf, size_t len, void *opaque); +static void vdeslirp_guest_error(const char *msg, void *opaque); +static int64_t vdeslirp_clock_get_ns(void *opaque); +static void *vdeslirp_timer_new(SlirpTimerCb cb, void *cb_opaque, void *opaque); +static void vdeslirp_timer_free(void *timer, void *opaque); +static void vdeslirp_timer_mod(void *timer, int64_t expire_time, void *opaque); +static void vdeslirp_register_poll_fd(int fd, void *opaque); +static void vdeslirp_unregister_poll_fd(int fd, void *opaque); +static void vdeslirp_notify(void *opaque); + +struct SlirpCb callbacks = { + .send_packet = vdeslirp_output, + .guest_error = vdeslirp_guest_error, + .clock_get_ns = vdeslirp_clock_get_ns, + .timer_new = vdeslirp_timer_new, + .timer_free = vdeslirp_timer_free, + .timer_mod = vdeslirp_timer_mod, + .register_poll_fd = vdeslirp_register_poll_fd, + .unregister_poll_fd = vdeslirp_unregister_poll_fd, + .notify = vdeslirp_notify, +}; + +#define SLIRP_ADD_FWD 0x11 +#define SLIRP_DEL_FWD 0x12 +#define SLIRP_ADD_UNIXFWD 0x21 +#define SLIRP_DEL_UNIXFWD 0x22 +#define SLIRP_ADD_EXEC 0x31 +#define SLIRP_DEL_EXEC 0x32 + +struct slirp_request { + int tag; + int pipefd[2]; + int intarg; + const void *ptrarg; + void *host_addr; + int host_port; + void *guest_addr; + int guest_port; +}; + +static void vdeslirp_guest_error(const char *msg, void *opaque){ + (void) opaque; + fprintf(stderr, "vdeslirp: %s\n", msg); +} + +/* FWD MANAGEMENT DAEMON SIDE */ + +void slirp_do_req(Slirp *slirp, struct slirp_request *preq) { + int rval; + struct in_addr *host_addr = preq->host_addr; + struct in_addr *guest_addr = preq->guest_addr; + switch (preq->tag) { + case SLIRP_ADD_FWD: + rval = slirp_add_hostfwd(slirp, preq->intarg, + *host_addr, preq->host_port, + *guest_addr, preq->guest_port); + break; + case SLIRP_DEL_FWD: + rval = slirp_remove_hostfwd(slirp, preq->intarg, + *host_addr, preq->host_port); + break; +#ifdef HAS_ADD_UNIX + /* currently unsupported by libslirp */ + case SLIRP_ADD_UNIXFWD: + rval = slirp_add_unix(slirp, preq->ptrarg, + guest_addr, preq->guest_port); + break; +#else +#ifdef HAS_ADD_EXEC + /* warkaround */ + case SLIRP_ADD_UNIXFWD: + { + size_t cmdlen = strlen(preq->ptrarg) + 8; + char cmd[cmdlen]; + snprintf(cmd, cmdlen, "nc -UN %s", (char *) preq->ptrarg); + rval = slirp_add_exec(slirp, cmd, + guest_addr, preq->guest_port); + } + break; +#endif +#endif +#ifdef HAS_ADD_EXEC + case SLIRP_ADD_EXEC: + rval = slirp_add_exec(slirp, preq->ptrarg, + guest_addr, preq->guest_port); + break; +#endif +#ifdef HAS_REMOVE_GUESTFWD + case SLIRP_DEL_UNIXFWD: + case SLIRP_DEL_EXEC: + rval = slirp_remove_guestfwd(slirp, + *guest_addr, preq->guest_port); + break; +#endif + default: + rval = -ENOSYS; + } + rval = write(preq->pipefd[DAEMONSIDE],&rval,sizeof(rval)); +} + + +/* FWD MANAGEMENT APP SIDE */ +static int slirp_send_req(struct vdeslirp *slirp, struct slirp_request *preq) { + int rval; + if (pipe(preq->pipefd) < 0) { + return -1; + } else { + rval = write(slirp->channel[APPSIDE], &preq, sizeof(struct slirp_request *)); + if (rval >= 0) + rval = read(preq->pipefd[APPSIDE],&rval,sizeof(rval)); + close(preq->pipefd[0]); + close(preq->pipefd[1]); + return rval < 0 ? (errno = -rval, -1) : 0; + } +} + +int vdeslirp_add_fwd(struct vdeslirp *slirp, int is_udp, + struct in_addr host_addr, int host_port, + struct in_addr guest_addr, int guest_port) { + struct slirp_request req = { + .tag = SLIRP_ADD_FWD, + .intarg = is_udp, + .host_addr = &host_addr, + .host_port = host_port, + .guest_addr = &guest_addr, + .guest_port = guest_port }; + return slirp_send_req(slirp, &req); +} + +int vdeslirp_remove_fwd(struct vdeslirp *slirp, int is_udp, + struct in_addr host_addr, int host_port) { + struct slirp_request req = { + .tag = SLIRP_DEL_FWD, + .intarg = is_udp, + .host_addr = &host_addr, + .host_port = host_port}; + return slirp_send_req(slirp, &req); +} + +int vdeslirp_add_unixfwd(struct vdeslirp *slirp, char *path, + struct in_addr *guest_addr, int guest_port) { + struct slirp_request req = { + .tag = SLIRP_ADD_UNIXFWD, + .guest_addr = guest_addr, + .guest_port = guest_port, + .ptrarg = path }; + return slirp_send_req(slirp, &req); +} + +int vdeslirp_remove_unixfwd(struct vdeslirp *slirp, + struct in_addr guest_addr, int guest_port) { + struct slirp_request req = { + .tag = SLIRP_DEL_UNIXFWD, + .guest_addr = &guest_addr, + .guest_port = guest_port}; + return slirp_send_req(slirp, &req); +} + +int vdeslirp_add_cmdexec(struct vdeslirp *slirp, const char *cmdline, + struct in_addr *guest_addr, int guest_port) { + struct slirp_request req = { + .tag = SLIRP_ADD_EXEC, + .ptrarg = cmdline, + .guest_addr = guest_addr, + .guest_port = guest_port }; + return slirp_send_req(slirp, &req); +} + +int vdeslirp_remove_cmdexec(struct vdeslirp *slirp, + struct in_addr guest_addr, int guest_port) { + struct slirp_request req = { + .tag = SLIRP_DEL_EXEC, + .guest_addr = &guest_addr, + .guest_port = guest_port}; + return slirp_send_req(slirp, &req); +} + +/* TIMER MANAGEMENT */ +static int64_t vdeslirp_clock_get_ns(void *opaque){ + (void) opaque; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +static void *vdeslirp_timer_new(SlirpTimerCb cb, void *cb_opaque, void *opaque){ + struct vdeslirp *slirp = opaque; + struct vdeslirp_timer *qt = malloc(sizeof(*qt)); + if (qt) { + qt->next = slirp->timer_head; + qt->expire_time = -1; + qt->handler = cb; + qt->opaque = cb_opaque; + slirp->timer_head=qt; + } + return qt; +} + +static void vdeslirp_timer_free(void *timer, void *opaque){ + struct vdeslirp *slirp = opaque; + struct vdeslirp_timer *qt = timer; + struct vdeslirp_timer **scan; + for (scan = &slirp->timer_head; *scan != NULL && *scan != qt; scan = &((*scan) ->next)) + ; + if (*scan) { + *scan = qt->next; + free(qt); + } +} + +static void vdeslirp_timer_mod(void *timer, int64_t expire_time, void *opaque){ + (void) opaque; + struct vdeslirp_timer *qt = timer; + qt->expire_time = expire_time; +} + +static void update_ra_timeout(uint32_t *timeout, void *opaque) { + struct vdeslirp *slirp = opaque; + struct vdeslirp_timer *qt; + int64_t now_ms = vdeslirp_clock_get_ns(opaque) / 1000000; + for (qt = slirp->timer_head; qt != NULL; qt = qt->next) { + if (qt->expire_time != -1UL) { + int64_t diff = qt->expire_time - now_ms; + if (diff < 0) diff = 0; + if (diff < *timeout) *timeout = diff; + } + } +} + +static void check_ra_timeout(void *opaque) { + struct vdeslirp *slirp = opaque; + struct vdeslirp_timer *qt; + int64_t now_ms = vdeslirp_clock_get_ns(opaque) / 1000000; + for (qt = slirp->timer_head; qt != NULL; qt = qt->next) { + if (qt->expire_time != -1UL) { + int64_t diff = qt->expire_time - now_ms; + if (diff <= 0) { + qt->expire_time = -1UL; + qt->handler(qt->opaque); + } + } + } +} + + +/* POLL MANAGEMENT */ +static int vdeslirp_slirp_to_poll(int events) { + int ret = 0; + if (events & SLIRP_POLL_IN) ret |= POLLIN; + if (events & SLIRP_POLL_OUT) ret |= POLLOUT; + if (events & SLIRP_POLL_PRI) ret |= POLLPRI; + if (events & SLIRP_POLL_ERR) ret |= POLLERR; + if (events & SLIRP_POLL_HUP) ret |= POLLHUP; + return ret; +} + +static int vdeslirp_poll_to_slirp(int events) { + int ret = 0; + if (events & POLLIN) ret |= SLIRP_POLL_IN; + if (events & POLLOUT) ret |= SLIRP_POLL_OUT; + if (events & POLLPRI) ret |= SLIRP_POLL_PRI; + if (events & POLLERR) ret |= SLIRP_POLL_ERR; + if (events & POLLHUP) ret |= SLIRP_POLL_HUP; + return ret; +} + +static int vdeslirp_add_poll(int fd, int events, void *opaque) { + struct vdeslirp *slirp = opaque; + if (slirp->pfd_len >= slirp->pfd_size) { + int newsize = slirp->pfd_size + LIBSLIRP_POLLFD_SIZE_INCREASE; + struct pollfd *new = realloc(slirp->pfd, newsize * sizeof(struct pollfd)); + if (new) { + slirp->pfd = new; + slirp->pfd_size = newsize; + } + } + if (slirp->pfd_len < slirp->pfd_size) { + int idx = slirp->pfd_len++; + slirp->pfd[idx].fd = fd; + slirp->pfd[idx].events = vdeslirp_slirp_to_poll(events); + return idx; + } else + return -1; +} + +static int vdeslirp_get_revents(int idx, void *opaque) { + struct vdeslirp *slirp = opaque; + return vdeslirp_poll_to_slirp(slirp->pfd[idx].revents); +} + +static void vdeslirp_register_poll_fd(int fd, void *opaque){ + (void) fd; + (void) opaque; +} + +static void vdeslirp_unregister_poll_fd(int fd, void *opaque){ + (void) fd; + (void) opaque; +} + +static void vdeslirp_notify(void *opaque){ + (void) opaque; +} + +static ssize_t vdeslirp_output(const void *buf, size_t len, void *opaque) { + struct vdeslirp *slirp = opaque; + return write(slirp->channel[DAEMONSIDE], buf, len); +} + +static void vdeslirp_cleanup(struct vdeslirp *slirp) { + slirp_cleanup(slirp->slirp); + while(slirp->timer_head) + vdeslirp_timer_free(slirp->timer_head, slirp); + close(slirp->channel[DAEMONSIDE]); + free(slirp->pfd); + free(slirp); +} + +static void *slirp_daemon(void *opaque) { + struct vdeslirp *slirp = opaque; + vdeslirp_add_poll(slirp->channel[DAEMONSIDE], SLIRP_POLL_IN | SLIRP_POLL_HUP, slirp); + for(;;) { + int pollout; + uint32_t timeout = -1; + slirp->pfd_len = 1; + slirp_pollfds_fill(slirp->slirp, &timeout, vdeslirp_add_poll, slirp); + update_ra_timeout(&timeout, slirp); + pollout = poll(slirp->pfd, slirp->pfd_len, timeout); + if (slirp->pfd[0].revents) { + uint8_t buf[JUMBOMTU]; + size_t len = read(slirp->channel[DAEMONSIDE], buf, JUMBOMTU); + if (len <= 0) + break; + if (len == sizeof(struct slirp_request *)) + slirp_do_req(slirp->slirp, *((struct slirp_request **) buf)); + else + slirp_input(slirp->slirp, buf, len); + } + slirp_pollfds_poll(slirp->slirp, (pollout <= 0), vdeslirp_get_revents, slirp); + check_ra_timeout(slirp); + } + return NULL; +} + +static void *memmask(void *mask, size_t len, size_t prefix) { + uint8_t *bufmask = mask; + size_t i; + for (i = 0; (i < len) & (prefix >= 8); i++, prefix -= 8) + bufmask[i] = 0xff; + for (; (i < len) & (prefix > 0); i++, prefix -= 8) + bufmask[i] = ~((1 << (8 - prefix)) - 1); + for (; i < len; i++, prefix -= 8) + bufmask[i] = 0x0; + return bufmask; +} + +static void *memmaskcpy(void *dest, const void *src, const void *mask, size_t n) { + uint8_t *bufdest = dest; + const uint8_t *bufsrc = src; + const uint8_t *bufmask = mask; + size_t i; + for (i = 0; i < n; i++) + bufdest[i] = (bufsrc[i] & bufmask[i]) | (bufdest[i] & ~bufmask[i]); + return bufdest; +} + +void vdeslirp_init(SlirpConfig *cfg, int flags) { + memset(cfg, 0, sizeof(*cfg)); + cfg->version = 1; + + if (flags & VDE_INIT_DEFAULT) { + cfg->restricted = 0; + cfg->in_enabled = 1; + inet_pton(AF_INET,"10.0.2.0", &(cfg->vnetwork)); + inet_pton(AF_INET,"255.255.255.0", &(cfg->vnetmask)); + inet_pton(AF_INET,"10.0.2.2", &(cfg->vhost)); + cfg->in6_enabled = 1; + inet_pton(AF_INET6, "fd00::", &cfg->vprefix_addr6); + cfg->vprefix_len = 64; + inet_pton(AF_INET6, "fd00::2", &cfg->vhost6); + cfg->vhostname = "slirp"; + cfg->tftp_server_name = NULL; + cfg->tftp_path = NULL; + cfg->bootfile = NULL; + inet_pton(AF_INET,"10.0.2.15", &(cfg->vdhcp_start)); + inet_pton(AF_INET,"10.0.2.3", &(cfg->vnameserver)); + inet_pton(AF_INET6, "fd00::3", &cfg->vnameserver6); + cfg->vdnssearch = NULL; + cfg->vdomainname = NULL; + cfg->if_mtu = 0; // IF_MTU_DEFAULT + cfg->if_mru = 0; // IF_MTU_DEFAULT + + cfg->disable_host_loopback = 0; + } +} + +void vdeslirp_setvprefix(SlirpConfig *cfg, int prefix) { + static const struct in_addr inaddr_any = {.s_addr = INADDR_ANY}; + memmask(&cfg->vnetmask, sizeof(struct in_addr), prefix); + cfg->vnetwork = inaddr_any; + memmaskcpy(&cfg->vnetwork, &cfg->vhost, &cfg->vnetmask, sizeof(struct in_addr)); + if (cfg->vdhcp_start.s_addr != inaddr_any.s_addr) + memmaskcpy(&cfg->vdhcp_start, &cfg->vhost, &cfg->vnetmask, sizeof(struct in_addr)); + if (cfg->vnameserver.s_addr != inaddr_any.s_addr) + memmaskcpy(&cfg->vnameserver, &cfg->vhost, &cfg->vnetmask, sizeof(struct in_addr)); +} + +void vdeslirp_setvprefix6(SlirpConfig *cfg, int prefix6) { + struct in6_addr vnetmask6; + cfg->vprefix_len = prefix6; + memmask(&vnetmask6, sizeof(struct in6_addr), prefix6); + cfg->vprefix_addr6 = in6addr_any; + memmaskcpy(&cfg->vprefix_addr6, &cfg->vhost6, &vnetmask6, sizeof(struct in6_addr)); + if (memcmp(&cfg->vnameserver6, &in6addr_any, sizeof(struct in6_addr)) != 0) + memmaskcpy(&cfg->vnameserver6, &cfg->vhost6, &vnetmask6, sizeof(struct in6_addr)); +} + +struct vdeslirp *vdeslirp_open(SlirpConfig *cfg) { + struct vdeslirp *slirp = calloc(1, sizeof(struct vdeslirp)); + if (slirp) { + if (socketpair(AF_LOCAL, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, slirp->channel) < 0) + goto nosocketpair; + if ((slirp->slirp = slirp_new(cfg, &callbacks, slirp)) == NULL) + goto noslirp; + if (pthread_create(&slirp->daemon, NULL, slirp_daemon, slirp) != 0) + goto nopthread; + } + return slirp; +nopthread: + slirp_cleanup(slirp->slirp); +noslirp: + close(slirp->channel[APPSIDE]); + close(slirp->channel[DAEMONSIDE]); +nosocketpair: + free(slirp); + return NULL; +} + +ssize_t vdeslirp_send(struct vdeslirp *slirp, const void *buf, size_t count) { + if (count > sizeof(void *)) { + return write(slirp->channel[APPSIDE], buf, count); + } else + return errno = EINVAL, -1; +} + +ssize_t vdeslirp_recv(struct vdeslirp *slirp, void *buf, size_t count) { + return read(slirp->channel[APPSIDE], buf, count); +} + +int vdeslirp_fd(struct vdeslirp *slirp) { + return slirp->channel[APPSIDE]; +} + +int vdeslirp_close(struct vdeslirp *slirp) { + void *retval; + int rv = close(slirp->channel[APPSIDE]); + pthread_join(slirp->daemon, &retval); + vdeslirp_cleanup(slirp); + return rv; +} + diff --git a/app/src/main/cpp/libslirp/src/libvdeslirp.h b/app/src/main/cpp/libslirp/src/libvdeslirp.h new file mode 100644 index 00000000..fcec594c --- /dev/null +++ b/app/src/main/cpp/libslirp/src/libvdeslirp.h @@ -0,0 +1,48 @@ +#ifndef VDESLIRP_H +#define VDESLIRP_H + +#define HAS_ADD_EXEC 1 +#define HAS_ADD_UNIX 1 +#define HAS_REMOVE_GUESTFWD 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "libslirp.h" + +struct vdeslirp; + +#define VDE_INIT_DEFAULT 1 +void vdeslirp_init(SlirpConfig *cfg, int flags); + +void vdeslirp_setvprefix(SlirpConfig *cfg, int prefix); +void vdeslirp_setvprefix6(SlirpConfig *cfg, int prefix6); + +struct vdeslirp *vdeslirp_open(SlirpConfig *cfg); +ssize_t vdeslirp_send(struct vdeslirp *slirp, const void *buf, size_t count); +ssize_t vdeslirp_recv(struct vdeslirp *slirp, void *buf, size_t count); +int vdeslirp_fd(struct vdeslirp *slirp); +int vdeslirp_close(struct vdeslirp *slirp); + +int vdeslirp_add_fwd(struct vdeslirp *slirp, int is_udp, + struct in_addr host_addr, int host_port, + struct in_addr guest_addr, int guest_port); +int vdeslirp_remove_fwd(struct vdeslirp *slirp, int is_udp, + struct in_addr host_addr, int host_port); + +int vdeslirp_add_unixfwd(struct vdeslirp *slirp, char *path, + struct in_addr *guest_addr, int guest_port); +int vdeslirp_remove_unixfwd(struct vdeslirp *slirp, + struct in_addr guest_addr, int guest_port); + +int vdeslirp_add_cmdexec(struct vdeslirp *slirp, const char *cmdline, + struct in_addr *guest_addr, int guest_port); +int vdeslirp_remove_cmdexec(struct vdeslirp *slirp, + struct in_addr guest_addr, int guest_port); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/libslirp/src/main.h b/app/src/main/cpp/libslirp/src/main.h new file mode 100644 index 00000000..ca36277e --- /dev/null +++ b/app/src/main/cpp/libslirp/src/main.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#ifndef SLIRP_MAIN_H +#define SLIRP_MAIN_H + +#include "libslirp.h" + +/* The current guest virtual time */ +extern unsigned curtime; +/* Always equal to INADDR_LOOPBACK, in network order */ +extern struct in_addr loopback_addr; +/* Always equal to IN_CLASSA_NET, in network order */ +extern unsigned long loopback_mask; + +/* Send a packet to the guest */ +int if_encap(Slirp *slirp, struct mbuf *ifm); +/* Send a frame to the guest. Flags are passed to the send() call */ +slirp_ssize_t slirp_send(struct socket *so, const void *buf, size_t len, int flags); + +#endif diff --git a/app/src/main/cpp/libslirp/src/mbuf.c b/app/src/main/cpp/libslirp/src/mbuf.c new file mode 100644 index 00000000..5ccbda31 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/mbuf.c @@ -0,0 +1,291 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski + */ + +/* + * mbuf's in SLiRP are much simpler than the real mbufs in + * FreeBSD. They are fixed size, determined by the MTU, + * so that one whole packet can fit. Mbuf's cannot be + * chained together. If there's more data than the mbuf + * could hold, an external g_malloced buffer is pointed to + * by m_ext (and the data pointers) and M_EXT is set in + * the flags + */ + +#include "slirp.h" + +#define MBUF_THRESH 30 + +/* + * Find a nice value for msize + */ +#define SLIRP_MSIZE(mtu) \ + (offsetof(struct mbuf, m_dat) + IF_MAXLINKHDR + TCPIPHDR_DELTA + (mtu)) + +void m_init(Slirp *slirp) +{ + slirp->m_freelist.qh_link = slirp->m_freelist.qh_rlink = &slirp->m_freelist; + slirp->m_usedlist.qh_link = slirp->m_usedlist.qh_rlink = &slirp->m_usedlist; +} + +static void m_cleanup_list(struct slirp_quehead *list_head, bool pkts) +{ + struct mbuf *m, *next, *next2; + bool last; + + m = (struct mbuf *)list_head->qh_link; + while ((struct slirp_quehead *)m != list_head) { + next = m->m_next; + + last = false; + while (1) { + next2 = m->m_nextpkt; + + if (pkts) { + ifs_remque(m); + last = next2 == m; + } else { + last = true; + } + + if (m->m_flags & M_EXT) { + g_free(m->m_ext); + } + + g_free(m); + + if (last) + break; + m = next2; + }; + + m = next; + } + list_head->qh_link = list_head; + list_head->qh_rlink = list_head; +} + +void m_cleanup(Slirp *slirp) +{ + m_cleanup_list(&slirp->m_usedlist, false); + m_cleanup_list(&slirp->m_freelist, false); + m_cleanup_list(&slirp->if_batchq, true); + m_cleanup_list(&slirp->if_fastq, true); +} + +/* + * Get an mbuf from the free list, if there are none + * allocate one + * + * Because fragmentation can occur if we alloc new mbufs and + * free old mbufs, we mark all mbufs above mbuf_thresh as M_DOFREE, + * which tells m_free to actually g_free() it + */ +struct mbuf *m_get(Slirp *slirp) +{ + register struct mbuf *m; + int flags = 0; + + DEBUG_CALL("m_get"); + + if (MBUF_DEBUG || slirp->m_freelist.qh_link == &slirp->m_freelist) { + m = g_malloc(SLIRP_MSIZE(slirp->if_mtu)); + slirp->mbuf_alloced++; + if (MBUF_DEBUG || slirp->mbuf_alloced > MBUF_THRESH) + flags = M_DOFREE; + m->slirp = slirp; + } else { + m = (struct mbuf *)slirp->m_freelist.qh_link; + slirp_remque(m); + } + + /* Insert it in the used list */ + slirp_insque(m, &slirp->m_usedlist); + m->m_flags = (flags | M_USEDLIST); + + /* Initialise it */ + m->m_size = SLIRP_MSIZE(slirp->if_mtu) - offsetof(struct mbuf, m_dat); + m->m_data = m->m_dat; + m->m_len = 0; + m->m_nextpkt = NULL; + m->m_prevpkt = NULL; + m->resolution_requested = false; + m->expiration_date = (uint64_t)-1; + DEBUG_ARG("m = %p", m); + return m; +} + +void m_free(struct mbuf *m) +{ + DEBUG_CALL("m_free"); + DEBUG_ARG("m = %p", m); + + if (m) { + /* Remove from m_usedlist */ + if (m->m_flags & M_USEDLIST) + slirp_remque(m); + + /* If it's M_EXT, free() it */ + if (m->m_flags & M_EXT) { + g_free(m->m_ext); + m->m_flags &= ~M_EXT; + } + /* + * Either free() it or put it on the free list + */ + if (m->m_flags & M_DOFREE) { + m->slirp->mbuf_alloced--; + g_free(m); + } else if ((m->m_flags & M_FREELIST) == 0) { + slirp_insque(m, &m->slirp->m_freelist); + m->m_flags = M_FREELIST; /* Clobber other flags */ + } + } /* if(m) */ +} + +/* + * Copy data from one mbuf to the end of + * the other.. if result is too big for one mbuf, allocate + * an M_EXT data segment + */ +void m_cat(struct mbuf *m, struct mbuf *n) +{ + /* + * If there's no room, realloc + */ + if (M_FREEROOM(m) < n->m_len) + m_inc(m, m->m_len + n->m_len); + + memcpy(m->m_data + m->m_len, n->m_data, n->m_len); + m->m_len += n->m_len; + + m_free(n); +} + + +/* make m 'size' bytes large from m_data */ +void m_inc(struct mbuf *m, int size) +{ + int gapsize; + + /* some compilers throw up on gotos. This one we can fake. */ + if (M_ROOM(m) >= size) { + return; + } + + if (m->m_flags & M_EXT) { + gapsize = m->m_data - m->m_ext; + m->m_ext = g_realloc(m->m_ext, size + gapsize); + } else { + gapsize = m->m_data - m->m_dat; + m->m_ext = g_malloc(size + gapsize); + memcpy(m->m_ext, m->m_dat, m->m_size); + m->m_flags |= M_EXT; + } + + m->m_data = m->m_ext + gapsize; + m->m_size = size + gapsize; +} + + +void m_adj(struct mbuf *m, int len) +{ + if (m == NULL) + return; + if (len >= 0) { + /* Trim from head */ + m->m_data += len; + m->m_len -= len; + } else { + /* Trim from tail */ + len = -len; + m->m_len -= len; + } +} + + +/* + * Copy len bytes from m, starting off bytes into n + */ +int m_copy(struct mbuf *n, struct mbuf *m, int off, int len) +{ + if (len > M_FREEROOM(n)) + return -1; + + memcpy((n->m_data + n->m_len), (m->m_data + off), len); + n->m_len += len; + return 0; +} + + +struct mbuf *dtom(Slirp *slirp, void *dat) +{ + struct mbuf *m; + + DEBUG_CALL("dtom"); + DEBUG_ARG("dat = %p", dat); + + /* bug corrected for M_EXT buffers */ + for (m = (struct mbuf *)slirp->m_usedlist.qh_link; + (struct slirp_quehead *)m != &slirp->m_usedlist; m = m->m_next) { + if (m->m_flags & M_EXT) { + if ((char *)dat >= m->m_ext && (char *)dat < (m->m_ext + m->m_size)) + return m; + } else { + if ((char *)dat >= m->m_dat && (char *)dat < (m->m_dat + m->m_size)) + return m; + } + } + + DEBUG_ERROR("dtom failed"); + + return (struct mbuf *)0; +} + +struct mbuf *m_dup(Slirp *slirp, struct mbuf *m, + bool copy_header, + size_t header_size) +{ + struct mbuf *n; + int mcopy_result; + + /* The previous mbuf was supposed to have it already, we can check it along + * the way */ + assert(M_ROOMBEFORE(m) >= header_size); + + n = m_get(slirp); + m_inc(n, m->m_len + header_size); + + if (copy_header) { + m->m_len += header_size; + m->m_data -= header_size; + mcopy_result = m_copy(n, m, 0, m->m_len); + n->m_data += header_size; + n->m_len -= header_size; + m->m_len -= header_size; + m->m_data += header_size; + } else { + n->m_data += header_size; + mcopy_result = m_copy(n, m, 0, m->m_len); + } + g_assert(mcopy_result == 0); + + return n; +} + +void *mtod_check(struct mbuf *m, size_t len) +{ + if (m->m_len >= len) { + return m->m_data; + } + + DEBUG_ERROR("mtod failed"); + + return NULL; +} + +void *m_end(struct mbuf *m) +{ + return m->m_data + m->m_len; +} diff --git a/app/src/main/cpp/libslirp/src/mbuf.h b/app/src/main/cpp/libslirp/src/mbuf.h new file mode 100644 index 00000000..ff1ddc96 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/mbuf.h @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)mbuf.h 8.3 (Berkeley) 1/21/94 + * mbuf.h,v 1.9 1994/11/14 13:54:20 bde Exp + */ + +#ifndef MBUF_H +#define MBUF_H + +/* + * Macros for type conversion + * mtod(m,t) - convert mbuf pointer to data pointer of correct type + */ +#define mtod(m, t) ((t)(m)->m_data) + +/* XXX About mbufs for slirp: + * Only one mbuf is ever used in a chain, for each "cell" of data. + * m_nextpkt points to the next packet, if fragmented. + * If the data is too large, the M_EXT is used, and a larger block + * is alloced. Therefore, m_free[m] must check for M_EXT and if set + * free the m_ext. This is inefficient memory-wise, but who cares. + */ + +/* + * mbufs allow to have a gap between the start of the allocated buffer (m_ext if + * M_EXT is set, m_dat otherwise) and the in-use data: + * + * |--gapsize----->|---m_len-------> + * |----------m_size------------------------------> + * |----M_ROOM--------------------> + * |-M_FREEROOM--> + * + * ^ ^ ^ + * m_dat/m_ext m_data end of buffer + */ + +/* + * How much room is in the mbuf, from m_data to the end of the mbuf + */ +#define M_ROOM(m) \ + ((m->m_flags & M_EXT) ? (((m)->m_ext + (m)->m_size) - (m)->m_data) : \ + (((m)->m_dat + (m)->m_size) - (m)->m_data)) + +/* + * How much free room there is + */ +#define M_FREEROOM(m) (M_ROOM(m) - (m)->m_len) + +/* + * How much free room there is before m_data + */ +#define M_ROOMBEFORE(m) \ + (((m)->m_flags & M_EXT) ? (m)->m_data - (m)->m_ext \ + : (m)->m_data - (m)->m_dat) + +struct mbuf { + /* XXX should union some of these! */ + /* header at beginning of each mbuf: */ + struct mbuf *m_next; /* Linked list of mbufs */ + struct mbuf *m_prev; + struct mbuf *m_nextpkt; /* Next packet in queue/record */ + struct mbuf *m_prevpkt; /* Flags aren't used in the output queue */ + int m_flags; /* Misc flags */ + + int m_size; /* Size of mbuf, from m_dat or m_ext */ + struct socket *m_so; + + char *m_data; /* Current location of data */ + int m_len; /* Amount of data in this mbuf, from m_data */ + + Slirp *slirp; + bool resolution_requested; + uint64_t expiration_date; + char *m_ext; + /* start of dynamic buffer area, must be last element */ + char m_dat[]; +}; + +static inline void ifs_remque(struct mbuf *ifm) +{ + ifm->m_prevpkt->m_nextpkt = ifm->m_nextpkt; + ifm->m_nextpkt->m_prevpkt = ifm->m_prevpkt; +} + +#define M_EXT 0x01 /* m_ext points to more (malloced) data */ +#define M_FREELIST 0x02 /* mbuf is on free list */ +#define M_USEDLIST 0x04 /* XXX mbuf is on used list (for dtom()) */ +#define M_DOFREE \ + 0x08 /* when m_free is called on the mbuf, free() \ + * it rather than putting it on the free list */ + +/* Called by slirp_new */ +void m_init(Slirp *); + +/* Called by slirp_cleanup */ +void m_cleanup(Slirp *slirp); + +/* Allocate an mbuf */ +struct mbuf *m_get(Slirp *); + +/* Release an mbuf (put possibly put it in allocation cache */ +void m_free(struct mbuf *); + +/* Catenate the second buffer to the end of the first buffer, and release the second */ +void m_cat(struct mbuf *, struct mbuf *); + +/* Grow the mbuf to the given size */ +void m_inc(struct mbuf *, int); + +/* If len is positive, trim that amount from the head of the mbuf. If it is negative, trim it from the tail of the mbuf */ +void m_adj(struct mbuf *, int len); + +/* Copy len bytes from the first buffer at the given offset, to the end of the second buffer */ +int m_copy(struct mbuf *, struct mbuf *, int off, int len); + +/* + * Duplicate the mbuf + * + * copy_header specifies whether the bytes before m_data should also be copied. + * header_size specifies how many bytes are to be reserved before m_data. + */ +struct mbuf *m_dup(Slirp *slirp, struct mbuf *m, bool copy_header, size_t header_size); + +/* + * Given a pointer into an mbuf, return the mbuf + * XXX This is a kludge, I should eliminate the need for it + * Fortunately, it's not used often + */ +struct mbuf *dtom(Slirp *, void *); + +/* Check that the mbuf contains at least len bytes, and return the data */ +void *mtod_check(struct mbuf *, size_t len); + +/* Return the end of the data of the mbuf */ +void *m_end(struct mbuf *); + +/* Initialize the ifs queue of the mbuf */ +static inline void ifs_init(struct mbuf *ifm) +{ + ifm->m_nextpkt = ifm->m_prevpkt = ifm; +} + +#ifdef SLIRP_DEBUG +# define MBUF_DEBUG 1 +#else +# ifdef HAVE_VALGRIND +# include +# define MBUF_DEBUG RUNNING_ON_VALGRIND +# else +# define MBUF_DEBUG 0 +# endif +#endif + +/* + * When a function is given an mbuf as well as the responsibility to free it, we + * want valgrind etc. to properly identify the new responsible for the + * free. Achieve this by making a new copy. For instance: + * + * f0(void) { + * struct mbuf *m = m_get(slirp); + * [...] + * switch (something) { + * case 1: + * f1(m); + * break; + * case 2: + * f2(m); + * break; + * [...] + * } + * } + * + * f1(struct mbuf *m) { + * M_DUP_DEBUG(m->slirp, m); + * [...] + * m_free(m); // but author of f1 might be forgetting this + * } + * + * f0 transfers the freeing responsibility to f1, f2, etc. Without the + * M_DUP_DEBUG call in f1, valgrind would tell us that it is f0 where the buffer + * was allocated, but it's difficult to know whether a leak is actually in f0, + * or in f1, or in f2, etc. Duplicating the mbuf in M_DUP_DEBUG each time the + * responsibility is transferred allows to immediately know where the leak + * actually is. + */ +#define M_DUP_DEBUG(slirp, m, copy_header, header_size) do { \ + if (MBUF_DEBUG) { \ + struct mbuf *__n; \ + __n = m_dup((slirp), (m), (copy_header), (header_size)); \ + m_free(m); \ + (m) = __n; \ + } else { \ + (void) (slirp); (void) (copy_header); \ + g_assert(M_ROOMBEFORE(m) >= (header_size)); \ + } \ +} while(0) + +#endif diff --git a/app/src/main/cpp/libslirp/src/minglib.h b/app/src/main/cpp/libslirp/src/minglib.h new file mode 100644 index 00000000..6fb0aa76 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/minglib.h @@ -0,0 +1,37 @@ +// Public Domain; CC0-1.0 license +#pragma once + +#include +#include +#include +#include + +#ifndef G_STATIC_ASSERT +#define G_STATIC_ASSERT(K) _Static_assert((K), #K) +#endif + +#ifndef G_GNUC_PRINTF +#define G_GNUC_PRINTF( format_idx, arg_idx ) \ + __attribute__((__format__ (__printf__, format_idx, arg_idx))) +#endif + +typedef char **GStrv; +typedef void GRand; + +inline size_t g_strv_length(GStrv strv) { + size_t sum = 0; + while (*strv) { + sum += strlen(*strv); + strv++; + } +} + +#define g_new0(T, N) calloc((N), sizeof(T)) + +typedef void *gpointer; + +typedef bool gboolean; + +typedef char gchar; + +#define GLIB_CHECK_VERSION(MAJ, MIN, PAT) (!(((MAJ) > 2) || ((MIN) > 60) || ((PAT) > 0))) \ No newline at end of file diff --git a/app/src/main/cpp/libslirp/src/misc.c b/app/src/main/cpp/libslirp/src/misc.c new file mode 100644 index 00000000..3c258350 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/misc.c @@ -0,0 +1,448 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" +#ifdef G_OS_UNIX +#include +#endif + +void slirp_insque(void *a, void *b) +{ + register struct slirp_quehead *element = (struct slirp_quehead *)a; + register struct slirp_quehead *head = (struct slirp_quehead *)b; + element->qh_link = head->qh_link; + head->qh_link = (struct slirp_quehead *)element; + element->qh_rlink = (struct slirp_quehead *)head; + ((struct slirp_quehead *)(element->qh_link))->qh_rlink = + (struct slirp_quehead *)element; +} + +void slirp_remque(void *a) +{ + register struct slirp_quehead *element = (struct slirp_quehead *)a; + ((struct slirp_quehead *)(element->qh_link))->qh_rlink = element->qh_rlink; + ((struct slirp_quehead *)(element->qh_rlink))->qh_link = element->qh_link; + element->qh_rlink = NULL; +} + +/* TODO: IPv6 */ +struct gfwd_list *add_guestfwd(struct gfwd_list **ex_ptr, SlirpWriteCb write_cb, + void *opaque, struct in_addr addr, int port) +{ + struct gfwd_list *f = g_new0(struct gfwd_list, 1); + + f->write_cb = write_cb; + f->opaque = opaque; + f->ex_fport = port; + f->ex_addr = addr; + f->ex_next = *ex_ptr; + *ex_ptr = f; + + return f; +} + +struct gfwd_list *add_exec(struct gfwd_list **ex_ptr, const char *cmdline, + struct in_addr addr, int port) +{ + struct gfwd_list *f = add_guestfwd(ex_ptr, NULL, NULL, addr, port); + + f->ex_exec = g_strdup(cmdline); + + return f; +} + +struct gfwd_list *add_unix(struct gfwd_list **ex_ptr, const char *unixsock, + struct in_addr addr, int port) +{ + struct gfwd_list *f = add_guestfwd(ex_ptr, NULL, NULL, addr, port); + + f->ex_unix = g_strdup(unixsock); + + return f; +} + +int remove_guestfwd(struct gfwd_list **ex_ptr, struct in_addr addr, int port) +{ + for (; *ex_ptr != NULL; ex_ptr = &((*ex_ptr)->ex_next)) { + struct gfwd_list *f = *ex_ptr; + if (f->ex_addr.s_addr == addr.s_addr && f->ex_fport == port) { + *ex_ptr = f->ex_next; + g_free(f->ex_exec); + g_free(f); + return 0; + } + } + return -1; +} + +static int slirp_socketpair_with_oob(int sv[2]) +{ + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = 0, + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), + }; + socklen_t addrlen = sizeof(addr); + int ret, s; + + sv[1] = -1; + s = slirp_socket(AF_INET, SOCK_STREAM, 0); + if (s < 0 || bind(s, (struct sockaddr *)&addr, addrlen) < 0 || + listen(s, 1) < 0 || + getsockname(s, (struct sockaddr *)&addr, &addrlen) < 0) { + goto err; + } + + sv[1] = slirp_socket(AF_INET, SOCK_STREAM, 0); + if (sv[1] < 0) { + goto err; + } + /* + * This connect won't block because we've already listen()ed on + * the server end (even though we won't accept() the connection + * until later on). + */ + do { + ret = connect(sv[1], (struct sockaddr *)&addr, addrlen); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + goto err; + } + + do { + sv[0] = accept(s, (struct sockaddr *)&addr, &addrlen); + } while (sv[0] < 0 && errno == EINTR); + if (sv[0] < 0) { + goto err; + } + + closesocket(s); + return 0; + +err: + g_critical("slirp_socketpair(): %s", strerror(errno)); + if (s >= 0) { + closesocket(s); + } + if (sv[1] >= 0) { + closesocket(sv[1]); + } + return -1; +} + +static void fork_exec_child_setup(gpointer data) +{ +#ifndef _WIN32 + setsid(); + + /* Unblock all signals and leave our exec()-ee to block what it wants */ + sigset_t ss; + sigemptyset(&ss); + sigprocmask(SIG_SETMASK, &ss, NULL); + + /* POSIX is obnoxious about SIGCHLD specifically across exec() */ + signal(SIGCHLD, SIG_DFL); +#endif +} + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#if !GLIB_CHECK_VERSION(2, 58, 0) +typedef struct SlirpGSpawnFds { + GSpawnChildSetupFunc child_setup; + gpointer user_data; + gint stdin_fd; + gint stdout_fd; + gint stderr_fd; +} SlirpGSpawnFds; + +static inline void slirp_gspawn_fds_setup(gpointer user_data) +{ + SlirpGSpawnFds *q = (SlirpGSpawnFds *)user_data; + + dup2(q->stdin_fd, 0); + dup2(q->stdout_fd, 1); + dup2(q->stderr_fd, 2); + q->child_setup(q->user_data); +} +#endif + +static inline gboolean +g_spawn_async_with_fds_slirp(const gchar *working_directory, gchar **argv, + gchar **envp, GSpawnFlags flags, + GSpawnChildSetupFunc child_setup, + gpointer user_data, GPid *child_pid, gint stdin_fd, + gint stdout_fd, gint stderr_fd, GError **error) +{ +#if GLIB_CHECK_VERSION(2, 58, 0) + return g_spawn_async_with_fds(working_directory, argv, envp, flags, + child_setup, user_data, child_pid, stdin_fd, + stdout_fd, stderr_fd, error); +#else + SlirpGSpawnFds setup = { + .child_setup = child_setup, + .user_data = user_data, + .stdin_fd = stdin_fd, + .stdout_fd = stdout_fd, + .stderr_fd = stderr_fd, + }; + + return g_spawn_async(working_directory, argv, envp, flags, + slirp_gspawn_fds_setup, &setup, child_pid, error); +#endif +} + +#define g_spawn_async_with_fds(wd, argv, env, f, c, d, p, ifd, ofd, efd, err) \ + g_spawn_async_with_fds_slirp(wd, argv, env, f, c, d, p, ifd, ofd, efd, err) + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +int fork_exec(struct socket *so, const char *ex) +{ + GError *err = NULL; + gint argc = 0; + gchar **argv = NULL; + int opt, sp[2]; + + DEBUG_CALL("fork_exec"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("ex = %p", ex); + + if (slirp_socketpair_with_oob(sp) < 0) { + return 0; + } + + if (!g_shell_parse_argv(ex, &argc, &argv, &err)) { + g_critical("fork_exec invalid command: %s\nerror: %s", ex, err->message); + g_error_free(err); + return 0; + } + + g_spawn_async_with_fds(NULL /* cwd */, argv, NULL /* env */, + G_SPAWN_SEARCH_PATH, fork_exec_child_setup, + NULL /* data */, NULL /* child_pid */, sp[1], sp[1], + sp[1], &err); + g_strfreev(argv); + + if (err) { + g_critical("fork_exec: %s", err->message); + g_error_free(err); + closesocket(sp[0]); + closesocket(sp[1]); + return 0; + } + + so->s = sp[0]; + closesocket(sp[1]); + slirp_socket_set_fast_reuse(so->s); + opt = 1; + setsockopt(so->s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(int)); + slirp_set_nonblock(so->s); + so->slirp->cb->register_poll_fd(so->s, so->slirp->opaque); + return 1; +} + +int open_unix(struct socket *so, const char *unixpath) +{ +#ifdef G_OS_UNIX + struct sockaddr_un sa; + int s; + + DEBUG_CALL("open_unix"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("unixpath = %s", unixpath); + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + if (g_strlcpy(sa.sun_path, unixpath, sizeof(sa.sun_path)) >= sizeof(sa.sun_path)) { + g_critical("Bad unix path: %s", unixpath); + return 0; + } + + s = slirp_socket(PF_UNIX, SOCK_STREAM, 0); + if (s < 0) { + g_critical("open_unix(): %s", strerror(errno)); + return 0; + } + + if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) { + g_critical("open_unix(): %s", strerror(errno)); + closesocket(s); + return 0; + } + + so->s = s; + slirp_set_nonblock(so->s); + so->slirp->cb->register_poll_fd(so->s, so->slirp->opaque); + + return 1; +#else + g_assert_not_reached(); +#endif +} + +char *slirp_connection_info(Slirp *slirp) +{ + GString *str = g_string_new(NULL); + const char *const tcpstates[] = { + [TCPS_CLOSED] = "CLOSED", [TCPS_LISTEN] = "LISTEN", + [TCPS_SYN_SENT] = "SYN_SENT", [TCPS_SYN_RECEIVED] = "SYN_RCVD", + [TCPS_ESTABLISHED] = "ESTABLISHED", [TCPS_CLOSE_WAIT] = "CLOSE_WAIT", + [TCPS_FIN_WAIT_1] = "FIN_WAIT_1", [TCPS_CLOSING] = "CLOSING", + [TCPS_LAST_ACK] = "LAST_ACK", [TCPS_FIN_WAIT_2] = "FIN_WAIT_2", + [TCPS_TIME_WAIT] = "TIME_WAIT", + }; + struct in_addr dst_addr; + struct sockaddr_in src; + socklen_t src_len; + uint16_t dst_port; + struct socket *so; + const char *state; + char addr[INET_ADDRSTRLEN]; + char buf[20]; + + g_string_append_printf(str, + " Protocol[State] FD Source Address Port " + "Dest. Address Port RecvQ SendQ\n"); + + /* TODO: IPv6 */ + + for (so = slirp->tcb.so_next; so != &slirp->tcb; so = so->so_next) { + if (so->so_state & SS_HOSTFWD) { + state = "HOST_FORWARD"; + } else if (so->so_tcpcb) { + state = tcpstates[so->so_tcpcb->t_state]; + } else { + state = "NONE"; + } + if (so->so_state & (SS_HOSTFWD | SS_INCOMING)) { + src_len = sizeof(src); + getsockname(so->s, (struct sockaddr *)&src, &src_len); + dst_addr = so->so_laddr; + dst_port = so->so_lport; + } else { + src.sin_addr = so->so_laddr; + src.sin_port = so->so_lport; + dst_addr = so->so_faddr; + dst_port = so->so_fport; + } + slirp_fmt0(buf, sizeof(buf), " TCP[%s]", state); + g_string_append_printf(str, "%-19s %3d %15s %5d ", buf, so->s, + src.sin_addr.s_addr ? + inet_ntop(AF_INET, &src.sin_addr, addr, sizeof(addr)) : "*", + ntohs(src.sin_port)); + g_string_append_printf(str, "%15s %5d %5d %5d\n", + inet_ntop(AF_INET, &dst_addr, addr, sizeof(addr)), + ntohs(dst_port), so->so_rcv.sb_cc, + so->so_snd.sb_cc); + } + + for (so = slirp->udb.so_next; so != &slirp->udb; so = so->so_next) { + if (so->so_state & SS_HOSTFWD) { + slirp_fmt0(buf, sizeof(buf), " UDP[HOST_FORWARD]"); + src_len = sizeof(src); + getsockname(so->s, (struct sockaddr *)&src, &src_len); + dst_addr = so->so_laddr; + dst_port = so->so_lport; + } else { + slirp_fmt0(buf, sizeof(buf), " UDP[%d sec]", + (so->so_expire - curtime) / 1000); + src.sin_addr = so->so_laddr; + src.sin_port = so->so_lport; + dst_addr = so->so_faddr; + dst_port = so->so_fport; + } + g_string_append_printf(str, "%-19s %3d %15s %5d ", buf, so->s, + src.sin_addr.s_addr ? + inet_ntop(AF_INET, &src.sin_addr, addr, sizeof(addr)) : "*", + ntohs(src.sin_port)); + g_string_append_printf(str, "%15s %5d %5d %5d\n", + inet_ntop(AF_INET, &dst_addr, addr, sizeof(addr)), + ntohs(dst_port), so->so_rcv.sb_cc, + so->so_snd.sb_cc); + } + + for (so = slirp->icmp.so_next; so != &slirp->icmp; so = so->so_next) { + slirp_fmt0(buf, sizeof(buf), " ICMP[%d sec]", + (so->so_expire - curtime) / 1000); + src.sin_addr = so->so_laddr; + dst_addr = so->so_faddr; + g_string_append_printf(str, "%-19s %3d %15s - ", buf, so->s, + src.sin_addr.s_addr ? + inet_ntop(AF_INET, &src.sin_addr, addr, sizeof(addr)) : "*"); + g_string_append_printf(str, "%15s - %5d %5d\n", + inet_ntop(AF_INET, &dst_addr, addr, sizeof(addr)), + so->so_rcv.sb_cc, so->so_snd.sb_cc); + } + + return g_string_free(str, FALSE); +} + +char *slirp_neighbor_info(Slirp *slirp) +{ + GString *str = g_string_new(NULL); + ArpTable *arp_table = &slirp->arp_table; + NdpTable *ndp_table = &slirp->ndp_table; + char ip_addr[INET6_ADDRSTRLEN]; + char eth_addr[ETH_ADDRSTRLEN]; + const char *ip; + + g_string_append_printf(str, " %5s %-17s %s\n", + "Table", "MacAddr", "IP Address"); + + for (int i = 0; i < ARP_TABLE_SIZE; ++i) { + struct in_addr addr; + addr.s_addr = arp_table->table[i].ar_sip; + if (!addr.s_addr) { + continue; + } + ip = inet_ntop(AF_INET, &addr, ip_addr, sizeof(ip_addr)); + g_assert(ip != NULL); + g_string_append_printf(str, " %5s %-17s %s\n", "ARP", + slirp_ether_ntoa(arp_table->table[i].ar_sha, + eth_addr, sizeof(eth_addr)), + ip); + } + + for (int i = 0; i < NDP_TABLE_SIZE; ++i) { + if (in6_zero(&ndp_table->table[i].ip_addr)) { + continue; + } + ip = inet_ntop(AF_INET6, &ndp_table->table[i].ip_addr, ip_addr, + sizeof(ip_addr)); + g_assert(ip != NULL); + g_string_append_printf(str, " %5s %-17s %s\n", "NDP", + slirp_ether_ntoa(ndp_table->table[i].eth_addr, + eth_addr, sizeof(eth_addr)), + ip); + } + + return g_string_free(str, FALSE); +} + +int slirp_bind_outbound(struct socket *so, unsigned short af) +{ + int ret = 0; + struct sockaddr *addr = NULL; + int addr_size = 0; + + if (af == AF_INET && so->slirp->outbound_addr != NULL) { + addr = (struct sockaddr *)so->slirp->outbound_addr; + addr_size = sizeof(struct sockaddr_in); + } else if (af == AF_INET6 && so->slirp->outbound_addr6 != NULL) { + addr = (struct sockaddr *)so->slirp->outbound_addr6; + addr_size = sizeof(struct sockaddr_in6); + } + + if (addr != NULL) { + ret = bind(so->s, addr, addr_size); + } + return ret; +} diff --git a/app/src/main/cpp/libslirp/src/misc.h b/app/src/main/cpp/libslirp/src/misc.h new file mode 100644 index 00000000..39fe367a --- /dev/null +++ b/app/src/main/cpp/libslirp/src/misc.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#ifndef MISC_H +#define MISC_H + +#include "libslirp.h" + +struct gfwd_list { + SlirpWriteCb write_cb; + void *opaque; + struct in_addr ex_addr; /* Server address */ + int ex_fport; /* Port to telnet to */ + char *ex_exec; /* Command line of what to exec */ + char *ex_unix; /* unix socket */ + struct gfwd_list *ex_next; +}; + +#define EMU_NONE 0x0 + +/* TCP emulations */ +#define EMU_CTL 0x1 +#define EMU_FTP 0x2 +#define EMU_KSH 0x3 +#define EMU_IRC 0x4 +#define EMU_REALAUDIO 0x5 +#define EMU_RLOGIN 0x6 +#define EMU_IDENT 0x7 + +#define EMU_NOCONNECT 0x10 /* Don't connect */ + +struct tos_t { + uint16_t lport; + uint16_t fport; + uint8_t tos; + uint8_t emu; +}; + +struct emu_t { + uint16_t lport; + uint16_t fport; + uint8_t tos; + uint8_t emu; + struct emu_t *next; +}; + +struct slirp_quehead { + struct slirp_quehead *qh_link; + struct slirp_quehead *qh_rlink; +}; + +/* Insert element a into queue b */ +void slirp_insque(void *a, void *b); + +/* Remove element a from its queue */ +void slirp_remque(void *a); + +/* Run the given command in the background, and expose its output as a socket */ +int fork_exec(struct socket *so, const char *ex); + +/* Create a Unix socket, and expose it as a socket */ +int open_unix(struct socket *so, const char *unixsock); + +/* Add a guest forward on the given address and port, with guest data being + * forwarded by calling write_cb */ +struct gfwd_list *add_guestfwd(struct gfwd_list **ex_ptr, SlirpWriteCb write_cb, + void *opaque, struct in_addr addr, int port); + +/* Run the given command in the backaground, and send its output to the guest on + * the given address and port */ +struct gfwd_list *add_exec(struct gfwd_list **ex_ptr, const char *cmdline, + struct in_addr addr, int port); + +/* Create a Unix socket, and expose it to the guest on the given address and + * port */ +struct gfwd_list *add_unix(struct gfwd_list **ex_ptr, const char *unixsock, + struct in_addr addr, int port); + +/* Remove the guest forward bound to the given address and port */ +int remove_guestfwd(struct gfwd_list **ex_ptr, struct in_addr addr, int port); + +/* Bind the socket to the outbound address specified in the slirp configuration */ +int slirp_bind_outbound(struct socket *so, unsigned short af); + +#endif diff --git a/app/src/main/cpp/libslirp/src/ncsi-pkt.h b/app/src/main/cpp/libslirp/src/ncsi-pkt.h new file mode 100644 index 00000000..27bedf6a --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ncsi-pkt.h @@ -0,0 +1,527 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright Gavin Shan, IBM Corporation 2016. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NCSI_PKT_H +#define NCSI_PKT_H + +/* from linux/net/ncsi/ncsi-pkt.h */ +#define __be32 uint32_t +#define __be16 uint16_t + +SLIRP_PACKED_BEGIN +struct ncsi_pkt_hdr { + unsigned char mc_id; /* Management controller ID */ + unsigned char revision; /* NCSI version - 0x01 */ + unsigned char reserved; /* Reserved */ + unsigned char id; /* Packet sequence number */ + unsigned char type; /* Packet type */ + unsigned char channel; /* Network controller ID */ + __be16 length; /* Payload length */ + __be32 reserved1[2]; /* Reserved */ +} SLIRP_PACKED_END; + +SLIRP_PACKED_BEGIN +struct ncsi_cmd_pkt_hdr { + struct ncsi_pkt_hdr common; /* Common NCSI packet header */ +} SLIRP_PACKED_END; + +SLIRP_PACKED_BEGIN +struct ncsi_rsp_pkt_hdr { + struct ncsi_pkt_hdr common; /* Common NCSI packet header */ + __be16 code; /* Response code */ + __be16 reason; /* Response reason */ +} SLIRP_PACKED_END; + +SLIRP_PACKED_BEGIN +struct ncsi_aen_pkt_hdr { + struct ncsi_pkt_hdr common; /* Common NCSI packet header */ + unsigned char reserved2[3]; /* Reserved */ + unsigned char type; /* AEN packet type */ +} SLIRP_PACKED_END; + +/* NCSI common command packet */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 checksum; /* Checksum */ + unsigned char pad[26]; +} SLIRP_PACKED_END; + +SLIRP_PACKED_BEGIN +struct ncsi_rsp_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* Select Package */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_sp_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char hw_arbitration; /* HW arbitration */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* Disable Channel */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_dc_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char ald; /* Allow link down */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* Reset Channel */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_rc_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 reserved; /* Reserved */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* AEN Enable */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_ae_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char mc_id; /* MC ID */ + __be32 mode; /* AEN working mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[18]; +} SLIRP_PACKED_END; + +/* Set Link */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_sl_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 mode; /* Link working mode */ + __be32 oem_mode; /* OEM link mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[18]; +} SLIRP_PACKED_END; + +/* Set VLAN Filter */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_svf_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be16 reserved; /* Reserved */ + __be16 vlan; /* VLAN ID */ + __be16 reserved1; /* Reserved */ + unsigned char index; /* VLAN table index */ + unsigned char enable; /* Enable or disable */ + __be32 checksum; /* Checksum */ + unsigned char pad[14]; +} SLIRP_PACKED_END; + +/* Enable VLAN */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_ev_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char mode; /* VLAN filter mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* Set MAC Address */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_sma_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char mac[6]; /* MAC address */ + unsigned char index; /* MAC table index */ + unsigned char at_e; /* Addr type and operation */ + __be32 checksum; /* Checksum */ + unsigned char pad[18]; +} SLIRP_PACKED_END; + +/* Enable Broadcast Filter */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_ebf_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 mode; /* Filter mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* Enable Global Multicast Filter */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_egmf_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 mode; /* Global MC mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* Set NCSI Flow Control */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_snfc_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char mode; /* Flow control mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* OEM Request Command as per NCSI Specification */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_oem_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 mfr_id; /* Manufacture ID */ + unsigned char data[]; /* OEM Payload Data */ +} SLIRP_PACKED_END; + +/* OEM Response Packet as per NCSI Specification */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_oem_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Command header */ + __be32 mfr_id; /* Manufacture ID */ + unsigned char data[]; /* Payload data */ +} SLIRP_PACKED_END; + +/* Mellanox Response Data */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_oem_mlx_pkt { + unsigned char cmd_rev; /* Command Revision */ + unsigned char cmd; /* Command ID */ + unsigned char param; /* Parameter */ + unsigned char optional; /* Optional data */ + unsigned char data[]; /* Data */ +} SLIRP_PACKED_END; + +/* Get Link Status */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gls_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 status; /* Link status */ + __be32 other; /* Other indications */ + __be32 oem_status; /* OEM link status */ + __be32 checksum; + unsigned char pad[10]; +} SLIRP_PACKED_END; + +/* Get Version ID */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gvi_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 ncsi_version; /* NCSI version */ + unsigned char reserved[3]; /* Reserved */ + unsigned char alpha2; /* NCSI version */ + unsigned char fw_name[12]; /* f/w name string */ + __be32 fw_version; /* f/w version */ + __be16 pci_ids[4]; /* PCI IDs */ + __be32 mf_id; /* Manufacture ID */ + __be32 checksum; +} SLIRP_PACKED_END; + +/* Get Capabilities */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gc_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 cap; /* Capabilities */ + __be32 bc_cap; /* Broadcast cap */ + __be32 mc_cap; /* Multicast cap */ + __be32 buf_cap; /* Buffering cap */ + __be32 aen_cap; /* AEN cap */ + unsigned char vlan_cnt; /* VLAN filter count */ + unsigned char mixed_cnt; /* Mix filter count */ + unsigned char mc_cnt; /* MC filter count */ + unsigned char uc_cnt; /* UC filter count */ + unsigned char reserved[2]; /* Reserved */ + unsigned char vlan_mode; /* VLAN mode */ + unsigned char channel_cnt; /* Channel count */ + __be32 checksum; /* Checksum */ +} SLIRP_PACKED_END; + +/* Get Parameters */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gp_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + unsigned char mac_cnt; /* Number of MAC addr */ + unsigned char reserved[2]; /* Reserved */ + unsigned char mac_enable; /* MAC addr enable flags */ + unsigned char vlan_cnt; /* VLAN tag count */ + unsigned char reserved1; /* Reserved */ + __be16 vlan_enable; /* VLAN tag enable flags */ + __be32 link_mode; /* Link setting */ + __be32 bc_mode; /* BC filter mode */ + __be32 valid_modes; /* Valid mode parameters */ + unsigned char vlan_mode; /* VLAN mode */ + unsigned char fc_mode; /* Flow control mode */ + unsigned char reserved2[2]; /* Reserved */ + __be32 aen_mode; /* AEN mode */ + unsigned char mac[6]; /* Supported MAC addr */ + __be16 vlan; /* Supported VLAN tags */ + __be32 checksum; /* Checksum */ +} SLIRP_PACKED_END; + +/* Get Controller Packet Statistics */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gcps_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 cnt_hi; /* Counter cleared */ + __be32 cnt_lo; /* Counter cleared */ + __be32 rx_bytes; /* Rx bytes */ + __be32 tx_bytes; /* Tx bytes */ + __be32 rx_uc_pkts; /* Rx UC packets */ + __be32 rx_mc_pkts; /* Rx MC packets */ + __be32 rx_bc_pkts; /* Rx BC packets */ + __be32 tx_uc_pkts; /* Tx UC packets */ + __be32 tx_mc_pkts; /* Tx MC packets */ + __be32 tx_bc_pkts; /* Tx BC packets */ + __be32 fcs_err; /* FCS errors */ + __be32 align_err; /* Alignment errors */ + __be32 false_carrier; /* False carrier detection */ + __be32 runt_pkts; /* Rx runt packets */ + __be32 jabber_pkts; /* Rx jabber packets */ + __be32 rx_pause_xon; /* Rx pause XON frames */ + __be32 rx_pause_xoff; /* Rx XOFF frames */ + __be32 tx_pause_xon; /* Tx XON frames */ + __be32 tx_pause_xoff; /* Tx XOFF frames */ + __be32 tx_s_collision; /* Single collision frames */ + __be32 tx_m_collision; /* Multiple collision frames */ + __be32 l_collision; /* Late collision frames */ + __be32 e_collision; /* Excessive collision frames */ + __be32 rx_ctl_frames; /* Rx control frames */ + __be32 rx_64_frames; /* Rx 64-bytes frames */ + __be32 rx_127_frames; /* Rx 65-127 bytes frames */ + __be32 rx_255_frames; /* Rx 128-255 bytes frames */ + __be32 rx_511_frames; /* Rx 256-511 bytes frames */ + __be32 rx_1023_frames; /* Rx 512-1023 bytes frames */ + __be32 rx_1522_frames; /* Rx 1024-1522 bytes frames */ + __be32 rx_9022_frames; /* Rx 1523-9022 bytes frames */ + __be32 tx_64_frames; /* Tx 64-bytes frames */ + __be32 tx_127_frames; /* Tx 65-127 bytes frames */ + __be32 tx_255_frames; /* Tx 128-255 bytes frames */ + __be32 tx_511_frames; /* Tx 256-511 bytes frames */ + __be32 tx_1023_frames; /* Tx 512-1023 bytes frames */ + __be32 tx_1522_frames; /* Tx 1024-1522 bytes frames */ + __be32 tx_9022_frames; /* Tx 1523-9022 bytes frames */ + __be32 rx_valid_bytes; /* Rx valid bytes */ + __be32 rx_runt_pkts; /* Rx error runt packets */ + __be32 rx_jabber_pkts; /* Rx error jabber packets */ + __be32 checksum; /* Checksum */ +} SLIRP_PACKED_END; + +/* Get NCSI Statistics */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gns_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 rx_cmds; /* Rx NCSI commands */ + __be32 dropped_cmds; /* Dropped commands */ + __be32 cmd_type_errs; /* Command type errors */ + __be32 cmd_csum_errs; /* Command checksum errors */ + __be32 rx_pkts; /* Rx NCSI packets */ + __be32 tx_pkts; /* Tx NCSI packets */ + __be32 tx_aen_pkts; /* Tx AEN packets */ + __be32 checksum; /* Checksum */ +} SLIRP_PACKED_END; + +/* Get NCSI Pass-through Statistics */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gnpts_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 tx_pkts; /* Tx packets */ + __be32 tx_dropped; /* Tx dropped packets */ + __be32 tx_channel_err; /* Tx channel errors */ + __be32 tx_us_err; /* Tx undersize errors */ + __be32 rx_pkts; /* Rx packets */ + __be32 rx_dropped; /* Rx dropped packets */ + __be32 rx_channel_err; /* Rx channel errors */ + __be32 rx_us_err; /* Rx undersize errors */ + __be32 rx_os_err; /* Rx oversize errors */ + __be32 checksum; /* Checksum */ +} SLIRP_PACKED_END; + +/* Get package status */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gps_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 status; /* Hardware arbitration status */ + __be32 checksum; +} SLIRP_PACKED_END; + +/* Get package UUID */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gpuuid_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + unsigned char uuid[16]; /* UUID */ + __be32 checksum; +} SLIRP_PACKED_END; + +/* AEN: Link State Change */ +SLIRP_PACKED_BEGIN +struct ncsi_aen_lsc_pkt { + struct ncsi_aen_pkt_hdr aen; /* AEN header */ + __be32 status; /* Link status */ + __be32 oem_status; /* OEM link status */ + __be32 checksum; /* Checksum */ + unsigned char pad[14]; +} SLIRP_PACKED_END; + +/* AEN: Configuration Required */ +SLIRP_PACKED_BEGIN +struct ncsi_aen_cr_pkt { + struct ncsi_aen_pkt_hdr aen; /* AEN header */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* AEN: Host Network Controller Driver Status Change */ +SLIRP_PACKED_BEGIN +struct ncsi_aen_hncdsc_pkt { + struct ncsi_aen_pkt_hdr aen; /* AEN header */ + __be32 status; /* Status */ + __be32 checksum; /* Checksum */ + unsigned char pad[18]; +} SLIRP_PACKED_END; + +/* NCSI packet revision */ +#define NCSI_PKT_REVISION 0x01 + +/* NCSI packet commands */ +#define NCSI_PKT_CMD_CIS 0x00 /* Clear Initial State */ +#define NCSI_PKT_CMD_SP 0x01 /* Select Package */ +#define NCSI_PKT_CMD_DP 0x02 /* Deselect Package */ +#define NCSI_PKT_CMD_EC 0x03 /* Enable Channel */ +#define NCSI_PKT_CMD_DC 0x04 /* Disable Channel */ +#define NCSI_PKT_CMD_RC 0x05 /* Reset Channel */ +#define NCSI_PKT_CMD_ECNT 0x06 /* Enable Channel Network Tx */ +#define NCSI_PKT_CMD_DCNT 0x07 /* Disable Channel Network Tx */ +#define NCSI_PKT_CMD_AE 0x08 /* AEN Enable */ +#define NCSI_PKT_CMD_SL 0x09 /* Set Link */ +#define NCSI_PKT_CMD_GLS 0x0a /* Get Link */ +#define NCSI_PKT_CMD_SVF 0x0b /* Set VLAN Filter */ +#define NCSI_PKT_CMD_EV 0x0c /* Enable VLAN */ +#define NCSI_PKT_CMD_DV 0x0d /* Disable VLAN */ +#define NCSI_PKT_CMD_SMA 0x0e /* Set MAC address */ +#define NCSI_PKT_CMD_EBF 0x10 /* Enable Broadcast Filter */ +#define NCSI_PKT_CMD_DBF 0x11 /* Disable Broadcast Filter */ +#define NCSI_PKT_CMD_EGMF 0x12 /* Enable Global Multicast Filter */ +#define NCSI_PKT_CMD_DGMF 0x13 /* Disable Global Multicast Filter */ +#define NCSI_PKT_CMD_SNFC 0x14 /* Set NCSI Flow Control */ +#define NCSI_PKT_CMD_GVI 0x15 /* Get Version ID */ +#define NCSI_PKT_CMD_GC 0x16 /* Get Capabilities */ +#define NCSI_PKT_CMD_GP 0x17 /* Get Parameters */ +#define NCSI_PKT_CMD_GCPS 0x18 /* Get Controller Packet Statistics */ +#define NCSI_PKT_CMD_GNS 0x19 /* Get NCSI Statistics */ +#define NCSI_PKT_CMD_GNPTS 0x1a /* Get NCSI Pass-throu Statistics */ +#define NCSI_PKT_CMD_GPS 0x1b /* Get package status */ +#define NCSI_PKT_CMD_OEM 0x50 /* OEM */ +#define NCSI_PKT_CMD_PLDM 0x51 /* PLDM request over NCSI over RBT */ +#define NCSI_PKT_CMD_GPUUID 0x52 /* Get package UUID */ + +/* NCSI packet responses */ +#define NCSI_PKT_RSP_CIS (NCSI_PKT_CMD_CIS + 0x80) +#define NCSI_PKT_RSP_SP (NCSI_PKT_CMD_SP + 0x80) +#define NCSI_PKT_RSP_DP (NCSI_PKT_CMD_DP + 0x80) +#define NCSI_PKT_RSP_EC (NCSI_PKT_CMD_EC + 0x80) +#define NCSI_PKT_RSP_DC (NCSI_PKT_CMD_DC + 0x80) +#define NCSI_PKT_RSP_RC (NCSI_PKT_CMD_RC + 0x80) +#define NCSI_PKT_RSP_ECNT (NCSI_PKT_CMD_ECNT + 0x80) +#define NCSI_PKT_RSP_DCNT (NCSI_PKT_CMD_DCNT + 0x80) +#define NCSI_PKT_RSP_AE (NCSI_PKT_CMD_AE + 0x80) +#define NCSI_PKT_RSP_SL (NCSI_PKT_CMD_SL + 0x80) +#define NCSI_PKT_RSP_GLS (NCSI_PKT_CMD_GLS + 0x80) +#define NCSI_PKT_RSP_SVF (NCSI_PKT_CMD_SVF + 0x80) +#define NCSI_PKT_RSP_EV (NCSI_PKT_CMD_EV + 0x80) +#define NCSI_PKT_RSP_DV (NCSI_PKT_CMD_DV + 0x80) +#define NCSI_PKT_RSP_SMA (NCSI_PKT_CMD_SMA + 0x80) +#define NCSI_PKT_RSP_EBF (NCSI_PKT_CMD_EBF + 0x80) +#define NCSI_PKT_RSP_DBF (NCSI_PKT_CMD_DBF + 0x80) +#define NCSI_PKT_RSP_EGMF (NCSI_PKT_CMD_EGMF + 0x80) +#define NCSI_PKT_RSP_DGMF (NCSI_PKT_CMD_DGMF + 0x80) +#define NCSI_PKT_RSP_SNFC (NCSI_PKT_CMD_SNFC + 0x80) +#define NCSI_PKT_RSP_GVI (NCSI_PKT_CMD_GVI + 0x80) +#define NCSI_PKT_RSP_GC (NCSI_PKT_CMD_GC + 0x80) +#define NCSI_PKT_RSP_GP (NCSI_PKT_CMD_GP + 0x80) +#define NCSI_PKT_RSP_GCPS (NCSI_PKT_CMD_GCPS + 0x80) +#define NCSI_PKT_RSP_GNS (NCSI_PKT_CMD_GNS + 0x80) +#define NCSI_PKT_RSP_GNPTS (NCSI_PKT_CMD_GNPTS + 0x80) +#define NCSI_PKT_RSP_GPS (NCSI_PKT_CMD_GPS + 0x80) +#define NCSI_PKT_RSP_OEM (NCSI_PKT_CMD_OEM + 0x80) +#define NCSI_PKT_RSP_PLDM (NCSI_PKT_CMD_PLDM + 0x80) +#define NCSI_PKT_RSP_GPUUID (NCSI_PKT_CMD_GPUUID + 0x80) + +/* NCSI response code/reason */ +#define NCSI_PKT_RSP_C_COMPLETED 0x0000 /* Command Completed */ +#define NCSI_PKT_RSP_C_FAILED 0x0001 /* Command Failed */ +#define NCSI_PKT_RSP_C_UNAVAILABLE 0x0002 /* Command Unavailable */ +#define NCSI_PKT_RSP_C_UNSUPPORTED 0x0003 /* Command Unsupported */ +#define NCSI_PKT_RSP_R_NO_ERROR 0x0000 /* No Error */ +#define NCSI_PKT_RSP_R_INTERFACE 0x0001 /* Interface not ready */ +#define NCSI_PKT_RSP_R_PARAM 0x0002 /* Invalid Parameter */ +#define NCSI_PKT_RSP_R_CHANNEL 0x0003 /* Channel not Ready */ +#define NCSI_PKT_RSP_R_PACKAGE 0x0004 /* Package not Ready */ +#define NCSI_PKT_RSP_R_LENGTH 0x0005 /* Invalid payload length */ +#define NCSI_PKT_RSP_R_UNKNOWN 0x7fff /* Command type unsupported */ + +/* NCSI AEN packet type */ +#define NCSI_PKT_AEN 0xFF /* AEN Packet */ +#define NCSI_PKT_AEN_LSC 0x00 /* Link status change */ +#define NCSI_PKT_AEN_CR 0x01 /* Configuration required */ +#define NCSI_PKT_AEN_HNCDSC 0x02 /* HNC driver status change */ + +/* OEM Vendor Manufacture ID */ +#define NCSI_OEM_MFR_MLX_ID 0x8119 +#define NCSI_OEM_MFR_BCM_ID 0x113d +#define NCSI_OEM_MFR_INTEL_ID 0x157 +/* Intel specific OEM command */ +#define NCSI_OEM_INTEL_CMD_GMA 0x06 /* CMD ID for Get MAC */ +#define NCSI_OEM_INTEL_CMD_KEEP_PHY 0x20 /* CMD ID for Keep PHY up */ +/* Broadcom specific OEM Command */ +#define NCSI_OEM_BCM_CMD_GMA 0x01 /* CMD ID for Get MAC */ +/* Mellanox specific OEM Command */ +#define NCSI_OEM_MLX_CMD_GMA 0x00 /* CMD ID for Get MAC */ +#define NCSI_OEM_MLX_CMD_GMA_PARAM 0x1b /* Parameter for GMA */ +#define NCSI_OEM_MLX_CMD_SMAF 0x01 /* CMD ID for Set MC Affinity */ +#define NCSI_OEM_MLX_CMD_SMAF_PARAM 0x07 /* Parameter for SMAF */ +/* Offset in OEM request */ +#define MLX_SMAF_MAC_ADDR_OFFSET 8 /* Offset for MAC in SMAF */ +#define MLX_SMAF_MED_SUPPORT_OFFSET 14 /* Offset for medium in SMAF */ +/* Mac address offset in OEM response */ +#define BCM_MAC_ADDR_OFFSET 28 +#define MLX_MAC_ADDR_OFFSET 8 +#define INTEL_MAC_ADDR_OFFSET 1 + +/* Status offset in OEM response */ +#define MLX_GMA_STATUS_OFFSET 0 +/* OEM Response payload length */ +#define MLX_GMA_PAYLOAD_LEN 24 + +#endif /* NCSI_PKT_H */ diff --git a/app/src/main/cpp/libslirp/src/ncsi.c b/app/src/main/cpp/libslirp/src/ncsi.c new file mode 100644 index 00000000..846da9a4 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ncsi.c @@ -0,0 +1,326 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * NC-SI (Network Controller Sideband Interface) "echo" model + * + * Copyright (C) 2016-2018 IBM Corp. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "slirp.h" + +#include "ncsi-pkt.h" + +static uint32_t ncsi_calculate_checksum(uint8_t *data, int len) +{ + uint32_t checksum = 0; + int i; + + /* + * 32-bit unsigned sum of the NC-SI packet header and NC-SI packet + * payload interpreted as a series of 16-bit unsigned integer values. + */ + for (i = 0; i < len; i += 2) { + checksum += (((uint16_t) data[i]) << 8) + data[i+1]; + } + + checksum = (~checksum + 1); + return checksum; +} + +/* Response handler for Mellanox command Get Mac Address */ +static int ncsi_rsp_handler_oem_mlx_gma(Slirp *slirp, + const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + uint8_t oob_eth_addr_allocated = 0; + struct ncsi_rsp_oem_pkt *rsp; + int i; + + rsp = (struct ncsi_rsp_oem_pkt *)rnh; + + /* Set the payload length */ + rsp->rsp.common.length = htons(MLX_GMA_PAYLOAD_LEN); + + for (i = 0; i < ETH_ALEN; i++) { + if (slirp->oob_eth_addr[i] != 0x00) { + oob_eth_addr_allocated = 1; + break; + } + } + rsp->data[MLX_GMA_STATUS_OFFSET] = oob_eth_addr_allocated; + + /* Set the allocated management address */ + memcpy(&rsp->data[MLX_MAC_ADDR_OFFSET], slirp->oob_eth_addr, ETH_ALEN); + + return 0; +} + +/* Response handler for Mellanox card */ +static int ncsi_rsp_handler_oem_mlx(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + const struct ncsi_cmd_oem_pkt *cmd; + const struct ncsi_rsp_oem_mlx_pkt *cmd_mlx; + struct ncsi_rsp_oem_pkt *rsp; + struct ncsi_rsp_oem_mlx_pkt *rsp_mlx; + + /* Get the command header */ + cmd = (const struct ncsi_cmd_oem_pkt *)nh; + cmd_mlx = (const struct ncsi_rsp_oem_mlx_pkt *)cmd->data; + + /* Get the response header */ + rsp = (struct ncsi_rsp_oem_pkt *)rnh; + rsp_mlx = (struct ncsi_rsp_oem_mlx_pkt *)rsp->data; + + /* Ensure the OEM response header matches the command's */ + rsp_mlx->cmd_rev = cmd_mlx->cmd_rev; + rsp_mlx->cmd = cmd_mlx->cmd; + rsp_mlx->param = cmd_mlx->param; + rsp_mlx->optional = cmd_mlx->optional; + + if (cmd_mlx->cmd == NCSI_OEM_MLX_CMD_GMA && + cmd_mlx->param == NCSI_OEM_MLX_CMD_GMA_PARAM) + return ncsi_rsp_handler_oem_mlx_gma(slirp, nh, rnh); + + rsp->rsp.common.length = htons(8); + rsp->rsp.code = htons(NCSI_PKT_RSP_C_UNSUPPORTED); + rsp->rsp.reason = htons(NCSI_PKT_RSP_R_UNKNOWN); + return -ENOENT; +} + +static const struct ncsi_rsp_oem_handler { + unsigned int mfr_id; + int (*handler)(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh); +} ncsi_rsp_oem_handlers[] = { + { NCSI_OEM_MFR_MLX_ID, ncsi_rsp_handler_oem_mlx }, + { NCSI_OEM_MFR_BCM_ID, NULL }, + { NCSI_OEM_MFR_INTEL_ID, NULL }, +}; + +/* Response handler for OEM command */ +static int ncsi_rsp_handler_oem(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + const struct ncsi_rsp_oem_handler *nrh = NULL; + const struct ncsi_cmd_oem_pkt *cmd = (const struct ncsi_cmd_oem_pkt *)nh; + struct ncsi_rsp_oem_pkt *rsp = (struct ncsi_rsp_oem_pkt *)rnh; + uint32_t mfr_id = ntohl(cmd->mfr_id); + int i; + + rsp->mfr_id = cmd->mfr_id; + + if (mfr_id != slirp->mfr_id) { + goto error; + } + + /* Check for manufacturer id and Find the handler */ + for (i = 0; i < G_N_ELEMENTS(ncsi_rsp_oem_handlers); i++) { + if (ncsi_rsp_oem_handlers[i].mfr_id == mfr_id) { + if (ncsi_rsp_oem_handlers[i].handler) + nrh = &ncsi_rsp_oem_handlers[i]; + else + nrh = NULL; + + break; + } + } + + if (!nrh) { + goto error; + } + + /* Process the packet */ + return nrh->handler(slirp, nh, rnh); + +error: + rsp->rsp.common.length = htons(8); + rsp->rsp.code = htons(NCSI_PKT_RSP_C_UNSUPPORTED); + rsp->rsp.reason = htons(NCSI_PKT_RSP_R_UNKNOWN); + return -ENOENT; +} + + +/* Get Version ID */ +static int ncsi_rsp_handler_gvi(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + struct ncsi_rsp_gvi_pkt *rsp = (struct ncsi_rsp_gvi_pkt *)rnh; + + rsp->ncsi_version = htonl(0xF1F0F000); + rsp->mf_id = htonl(slirp->mfr_id); + + return 0; +} + +/* Get Capabilities */ +static int ncsi_rsp_handler_gc(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + struct ncsi_rsp_gc_pkt *rsp = (struct ncsi_rsp_gc_pkt *)rnh; + + rsp->cap = htonl(~0); + rsp->bc_cap = htonl(~0); + rsp->mc_cap = htonl(~0); + rsp->buf_cap = htonl(~0); + rsp->aen_cap = htonl(~0); + rsp->vlan_mode = 0xff; + rsp->uc_cnt = 2; + return 0; +} + +/* Get Link status */ +static int ncsi_rsp_handler_gls(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + struct ncsi_rsp_gls_pkt *rsp = (struct ncsi_rsp_gls_pkt *)rnh; + + rsp->status = htonl(0x1); + return 0; +} + +/* Get Parameters */ +static int ncsi_rsp_handler_gp(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + struct ncsi_rsp_gp_pkt *rsp = (struct ncsi_rsp_gp_pkt *)rnh; + + /* no MAC address filters or VLAN filters on the channel */ + rsp->mac_cnt = 0; + rsp->mac_enable = 0; + rsp->vlan_cnt = 0; + rsp->vlan_enable = 0; + + return 0; +} + +static const struct ncsi_rsp_handler { + unsigned char type; + int payload; + int (*handler)(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh); +} ncsi_rsp_handlers[] = { { NCSI_PKT_RSP_CIS, 4, NULL }, + { NCSI_PKT_RSP_SP, 4, NULL }, + { NCSI_PKT_RSP_DP, 4, NULL }, + { NCSI_PKT_RSP_EC, 4, NULL }, + { NCSI_PKT_RSP_DC, 4, NULL }, + { NCSI_PKT_RSP_RC, 4, NULL }, + { NCSI_PKT_RSP_ECNT, 4, NULL }, + { NCSI_PKT_RSP_DCNT, 4, NULL }, + { NCSI_PKT_RSP_AE, 4, NULL }, + { NCSI_PKT_RSP_SL, 4, NULL }, + { NCSI_PKT_RSP_GLS, 16, ncsi_rsp_handler_gls }, + { NCSI_PKT_RSP_SVF, 4, NULL }, + { NCSI_PKT_RSP_EV, 4, NULL }, + { NCSI_PKT_RSP_DV, 4, NULL }, + { NCSI_PKT_RSP_SMA, 4, NULL }, + { NCSI_PKT_RSP_EBF, 4, NULL }, + { NCSI_PKT_RSP_DBF, 4, NULL }, + { NCSI_PKT_RSP_EGMF, 4, NULL }, + { NCSI_PKT_RSP_DGMF, 4, NULL }, + { NCSI_PKT_RSP_SNFC, 4, NULL }, + { NCSI_PKT_RSP_GVI, 40, ncsi_rsp_handler_gvi }, + { NCSI_PKT_RSP_GC, 32, ncsi_rsp_handler_gc }, + { NCSI_PKT_RSP_GP, 40, ncsi_rsp_handler_gp }, + { NCSI_PKT_RSP_GCPS, 172, NULL }, + { NCSI_PKT_RSP_GNS, 172, NULL }, + { NCSI_PKT_RSP_GNPTS, 172, NULL }, + { NCSI_PKT_RSP_GPS, 8, NULL }, + { NCSI_PKT_RSP_OEM, 0, ncsi_rsp_handler_oem }, + { NCSI_PKT_RSP_PLDM, 0, NULL }, + { NCSI_PKT_RSP_GPUUID, 20, NULL } }; + +/* + * packet format : ncsi header + payload + checksum + */ +#define NCSI_MAX_PAYLOAD 172 +#define NCSI_MAX_LEN (sizeof(struct ncsi_pkt_hdr) + NCSI_MAX_PAYLOAD + 4) + +void ncsi_input(Slirp *slirp, const uint8_t *pkt, int pkt_len) +{ + const struct ncsi_pkt_hdr *nh = + (const struct ncsi_pkt_hdr *)(pkt + ETH_HLEN); + uint8_t ncsi_reply[2 + ETH_HLEN + NCSI_MAX_LEN]; + struct ethhdr *reh = (struct ethhdr *)(ncsi_reply + 2); + struct ncsi_rsp_pkt_hdr *rnh = + (struct ncsi_rsp_pkt_hdr *)(ncsi_reply + 2 + ETH_HLEN); + const struct ncsi_rsp_handler *handler = NULL; + int i; + int ncsi_rsp_len = sizeof(*nh); + uint32_t checksum; + uint32_t *pchecksum; + + if (pkt_len < ETH_HLEN + sizeof(struct ncsi_pkt_hdr)) { + return; /* packet too short */ + } + + memset(ncsi_reply, 0, sizeof(ncsi_reply)); + + memset(reh->h_dest, 0xff, ETH_ALEN); + memset(reh->h_source, 0xff, ETH_ALEN); + reh->h_proto = htons(ETH_P_NCSI); + + for (i = 0; i < G_N_ELEMENTS(ncsi_rsp_handlers); i++) { + if (ncsi_rsp_handlers[i].type == nh->type + 0x80) { + handler = &ncsi_rsp_handlers[i]; + break; + } + } + + rnh->common.mc_id = nh->mc_id; + rnh->common.revision = NCSI_PKT_REVISION; + rnh->common.id = nh->id; + rnh->common.type = nh->type + 0x80; + rnh->common.channel = nh->channel; + + if (handler) { + rnh->common.length = htons(handler->payload); + rnh->code = htons(NCSI_PKT_RSP_C_COMPLETED); + rnh->reason = htons(NCSI_PKT_RSP_R_NO_ERROR); + + if (handler->handler) { + handler->handler(slirp, nh, rnh); + } + ncsi_rsp_len += ntohs(rnh->common.length); + } else { + rnh->common.length = 0; + rnh->code = htons(NCSI_PKT_RSP_C_UNAVAILABLE); + rnh->reason = htons(NCSI_PKT_RSP_R_UNKNOWN); + } + + /* Add the optional checksum at the end of the frame. */ + checksum = ncsi_calculate_checksum((uint8_t *)rnh, ncsi_rsp_len); + pchecksum = (uint32_t *)((char *)rnh + ncsi_rsp_len); + *pchecksum = htonl(checksum); + ncsi_rsp_len += 4; + + slirp_send_packet_all(slirp, ncsi_reply + 2, ETH_HLEN + ncsi_rsp_len); +} diff --git a/app/src/main/cpp/libslirp/src/ndp_table.c b/app/src/main/cpp/libslirp/src/ndp_table.c new file mode 100644 index 00000000..fdb189d5 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/ndp_table.c @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#include "slirp.h" + +void ndp_table_add(Slirp *slirp, struct in6_addr ip_addr, + uint8_t ethaddr[ETH_ALEN]) +{ + char addrstr[INET6_ADDRSTRLEN]; + NdpTable *ndp_table = &slirp->ndp_table; + int i; + char ethaddr_str[ETH_ADDRSTRLEN]; + + inet_ntop(AF_INET6, &(ip_addr), addrstr, INET6_ADDRSTRLEN); + + DEBUG_CALL("ndp_table_add"); + DEBUG_ARG("ip = %s", addrstr); + DEBUG_ARG("hw addr = %s", slirp_ether_ntoa(ethaddr, ethaddr_str, + sizeof(ethaddr_str))); + + if (IN6_IS_ADDR_MULTICAST(&ip_addr) || in6_zero(&ip_addr)) { + /* Do not register multicast or unspecified addresses */ + DEBUG_CALL(" abort: do not register multicast or unspecified address"); + return; + } + + /* Search for an entry */ + for (i = 0; i < NDP_TABLE_SIZE; i++) { + if (in6_equal(&ndp_table->table[i].ip_addr, &ip_addr)) { + DEBUG_CALL(" already in table: update the entry"); + /* Update the entry */ + memcpy(ndp_table->table[i].eth_addr, ethaddr, ETH_ALEN); + return; + } + } + + /* No entry found, create a new one */ + DEBUG_CALL(" create new entry"); + /* Save the first entry, it is the guest. */ + if (in6_zero(&ndp_table->guest_in6_addr)) { + ndp_table->guest_in6_addr = ip_addr; + } + ndp_table->table[ndp_table->next_victim].ip_addr = ip_addr; + memcpy(ndp_table->table[ndp_table->next_victim].eth_addr, ethaddr, + ETH_ALEN); + ndp_table->next_victim = (ndp_table->next_victim + 1) % NDP_TABLE_SIZE; +} + +bool ndp_table_search(Slirp *slirp, struct in6_addr ip_addr, + uint8_t out_ethaddr[ETH_ALEN]) +{ + char addrstr[INET6_ADDRSTRLEN]; + NdpTable *ndp_table = &slirp->ndp_table; + int i; + char ethaddr_str[ETH_ADDRSTRLEN]; + + inet_ntop(AF_INET6, &(ip_addr), addrstr, INET6_ADDRSTRLEN); + + DEBUG_CALL("ndp_table_search"); + DEBUG_ARG("ip = %s", addrstr); + + /* If unspecified address */ + if (in6_zero(&ip_addr)) { + /* return Ethernet broadcast address */ + memset(out_ethaddr, 0xff, ETH_ALEN); + return 1; + } + + /* Multicast address: fec0::abcd:efgh/8 -> 33:33:ab:cd:ef:gh */ + if (IN6_IS_ADDR_MULTICAST(&ip_addr)) { + out_ethaddr[0] = 0x33; + out_ethaddr[1] = 0x33; + out_ethaddr[2] = ip_addr.s6_addr[12]; + out_ethaddr[3] = ip_addr.s6_addr[13]; + out_ethaddr[4] = ip_addr.s6_addr[14]; + out_ethaddr[5] = ip_addr.s6_addr[15]; + DEBUG_ARG("multicast addr = %s", + slirp_ether_ntoa(out_ethaddr, ethaddr_str, + sizeof(ethaddr_str))); + return 1; + } + + for (i = 0; i < NDP_TABLE_SIZE; i++) { + if (in6_equal(&ndp_table->table[i].ip_addr, &ip_addr)) { + memcpy(out_ethaddr, ndp_table->table[i].eth_addr, ETH_ALEN); + DEBUG_ARG("found hw addr = %s", + slirp_ether_ntoa(out_ethaddr, ethaddr_str, + sizeof(ethaddr_str))); + return 1; + } + } + + DEBUG_CALL(" ip not found in table"); + return 0; +} diff --git a/app/src/main/cpp/libslirp/src/sbuf.c b/app/src/main/cpp/libslirp/src/sbuf.c new file mode 100644 index 00000000..6126baad --- /dev/null +++ b/app/src/main/cpp/libslirp/src/sbuf.c @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" + +static void sbappendsb(struct sbuf *sb, struct mbuf *m); + +void sbfree(struct sbuf *sb) +{ + g_free(sb->sb_data); +} + +bool sbdrop(struct sbuf *sb, size_t num) +{ + int limit = sb->sb_datalen / 2; + + g_warn_if_fail(num <= sb->sb_cc); + if (num > sb->sb_cc) + num = sb->sb_cc; + + sb->sb_cc -= num; + sb->sb_rptr += num; + if (sb->sb_rptr >= sb->sb_data + sb->sb_datalen) + sb->sb_rptr -= sb->sb_datalen; + + if (sb->sb_cc < limit && sb->sb_cc + num >= limit) { + return true; + } + + return false; +} + +void sbreserve(struct sbuf *sb, size_t size) +{ + sb->sb_wptr = sb->sb_rptr = sb->sb_data = g_realloc(sb->sb_data, size); + sb->sb_cc = 0; + sb->sb_datalen = size; +} + +void sbappend(struct socket *so, struct mbuf *m) +{ + int ret = 0; + + DEBUG_CALL("sbappend"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("m->m_len = %d", m->m_len); + + /* Shouldn't happen, but... e.g. foreign host closes connection */ + if (m->m_len <= 0) { + m_free(m); + return; + } + + /* + * If there is urgent data, call sosendoob + * if not all was sent, sowrite will take care of the rest + * (The rest of this function is just an optimisation) + */ + if (so->so_urgc) { + sbappendsb(&so->so_rcv, m); + m_free(m); + sosendoob(so); + return; + } + + /* + * We only write if there's nothing in the buffer, + * ottherwise it'll arrive out of order, and hence corrupt + */ + if (!so->so_rcv.sb_cc) + ret = slirp_send(so, m->m_data, m->m_len, 0); + + if (ret <= 0) { + /* + * Nothing was written + * It's possible that the socket has closed, but + * we don't need to check because if it has closed, + * it will be detected in the normal way by soread() + */ + sbappendsb(&so->so_rcv, m); + } else if (ret != m->m_len) { + /* + * Something was written, but not everything.. + * sbappendsb the rest + */ + m->m_len -= ret; + m->m_data += ret; + sbappendsb(&so->so_rcv, m); + } /* else */ + /* Whatever happened, we free the mbuf */ + m_free(m); +} + +/* + * Copy the data from m into sb + * The caller is responsible to make sure there's enough room + */ +static void sbappendsb(struct sbuf *sb, struct mbuf *m) +{ + int len, n, nn; + + len = m->m_len; + + if (sb->sb_wptr < sb->sb_rptr) { + n = sb->sb_rptr - sb->sb_wptr; + if (n > len) + n = len; + memcpy(sb->sb_wptr, m->m_data, n); + } else { + /* Do the right edge first */ + n = sb->sb_data + sb->sb_datalen - sb->sb_wptr; + if (n > len) + n = len; + memcpy(sb->sb_wptr, m->m_data, n); + len -= n; + if (len) { + /* Now the left edge */ + nn = sb->sb_rptr - sb->sb_data; + if (nn > len) + nn = len; + memcpy(sb->sb_data, m->m_data + n, nn); + n += nn; + } + } + + sb->sb_cc += n; + sb->sb_wptr += n; + if (sb->sb_wptr >= sb->sb_data + sb->sb_datalen) + sb->sb_wptr -= sb->sb_datalen; +} + +void sbcopy(struct sbuf *sb, size_t off, size_t len, char *to) +{ + char *from; + + g_assert(len + off <= sb->sb_cc); + + from = sb->sb_rptr + off; + if (from >= sb->sb_data + sb->sb_datalen) + from -= sb->sb_datalen; + + if (from < sb->sb_wptr) { + memcpy(to, from, len); + } else { + /* re-use off */ + off = (sb->sb_data + sb->sb_datalen) - from; + if (off > len) + off = len; + memcpy(to, from, off); + len -= off; + if (len) + memcpy(to + off, sb->sb_data, len); + } +} diff --git a/app/src/main/cpp/libslirp/src/sbuf.h b/app/src/main/cpp/libslirp/src/sbuf.h new file mode 100644 index 00000000..a3eaf496 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/sbuf.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#ifndef SBUF_H +#define SBUF_H + +/* How many bytes are free in the sbuf */ +#define sbspace(sb) ((sb)->sb_datalen - (sb)->sb_cc) + +struct sbuf { + uint32_t sb_cc; /* actual chars in buffer */ + uint32_t sb_datalen; /* Length of data */ + char *sb_wptr; /* write pointer. points to where the next + * bytes should be written in the sbuf */ + char *sb_rptr; /* read pointer. points to where the next + * byte should be read from the sbuf */ + char *sb_data; /* Actual data */ +}; + +/* Release the sbuf */ +void sbfree(struct sbuf *sb); + +/* Drop len bytes from the reading end of the sbuf */ +bool sbdrop(struct sbuf *sb, size_t len); + +/* (re)Allocate sbuf buffer to store size bytes */ +void sbreserve(struct sbuf *sb, size_t size); + +/* + * Try and write() to the socket, whatever doesn't get written + * append to the buffer... for a host with a fast net connection, + * this prevents an unnecessary copy of the data + * (the socket is non-blocking, so we won't hang) + */ +void sbappend(struct socket *sb, struct mbuf *mb); + +/* + * Copy data from sbuf to a normal, straight buffer + * Don't update the sbuf rptr, this will be + * done in sbdrop when the data is acked + */ +void sbcopy(struct sbuf *sb, size_t off, size_t len, char *p); + +#endif diff --git a/app/src/main/cpp/libslirp/src/slirp.c b/app/src/main/cpp/libslirp/src/slirp.c new file mode 100644 index 00000000..fd68f632 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/slirp.c @@ -0,0 +1,1642 @@ +/* SPDX-License-Identifier: MIT */ +/* + * libslirp glue + * + * Copyright (c) 2004-2008 Fabrice Bellard + * + * 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 "slirp.h" + + +#ifndef _WIN32 +#include +#endif + +/* https://gitlab.freedesktop.org/slirp/libslirp/issues/18 */ +#if defined(__NetBSD__) && defined(if_mtu) +#undef if_mtu +#endif + +#if defined(_WIN32) + +#define INITIAL_DNS_ADDR_BUF_SIZE 32 * 1024 +#define REALLOC_RETRIES 5 + +// Broadcast site local DNS resolvers. We do not use these because they are +// highly unlikely to be valid. +// https://www.ietf.org/proceedings/52/I-D/draft-ietf-ipngwg-dns-discovery-03.txt +static const struct in6_addr SITE_LOCAL_DNS_BROADCAST_ADDRS[] = { + { + {{ + 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }} + }, + { + {{ + 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 + }} + }, + { + {{ + 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + }} + }, +}; + +#endif + +int slirp_debug; + +/* Define to 1 if you want KEEPALIVE timers */ +bool slirp_do_keepalive; + +/* host loopback address */ +struct in_addr loopback_addr; +/* host loopback network mask */ +unsigned long loopback_mask; + +/* emulated hosts use the MAC addr 52:55:IP:IP:IP:IP */ +static const uint8_t special_ethaddr[ETH_ALEN] = { 0x52, 0x55, 0x00, + 0x00, 0x00, 0x00 }; + +unsigned curtime; + +static struct in_addr dns_addr; +static struct in6_addr dns6_addr; +static uint32_t dns6_scope_id; +static unsigned dns_addr_time; +static unsigned dns6_addr_time; + +#define TIMEOUT_FAST 2 /* milliseconds */ +#define TIMEOUT_SLOW 499 /* milliseconds */ +/* for the aging of certain requests like DNS */ +#define TIMEOUT_DEFAULT 1000 /* milliseconds */ + +#if defined(_WIN32) + +int get_dns_addr(struct in_addr *pdns_addr) +{ + FIXED_INFO *FixedInfo = NULL; + ULONG BufLen; + DWORD ret; + IP_ADDR_STRING *pIPAddr; + struct in_addr tmp_addr; + + if (dns_addr.s_addr != 0 && (curtime - dns_addr_time) < TIMEOUT_DEFAULT) { + *pdns_addr = dns_addr; + return 0; + } + + FixedInfo = (FIXED_INFO *)GlobalAlloc(GPTR, sizeof(FIXED_INFO)); + BufLen = sizeof(FIXED_INFO); + + if (ERROR_BUFFER_OVERFLOW == GetNetworkParams(FixedInfo, &BufLen)) { + if (FixedInfo) { + GlobalFree(FixedInfo); + FixedInfo = NULL; + } + FixedInfo = GlobalAlloc(GPTR, BufLen); + } + + if ((ret = GetNetworkParams(FixedInfo, &BufLen)) != ERROR_SUCCESS) { + printf("GetNetworkParams failed. ret = %08x\n", (unsigned)ret); + if (FixedInfo) { + GlobalFree(FixedInfo); + FixedInfo = NULL; + } + return -1; + } + + pIPAddr = &(FixedInfo->DnsServerList); + inet_aton(pIPAddr->IpAddress.String, &tmp_addr); + *pdns_addr = tmp_addr; + dns_addr = tmp_addr; + dns_addr_time = curtime; + if (FixedInfo) { + GlobalFree(FixedInfo); + FixedInfo = NULL; + } + return 0; +} + +static int is_site_local_dns_broadcast(struct in6_addr *address) +{ + int i; + for (i = 0; i < G_N_ELEMENTS(SITE_LOCAL_DNS_BROADCAST_ADDRS); i++) { + if (in6_equal(address, &SITE_LOCAL_DNS_BROADCAST_ADDRS[i])) { + return 1; + } + } + return 0; +} + +static void print_dns_v6_address(struct in6_addr address) +{ + char address_str[INET6_ADDRSTRLEN] = ""; + if (inet_ntop(AF_INET6, &address, address_str, INET6_ADDRSTRLEN) + == NULL) { + DEBUG_ERROR("Failed to stringify IPv6 address for logging."); + return; + } + DEBUG_RAW_CALL("IPv6 DNS server found: %s", address_str); +} + +// Gets the first valid DNS resolver with an IPv6 address. +// Ignores any site local broadcast DNS servers, as these +// are on deprecated addresses and not generally expected +// to work. Further details at: +// https://www.ietf.org/proceedings/52/I-D/draft-ietf-ipngwg-dns-discovery-03.txt +static int get_ipv6_dns_server(struct in6_addr *dns_server_address, uint32_t *scope_id) +{ + PIP_ADAPTER_ADDRESSES addresses = NULL; + PIP_ADAPTER_ADDRESSES address = NULL; + IP_ADAPTER_DNS_SERVER_ADDRESS *dns_server = NULL; + struct sockaddr_in6 *dns_v6_addr = NULL; + + ULONG buf_size = INITIAL_DNS_ADDR_BUF_SIZE; + DWORD res = ERROR_BUFFER_OVERFLOW; + int i; + + for (i = 0; i < REALLOC_RETRIES; i++) { + // If non null, we hit buffer overflow, free it so we can try again. + if (addresses != NULL) { + g_free(addresses); + } + + addresses = g_malloc(buf_size); + res = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, + addresses, &buf_size); + + if (res != ERROR_BUFFER_OVERFLOW) { + break; + } + } + + if (res != NO_ERROR) { + DEBUG_ERROR("Failed to get IPv6 DNS addresses due to error %lX", res); + goto failure; + } + + address = addresses; + for (address = addresses; address != NULL; address = address->Next) { + for (dns_server = address->FirstDnsServerAddress; + dns_server != NULL; + dns_server = dns_server->Next) { + + if (dns_server->Address.lpSockaddr->sa_family != AF_INET6) { + continue; + } + + dns_v6_addr = (struct sockaddr_in6 *)dns_server->Address.lpSockaddr; + if (is_site_local_dns_broadcast(&dns_v6_addr->sin6_addr) == 0) { + print_dns_v6_address(dns_v6_addr->sin6_addr); + *dns_server_address = dns_v6_addr->sin6_addr; + *scope_id = dns_v6_addr->sin6_scope_id; + + g_free(addresses); + return 0; + } + } + } + + DEBUG_ERROR("No IPv6 DNS servers found.\n"); + +failure: + g_free(addresses); + return -1; +} + +int get_dns6_addr(struct in6_addr *pdns6_addr, uint32_t *scope_id) +{ + if (!in6_zero(&dns6_addr) && (curtime - dns6_addr_time) < TIMEOUT_DEFAULT) { + *pdns6_addr = dns6_addr; + *scope_id = dns6_scope_id; + return 0; + } + + if (get_ipv6_dns_server(pdns6_addr, scope_id) == 0) { + dns6_addr = *pdns6_addr; + dns6_addr_time = curtime; + dns6_scope_id = *scope_id; + return 0; + } + + return -1; +} + +static void winsock_cleanup(void) +{ + WSACleanup(); +} + +#elif defined(__APPLE__) + +#include + +static int get_dns_addr_cached(void *pdns_addr, void *cached_addr, + socklen_t addrlen, unsigned *cached_time) +{ + if (curtime - *cached_time < TIMEOUT_DEFAULT) { + memcpy(pdns_addr, cached_addr, addrlen); + return 0; + } + return 1; +} + +static int get_dns_addr_libresolv(int af, void *pdns_addr, void *cached_addr, + socklen_t addrlen, + uint32_t *scope_id, uint32_t *cached_scope_id, + unsigned *cached_time) +{ + struct __res_state state; + union res_sockaddr_union servers[NI_MAXSERV]; + int count; + int found; + void *addr; + + // we only support IPv4 and IPv4, we assume it's one or the other + assert(af == AF_INET || af == AF_INET6); + + if (res_ninit(&state) != 0) { + return -1; + } + + count = res_getservers(&state, servers, NI_MAXSERV); + found = 0; + DEBUG_MISC("IP address of your DNS(s):"); + for (int i = 0; i < count; i++) { + if (af == servers[i].sin.sin_family) { + found++; + } + if (af == AF_INET) { + addr = &servers[i].sin.sin_addr; + } else { // af == AF_INET6 + addr = &servers[i].sin6.sin6_addr; + } + + // we use the first found entry + if (found == 1) { + memcpy(pdns_addr, addr, addrlen); + memcpy(cached_addr, addr, addrlen); + if (scope_id) { + *scope_id = 0; + } + if (cached_scope_id) { + *cached_scope_id = 0; + } + *cached_time = curtime; + } + + if (found > 3) { + DEBUG_MISC(" (more)"); + break; + } else if (slirp_debug & DBG_MISC) { + char s[INET6_ADDRSTRLEN]; + const char *res = inet_ntop(af, addr, s, sizeof(s)); + if (!res) { + res = " (string conversion error)"; + } + DEBUG_MISC(" %s", res); + } + } + + res_ndestroy(&state); + if (!found) + return -1; + return 0; +} + +int get_dns_addr(struct in_addr *pdns_addr) +{ + if (dns_addr.s_addr != 0) { + int ret; + ret = get_dns_addr_cached(pdns_addr, &dns_addr, sizeof(dns_addr), + &dns_addr_time); + if (ret <= 0) { + return ret; + } + } + return get_dns_addr_libresolv(AF_INET, pdns_addr, &dns_addr, + sizeof(dns_addr), NULL, NULL, &dns_addr_time); +} + +int get_dns6_addr(struct in6_addr *pdns6_addr, uint32_t *scope_id) +{ + if (!in6_zero(&dns6_addr)) { + int ret; + ret = get_dns_addr_cached(pdns6_addr, &dns6_addr, sizeof(dns6_addr), + &dns6_addr_time); + if (ret == 0) { + *scope_id = dns6_scope_id; + } + if (ret <= 0) { + return ret; + } + } + return get_dns_addr_libresolv(AF_INET6, pdns6_addr, &dns6_addr, + sizeof(dns6_addr), + scope_id, &dns6_scope_id, &dns6_addr_time); +} + +#else // !defined(_WIN32) && !defined(__APPLE__) + +#if defined(__HAIKU__) +#define RESOLV_CONF_PATH "/boot/system/settings/network/resolv.conf" +#else +#define RESOLV_CONF_PATH "/etc/resolv.conf" +#endif + +static int get_dns_addr_cached(void *pdns_addr, void *cached_addr, + socklen_t addrlen, struct stat *cached_stat, + unsigned *cached_time) +{ + struct stat old_stat; + if (curtime - *cached_time < TIMEOUT_DEFAULT) { + memcpy(pdns_addr, cached_addr, addrlen); + return 0; + } + old_stat = *cached_stat; + if (stat(RESOLV_CONF_PATH, cached_stat) != 0) { + return -1; + } + if (cached_stat->st_dev == old_stat.st_dev && + cached_stat->st_ino == old_stat.st_ino && + cached_stat->st_size == old_stat.st_size && + cached_stat->st_mtime == old_stat.st_mtime) { + memcpy(pdns_addr, cached_addr, addrlen); + return 0; + } + return 1; +} + +static bool try_and_setdns_server(int af, unsigned found, unsigned if_index, + const char *buff2, void *pdns_addr, void *cached_addr, + socklen_t addrlen, uint32_t *scope_id, uint32_t *cached_scope_id, + unsigned *cached_time) +{ + union { + struct in_addr dns_addr; + struct in6_addr dns6_addr; + } tmp_addr; + + assert(sizeof(tmp_addr) >= addrlen); + + if (!inet_pton(af, buff2, &tmp_addr)) + return false; + + /* If it's the first one, set it to dns_addr */ + if (!found) { + memcpy(pdns_addr, &tmp_addr, addrlen); + memcpy(cached_addr, &tmp_addr, addrlen); + if (scope_id) { + *scope_id = if_index; + } + if (cached_scope_id) { + *cached_scope_id = if_index; + } + *cached_time = curtime; + } + + if (found > 2) { + DEBUG_MISC(" (more)"); + } else if (slirp_debug & DBG_MISC) { + char s[INET6_ADDRSTRLEN]; + const char *res = inet_ntop(af, &tmp_addr, s, sizeof(s)); + if (!res) { + res = " (string conversion error)"; + } + DEBUG_MISC(" %s", res); + } + + return true; +} + +static int get_dns_addr_resolv_conf(int af, void *pdns_addr, void *cached_addr, + socklen_t addrlen, + uint32_t *scope_id, uint32_t *cached_scope_id, + unsigned *cached_time) +{ + char buff[512]; + char buff2[257]; + FILE *f; + int found = 0; + unsigned if_index; + unsigned nameservers = 0; + + f = fopen(RESOLV_CONF_PATH, "r"); + if (!f) + return -1; + + DEBUG_MISC("IP address of your DNS(s):"); + while (fgets(buff, 512, f) != NULL) { + if (sscanf(buff, "nameserver%*[ \t]%256s", buff2) == 1) { + char *c = strchr(buff2, '%'); + if (c) { + if_index = if_nametoindex(c + 1); + *c = '\0'; + } else { + if_index = 0; + } + + nameservers++; + + if (!try_and_setdns_server(af, found, if_index, buff2, pdns_addr, + cached_addr, addrlen, scope_id, + cached_scope_id, cached_time)) + continue; + + if (++found > 3) + break; + } + } + fclose(f); + if (nameservers && !found) + return -1; + if (!nameservers) { + found += try_and_setdns_server(af, found, 0, "127.0.0.1", + pdns_addr, cached_addr, addrlen, scope_id, + cached_scope_id, cached_time); + found += try_and_setdns_server(af, found, 0, "::1", + pdns_addr, cached_addr, addrlen, scope_id, + cached_scope_id, cached_time); + } + + return found ? 0 : -1; +} + +int get_dns_addr(struct in_addr *pdns_addr) +{ + static struct stat dns_addr_stat; + + if (dns_addr.s_addr != 0) { + int ret; + ret = get_dns_addr_cached(pdns_addr, &dns_addr, sizeof(dns_addr), + &dns_addr_stat, &dns_addr_time); + if (ret <= 0) { + return ret; + } + } + return get_dns_addr_resolv_conf(AF_INET, pdns_addr, &dns_addr, + sizeof(dns_addr), + NULL, NULL, &dns_addr_time); +} + +int get_dns6_addr(struct in6_addr *pdns6_addr, uint32_t *scope_id) +{ + static struct stat dns6_addr_stat; + + if (!in6_zero(&dns6_addr)) { + int ret; + ret = get_dns_addr_cached(pdns6_addr, &dns6_addr, sizeof(dns6_addr), + &dns6_addr_stat, &dns6_addr_time); + if (ret == 0) { + *scope_id = dns6_scope_id; + } + if (ret <= 0) { + return ret; + } + } + return get_dns_addr_resolv_conf(AF_INET6, pdns6_addr, &dns6_addr, + sizeof(dns6_addr), + scope_id, &dns6_scope_id, &dns6_addr_time); +} + +#endif + +static void slirp_init_once(void) +{ + static int initialized; + const char *debug; +#ifdef _WIN32 + WSADATA Data; +#endif + + if (initialized) { + return; + } + initialized = 1; + +#ifdef _WIN32 + WSAStartup(MAKEWORD(2, 0), &Data); + atexit(winsock_cleanup); +#endif + + loopback_addr.s_addr = htonl(INADDR_LOOPBACK); + loopback_mask = htonl(IN_CLASSA_NET); + + debug = g_getenv("SLIRP_DEBUG"); + if (debug) { + const GDebugKey keys[] = { + { "call", DBG_CALL }, + { "misc", DBG_MISC }, + { "error", DBG_ERROR }, + { "tftp", DBG_TFTP }, + { "verbose_call", DBG_VERBOSE_CALL }, + }; + slirp_debug = g_parse_debug_string(debug, keys, G_N_ELEMENTS(keys)); + } +} + +static void ra_timer_handler_cb(void *opaque) +{ + Slirp *slirp = opaque; + + ra_timer_handler(slirp, NULL); +} + +void slirp_handle_timer(Slirp *slirp, SlirpTimerId id, void *cb_opaque) +{ + g_return_if_fail(id >= 0 && id < SLIRP_TIMER_NUM); + + switch (id) { + case SLIRP_TIMER_RA: + ra_timer_handler(slirp, cb_opaque); + return; + default: + abort(); + } +} + +void *slirp_timer_new(Slirp *slirp, SlirpTimerId id, void *cb_opaque) +{ + g_return_val_if_fail(id >= 0 && id < SLIRP_TIMER_NUM, NULL); + + if (slirp->cfg_version >= 4 && slirp->cb->timer_new_opaque) { + return slirp->cb->timer_new_opaque(id, cb_opaque, slirp->opaque); + } + + switch (id) { + case SLIRP_TIMER_RA: + g_return_val_if_fail(cb_opaque == NULL, NULL); + return slirp->cb->timer_new(ra_timer_handler_cb, slirp, slirp->opaque); + + default: + abort(); + } +} + +Slirp *slirp_new(const SlirpConfig *cfg, const SlirpCb *callbacks, void *opaque) +{ + Slirp *slirp; + + g_return_val_if_fail(cfg != NULL, NULL); + g_return_val_if_fail(cfg->version >= SLIRP_CONFIG_VERSION_MIN, NULL); + g_return_val_if_fail(cfg->version <= SLIRP_CONFIG_VERSION_MAX, NULL); + g_return_val_if_fail(cfg->if_mtu >= IF_MTU_MIN || cfg->if_mtu == 0, NULL); + g_return_val_if_fail(cfg->if_mtu <= IF_MTU_MAX, NULL); + g_return_val_if_fail(cfg->if_mru >= IF_MRU_MIN || cfg->if_mru == 0, NULL); + g_return_val_if_fail(cfg->if_mru <= IF_MRU_MAX, NULL); + g_return_val_if_fail(!cfg->bootfile || + (strlen(cfg->bootfile) < + G_SIZEOF_MEMBER(struct bootp_t, bp_file)), NULL); + + slirp = g_malloc0(sizeof(Slirp)); + + slirp_init_once(); + + slirp->cfg_version = cfg->version; + slirp->opaque = opaque; + slirp->cb = callbacks; + slirp->grand = g_rand_new(); + slirp->restricted = cfg->restricted; + + slirp->in_enabled = cfg->in_enabled; + slirp->in6_enabled = cfg->in6_enabled; + + if_init(slirp); + ip_init(slirp); + + m_init(slirp); + + slirp->vnetwork_addr = cfg->vnetwork; + slirp->vnetwork_mask = cfg->vnetmask; + slirp->vhost_addr = cfg->vhost; + slirp->vprefix_addr6 = cfg->vprefix_addr6; + slirp->vprefix_len = cfg->vprefix_len; + slirp->vhost_addr6 = cfg->vhost6; + if (cfg->vhostname) { + slirp_pstrcpy(slirp->client_hostname, sizeof(slirp->client_hostname), + cfg->vhostname); + } + slirp->tftp_prefix = g_strdup(cfg->tftp_path); + slirp->bootp_filename = g_strdup(cfg->bootfile); + slirp->vdomainname = g_strdup(cfg->vdomainname); + slirp->vdhcp_startaddr = cfg->vdhcp_start; + slirp->vnameserver_addr = cfg->vnameserver; + slirp->vnameserver_addr6 = cfg->vnameserver6; + slirp->tftp_server_name = g_strdup(cfg->tftp_server_name); + + if (cfg->vdnssearch) { + translate_dnssearch(slirp, cfg->vdnssearch); + } + slirp->if_mtu = cfg->if_mtu == 0 ? IF_MTU_DEFAULT : cfg->if_mtu; + slirp->if_mru = cfg->if_mru == 0 ? IF_MRU_DEFAULT : cfg->if_mru; + slirp->disable_host_loopback = cfg->disable_host_loopback; + slirp->enable_emu = cfg->enable_emu; + + if (cfg->version >= 2) { + slirp->outbound_addr = cfg->outbound_addr; + slirp->outbound_addr6 = cfg->outbound_addr6; + } else { + slirp->outbound_addr = NULL; + slirp->outbound_addr6 = NULL; + } + + if (cfg->version >= 3) { + slirp->disable_dns = cfg->disable_dns; + } else { + slirp->disable_dns = false; + } + + if (cfg->version >= 4) { + slirp->disable_dhcp = cfg->disable_dhcp; + } else { + slirp->disable_dhcp = false; + } + + if (slirp->cfg_version >= 4 && slirp->cb->init_completed) { + slirp->cb->init_completed(slirp, slirp->opaque); + } + + if (cfg->version >= 5) { + slirp->mfr_id = cfg->mfr_id; + memcpy(slirp->oob_eth_addr, cfg->oob_eth_addr, ETH_ALEN); + } else { + slirp->mfr_id = 0; + memset(slirp->oob_eth_addr, 0, ETH_ALEN); + } + + ip6_post_init(slirp); + return slirp; +} + +Slirp *slirp_init(int restricted, bool in_enabled, struct in_addr vnetwork, + struct in_addr vnetmask, struct in_addr vhost, + bool in6_enabled, struct in6_addr vprefix_addr6, + uint8_t vprefix_len, struct in6_addr vhost6, + const char *vhostname, const char *tftp_server_name, + const char *tftp_path, const char *bootfile, + struct in_addr vdhcp_start, struct in_addr vnameserver, + struct in6_addr vnameserver6, const char **vdnssearch, + const char *vdomainname, const SlirpCb *callbacks, + void *opaque) +{ + SlirpConfig cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.version = 1; + cfg.restricted = restricted; + cfg.in_enabled = in_enabled; + cfg.vnetwork = vnetwork; + cfg.vnetmask = vnetmask; + cfg.vhost = vhost; + cfg.in6_enabled = in6_enabled; + cfg.vprefix_addr6 = vprefix_addr6; + cfg.vprefix_len = vprefix_len; + cfg.vhost6 = vhost6; + cfg.vhostname = vhostname; + cfg.tftp_server_name = tftp_server_name; + cfg.tftp_path = tftp_path; + cfg.bootfile = bootfile; + cfg.vdhcp_start = vdhcp_start; + cfg.vnameserver = vnameserver; + cfg.vnameserver6 = vnameserver6; + cfg.vdnssearch = vdnssearch; + cfg.vdomainname = vdomainname; + return slirp_new(&cfg, callbacks, opaque); +} + +void slirp_cleanup(Slirp *slirp) +{ + struct gfwd_list *e, *next; + + for (e = slirp->guestfwd_list; e; e = next) { + next = e->ex_next; + g_free(e->ex_exec); + g_free(e->ex_unix); + g_free(e); + } + + ip_cleanup(slirp); + ip6_cleanup(slirp); + m_cleanup(slirp); + tftp_cleanup(slirp); + + g_rand_free(slirp->grand); + + g_free(slirp->vdnssearch); + g_free(slirp->tftp_prefix); + g_free(slirp->bootp_filename); + g_free(slirp->vdomainname); + g_free(slirp); +} + +#define CONN_CANFSEND(so) \ + (((so)->so_state & (SS_FCANTSENDMORE | SS_ISFCONNECTED)) == SS_ISFCONNECTED) +#define CONN_CANFRCV(so) \ + (((so)->so_state & (SS_FCANTRCVMORE | SS_ISFCONNECTED)) == SS_ISFCONNECTED) + +static void slirp_update_timeout(Slirp *slirp, uint32_t *timeout) +{ + uint32_t t; + + if (*timeout <= TIMEOUT_FAST) { + return; + } + + t = MIN(1000, *timeout); + + /* If we have tcp timeout with slirp, then we will fill @timeout with + * more precise value. + */ + if (slirp->time_fasttimo) { + *timeout = TIMEOUT_FAST; + return; + } + if (slirp->do_slowtimo) { + t = MIN(TIMEOUT_SLOW, t); + } + *timeout = t; +} + +void slirp_pollfds_fill(Slirp *slirp, uint32_t *timeout, + SlirpAddPollCb add_poll, void *opaque) +{ + struct socket *so, *so_next; + + /* + * First, TCP sockets + */ + + /* + * *_slowtimo needs calling if there are IP fragments + * in the fragment queue, or there are TCP connections active + */ + slirp->do_slowtimo = ((slirp->tcb.so_next != &slirp->tcb) || + (&slirp->ipq.ip_link != slirp->ipq.ip_link.next)); + + for (so = slirp->tcb.so_next; so != &slirp->tcb; so = so_next) { + int events = 0; + + so_next = so->so_next; + + so->pollfds_idx = -1; + + /* + * See if we need a tcp_fasttimo + */ + if (slirp->time_fasttimo == 0 && so->so_tcpcb->t_flags & TF_DELACK) { + slirp->time_fasttimo = curtime; /* Flag when want a fasttimo */ + } + + /* + * NOFDREF can include still connecting to local-host, + * newly socreated() sockets etc. Don't want to select these. + */ + if (so->so_state & SS_NOFDREF || so->s == -1) { + continue; + } + + /* + * Set for reading sockets which are accepting + */ + if (so->so_state & SS_FACCEPTCONN) { + so->pollfds_idx = add_poll( + so->s, SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR, opaque); + continue; + } + + /* + * Set for writing sockets which are connecting + */ + if (so->so_state & SS_ISFCONNECTING) { + so->pollfds_idx = + add_poll(so->s, SLIRP_POLL_OUT | SLIRP_POLL_ERR, opaque); + continue; + } + + /* + * Set for writing if we are connected, can send more, and + * we have something to send + */ + if (CONN_CANFSEND(so) && so->so_rcv.sb_cc) { + events |= SLIRP_POLL_OUT | SLIRP_POLL_ERR; + } + + /* + * Set for reading (and urgent data) if we are connected, can + * receive more, and we have room for it. + * + * If sb is already half full, we will wait for the guest to consume it, + * and notify again in sbdrop() when the sb becomes less than half full. + */ + if (CONN_CANFRCV(so) && + (so->so_snd.sb_cc < (so->so_snd.sb_datalen / 2))) { + events |= SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR | + SLIRP_POLL_PRI; + } + + if (events) { + so->pollfds_idx = add_poll(so->s, events, opaque); + } + } + + /* + * UDP sockets + */ + for (so = slirp->udb.so_next; so != &slirp->udb; so = so_next) { + so_next = so->so_next; + + so->pollfds_idx = -1; + + /* + * See if it's timed out + */ + if (so->so_expire) { + if (so->so_expire <= curtime) { + udp_detach(so); + continue; + } else { + slirp->do_slowtimo = true; /* Let socket expire */ + } + } + + /* + * When UDP packets are received from over the + * link, they're sendto()'d straight away, so + * no need for setting for writing + * Limit the number of packets queued by this session + * to 4. Note that even though we try and limit this + * to 4 packets, the session could have more queued + * if the packets needed to be fragmented + * (XXX <= 4 ?) + */ + if ((so->so_state & SS_ISFCONNECTED) && so->so_queued <= 4) { + so->pollfds_idx = add_poll( + so->s, SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR, opaque); + } + } + + /* + * ICMP sockets + */ + for (so = slirp->icmp.so_next; so != &slirp->icmp; so = so_next) { + so_next = so->so_next; + + so->pollfds_idx = -1; + + /* + * See if it's timed out + */ + if (so->so_expire) { + if (so->so_expire <= curtime) { + icmp_detach(so); + continue; + } else { + slirp->do_slowtimo = true; /* Let socket expire */ + } + } + + if (so->so_state & SS_ISFCONNECTED) { + so->pollfds_idx = add_poll( + so->s, SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR, opaque); + } + } + + slirp_update_timeout(slirp, timeout); +} + +void slirp_pollfds_poll(Slirp *slirp, int select_error, + SlirpGetREventsCb get_revents, void *opaque) +{ + struct socket *so, *so_next; + int ret; + + curtime = slirp->cb->clock_get_ns(slirp->opaque) / SCALE_MS; + + /* + * See if anything has timed out + */ + if (slirp->time_fasttimo && + ((curtime - slirp->time_fasttimo) >= TIMEOUT_FAST)) { + tcp_fasttimo(slirp); + slirp->time_fasttimo = 0; + } + if (slirp->do_slowtimo && + ((curtime - slirp->last_slowtimo) >= TIMEOUT_SLOW)) { + ip_slowtimo(slirp); + tcp_slowtimo(slirp); + slirp->last_slowtimo = curtime; + } + + /* + * Check sockets + */ + if (!select_error) { + /* + * Check TCP sockets + */ + for (so = slirp->tcb.so_next; so != &slirp->tcb; so = so_next) { + int revents; + + so_next = so->so_next; + + revents = 0; + if (so->pollfds_idx != -1) { + revents = get_revents(so->pollfds_idx, opaque); + } + + if (so->so_state & SS_NOFDREF || so->s == -1) { + continue; + } + +#ifndef __APPLE__ + /* + * Check for URG data + * This will soread as well, so no need to + * test for SLIRP_POLL_IN below if this succeeds. + * + * This is however disabled on MacOS, which apparently always + * reports data as PRI when it is the last data of the + * connection. We would then report it out of band, which the guest + * would most probably not be ready for. + */ + if (revents & SLIRP_POLL_PRI) { + ret = sorecvoob(so); + if (ret < 0) { + /* Socket error might have resulted in the socket being + * removed, do not try to do anything more with it. */ + continue; + } + } + /* + * Check sockets for reading + */ + else +#endif + if (revents & + (SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR | SLIRP_POLL_PRI)) { + /* + * Check for incoming connections + */ + if (so->so_state & SS_FACCEPTCONN) { + tcp_connect(so); + continue; + } /* else */ + ret = soread(so); + + /* Output it if we read something */ + if (ret > 0) { + tcp_output(sototcpcb(so)); + } + if (ret < 0) { + /* Socket error might have resulted in the socket being + * removed, do not try to do anything more with it. */ + continue; + } + } + + /* + * Check sockets for writing + */ + if (!(so->so_state & SS_NOFDREF) && + (revents & (SLIRP_POLL_OUT | SLIRP_POLL_ERR))) { + /* + * Check for non-blocking, still-connecting sockets + */ + if (so->so_state & SS_ISFCONNECTING) { + /* Connected */ + so->so_state &= ~SS_ISFCONNECTING; + + ret = send(so->s, (const void *)&ret, 0, 0); + if (ret < 0) { + /* XXXXX Must fix, zero bytes is a NOP */ + if (errno == EAGAIN || errno == EWOULDBLOCK || + errno == EINPROGRESS || errno == ENOTCONN) { + continue; + } + + /* else failed */ + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; + } + /* else so->so_state &= ~SS_ISFCONNECTING; */ + + /* + * Continue tcp_input + */ + tcp_input((struct mbuf *)NULL, sizeof(struct ip), so, + so->so_ffamily); + /* continue; */ + } else { + ret = sowrite(so); + if (ret > 0) { + /* Call tcp_output in case we need to send a window + * update to the guest, otherwise it will be stuck + * until it sends a window probe. */ + tcp_output(sototcpcb(so)); + } + } + } + } + + /* + * Now UDP sockets. + * Incoming packets are sent straight away, they're not buffered. + * Incoming UDP data isn't buffered either. + */ + for (so = slirp->udb.so_next; so != &slirp->udb; so = so_next) { + int revents; + + so_next = so->so_next; + + revents = 0; + if (so->pollfds_idx != -1) { + revents = get_revents(so->pollfds_idx, opaque); + } + + if (so->s != -1 && + (revents & (SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR))) { + sorecvfrom(so); + } + } + + /* + * Check incoming ICMP relies. + */ + for (so = slirp->icmp.so_next; so != &slirp->icmp; so = so_next) { + int revents; + + so_next = so->so_next; + + revents = 0; + if (so->pollfds_idx != -1) { + revents = get_revents(so->pollfds_idx, opaque); + } + + if (so->s != -1 && + (revents & (SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR))) { + if (so->so_type == IPPROTO_IPV6 || so->so_type == IPPROTO_ICMPV6) + icmp6_receive(so); + else + icmp_receive(so); + } + } + } + + if_start(slirp); +} + +static void arp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len) +{ + const struct slirp_arphdr *ah = + (const struct slirp_arphdr *)(pkt + ETH_HLEN); + uint8_t arp_reply[MAX(2 + ETH_HLEN + sizeof(struct slirp_arphdr), 2 + 64)]; + struct ethhdr *reh = (struct ethhdr *)(arp_reply + 2); + struct slirp_arphdr *rah = (struct slirp_arphdr *)(arp_reply + 2 + ETH_HLEN); + int ar_op; + struct gfwd_list *ex_ptr; + + if (!slirp->in_enabled) { + return; + } + + if (pkt_len < ETH_HLEN + sizeof(struct slirp_arphdr)) { + return; /* packet too short */ + } + + ar_op = ntohs(ah->ar_op); + switch (ar_op) { + case ARPOP_REQUEST: + if (ah->ar_tip == ah->ar_sip) { + /* Gratuitous ARP */ + arp_table_add(slirp, ah->ar_sip, ah->ar_sha); + return; + } + + if ((ah->ar_tip & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + if (ah->ar_tip == slirp->vnameserver_addr.s_addr || + ah->ar_tip == slirp->vhost_addr.s_addr) + goto arp_ok; + /* TODO: IPv6 */ + for (ex_ptr = slirp->guestfwd_list; ex_ptr; + ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->ex_addr.s_addr == ah->ar_tip) + goto arp_ok; + } + return; + arp_ok: + memset(arp_reply, 0, sizeof(arp_reply)); + + arp_table_add(slirp, ah->ar_sip, ah->ar_sha); + + /* ARP request for alias/dns mac address */ + memcpy(reh->h_dest, pkt + ETH_ALEN, ETH_ALEN); + memcpy(reh->h_source, special_ethaddr, ETH_ALEN - 4); + memcpy(&reh->h_source[2], &ah->ar_tip, 4); + reh->h_proto = htons(ETH_P_ARP); + + rah->ar_hrd = htons(1); + rah->ar_pro = htons(ETH_P_IP); + rah->ar_hln = ETH_ALEN; + rah->ar_pln = 4; + rah->ar_op = htons(ARPOP_REPLY); + memcpy(rah->ar_sha, reh->h_source, ETH_ALEN); + rah->ar_sip = ah->ar_tip; + memcpy(rah->ar_tha, ah->ar_sha, ETH_ALEN); + rah->ar_tip = ah->ar_sip; + slirp_send_packet_all(slirp, arp_reply + 2, sizeof(arp_reply) - 2); + } + break; + case ARPOP_REPLY: + arp_table_add(slirp, ah->ar_sip, ah->ar_sha); + break; + default: + break; + } +} + +void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len) +{ + struct mbuf *m; + int proto; + + if (pkt_len < ETH_HLEN) + return; + + proto = (((uint16_t)pkt[12]) << 8) + pkt[13]; + switch (proto) { + case ETH_P_ARP: + arp_input(slirp, pkt, pkt_len); + break; + case ETH_P_IP: + case ETH_P_IPV6: + m = m_get(slirp); + if (!m) + return; + /* Note: we add 2 to align the IP header on 8 bytes despite the ethernet + * header, and add the margin for the tcpiphdr overhead */ + if (M_FREEROOM(m) < pkt_len + TCPIPHDR_DELTA + 2) { + m_inc(m, pkt_len + TCPIPHDR_DELTA + 2); + } + m->m_len = pkt_len + TCPIPHDR_DELTA + 2; + memcpy(m->m_data + TCPIPHDR_DELTA + 2, pkt, pkt_len); + + m->m_data += TCPIPHDR_DELTA + 2 + ETH_HLEN; + m->m_len -= TCPIPHDR_DELTA + 2 + ETH_HLEN; + + if (proto == ETH_P_IP) { + ip_input(m); + } else if (proto == ETH_P_IPV6) { + ip6_input(m); + } + break; + + case ETH_P_NCSI: + ncsi_input(slirp, pkt, pkt_len); + break; + + default: + break; + } +} + +/* Prepare the IPv4 packet to be sent to the ethernet device. Returns 1 if no + * packet should be sent, 0 if the packet must be re-queued, 2 if the packet + * is ready to go. + */ +static int if_encap4(Slirp *slirp, struct mbuf *ifm, struct ethhdr *eh, + uint8_t ethaddr[ETH_ALEN]) +{ + const struct ip *iph = (const struct ip *)ifm->m_data; + + if (!arp_table_search(slirp, iph->ip_dst.s_addr, ethaddr)) { + uint8_t arp_req[2 + ETH_HLEN + sizeof(struct slirp_arphdr)]; + struct ethhdr *reh = (struct ethhdr *)(arp_req + 2); + struct slirp_arphdr *rah = (struct slirp_arphdr *)(arp_req + 2 + ETH_HLEN); + + if (!ifm->resolution_requested) { + /* If the client addr is not known, send an ARP request */ + memset(reh->h_dest, 0xff, ETH_ALEN); + memcpy(reh->h_source, special_ethaddr, ETH_ALEN - 4); + memcpy(&reh->h_source[2], &slirp->vhost_addr, 4); + reh->h_proto = htons(ETH_P_ARP); + rah->ar_hrd = htons(1); + rah->ar_pro = htons(ETH_P_IP); + rah->ar_hln = ETH_ALEN; + rah->ar_pln = 4; + rah->ar_op = htons(ARPOP_REQUEST); + + /* source hw addr */ + memcpy(rah->ar_sha, special_ethaddr, ETH_ALEN - 4); + memcpy(&rah->ar_sha[2], &slirp->vhost_addr, 4); + + /* source IP */ + rah->ar_sip = slirp->vhost_addr.s_addr; + + /* target hw addr (none) */ + memset(rah->ar_tha, 0, ETH_ALEN); + + /* target IP */ + rah->ar_tip = iph->ip_dst.s_addr; + slirp->client_ipaddr = iph->ip_dst; + slirp_send_packet_all(slirp, arp_req + 2, sizeof(arp_req) - 2); + ifm->resolution_requested = true; + + /* Expire request and drop outgoing packet after 1 second */ + ifm->expiration_date = + slirp->cb->clock_get_ns(slirp->opaque) + 1000000000ULL; + } + return 0; + } else { + memcpy(eh->h_source, special_ethaddr, ETH_ALEN - 4); + /* XXX: not correct */ + memcpy(&eh->h_source[2], &slirp->vhost_addr, 4); + eh->h_proto = htons(ETH_P_IP); + + /* Send this */ + return 2; + } +} + +/* Prepare the IPv6 packet to be sent to the ethernet device. Returns 1 if no + * packet should be sent, 0 if the packet must be re-queued, 2 if the packet + * is ready to go. + */ +static int if_encap6(Slirp *slirp, struct mbuf *ifm, struct ethhdr *eh, + uint8_t ethaddr[ETH_ALEN]) +{ + const struct ip6 *ip6h = mtod(ifm, const struct ip6 *); + if (!ndp_table_search(slirp, ip6h->ip_dst, ethaddr)) { + if (!ifm->resolution_requested) { + ndp_send_ns(slirp, ip6h->ip_dst); + ifm->resolution_requested = true; + ifm->expiration_date = + slirp->cb->clock_get_ns(slirp->opaque) + 1000000000ULL; + } + return 0; + } else { + eh->h_proto = htons(ETH_P_IPV6); + in6_compute_ethaddr(ip6h->ip_src, eh->h_source); + + /* Send this */ + return 2; + } +} + +/* Output the IP packet to the ethernet device. Returns 0 if the packet must be + * re-queued. + */ +int if_encap(Slirp *slirp, struct mbuf *ifm) +{ + uint8_t buf[IF_MTU_MAX + 100]; + struct ethhdr *eh = (struct ethhdr *)(buf + 2); + uint8_t ethaddr[ETH_ALEN]; + const struct ip *iph = (const struct ip *)ifm->m_data; + int ret; + char ethaddr_str[ETH_ADDRSTRLEN]; + + if (ifm->m_len + ETH_HLEN > sizeof(buf) - 2) { + return 1; + } + + switch (iph->ip_v) { + case IPVERSION: + ret = if_encap4(slirp, ifm, eh, ethaddr); + if (ret < 2) { + return ret; + } + break; + + case IP6VERSION: + ret = if_encap6(slirp, ifm, eh, ethaddr); + if (ret < 2) { + return ret; + } + break; + + default: + g_assert_not_reached(); + } + + memcpy(eh->h_dest, ethaddr, ETH_ALEN); + DEBUG_ARG("src = %s", slirp_ether_ntoa(eh->h_source, ethaddr_str, + sizeof(ethaddr_str))); + DEBUG_ARG("dst = %s", slirp_ether_ntoa(eh->h_dest, ethaddr_str, + sizeof(ethaddr_str))); + memcpy(buf + 2 + sizeof(struct ethhdr), ifm->m_data, ifm->m_len); + slirp_send_packet_all(slirp, buf + 2, ifm->m_len + ETH_HLEN); + return 1; +} + +/* Drop host forwarding rule, return 0 if found. */ +int slirp_remove_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, + int host_port) +{ + struct socket *so; + struct socket *head = (is_udp ? &slirp->udb : &slirp->tcb); + struct sockaddr_in addr; + int port = htons(host_port); + socklen_t addr_len; + + for (so = head->so_next; so != head; so = so->so_next) { + addr_len = sizeof(addr); + if ((so->so_state & SS_HOSTFWD) && + getsockname(so->s, (struct sockaddr *)&addr, &addr_len) == 0 && + addr_len == sizeof(addr) && + addr.sin_family == AF_INET && + addr.sin_addr.s_addr == host_addr.s_addr && + addr.sin_port == port) { + so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); + closesocket(so->s); + sofree(so); + return 0; + } + } + + return -1; +} + +int slirp_add_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, + int host_port, struct in_addr guest_addr, int guest_port) +{ + if (!guest_addr.s_addr) { + guest_addr = slirp->vdhcp_startaddr; + } + if (is_udp) { + if (!udp_listen(slirp, host_addr.s_addr, htons(host_port), + guest_addr.s_addr, htons(guest_port), SS_HOSTFWD)) + return -1; + } else { + if (!tcp_listen(slirp, host_addr.s_addr, htons(host_port), + guest_addr.s_addr, htons(guest_port), SS_HOSTFWD)) + return -1; + } + return 0; +} + +int slirp_remove_hostxfwd(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + int flags) +{ + struct socket *so; + struct socket *head = (flags & SLIRP_HOSTFWD_UDP ? &slirp->udb : &slirp->tcb); + struct sockaddr_storage addr; + socklen_t addr_len; + + for (so = head->so_next; so != head; so = so->so_next) { + addr_len = sizeof(addr); + if ((so->so_state & SS_HOSTFWD) && + getsockname(so->s, (struct sockaddr *)&addr, &addr_len) == 0 && + sockaddr_equal(&addr, (const struct sockaddr_storage *) haddr)) { + so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); + closesocket(so->s); + sofree(so); + return 0; + } + } + + return -1; +} + +int slirp_add_hostxfwd(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *gaddr, socklen_t gaddrlen, + int flags) +{ + struct sockaddr_in gdhcp_addr; + int fwd_flags = SS_HOSTFWD; + + if (flags & SLIRP_HOSTFWD_V6ONLY) + fwd_flags |= SS_HOSTFWD_V6ONLY; + + if (gaddr->sa_family == AF_INET) { + const struct sockaddr_in *gaddr_in = (const struct sockaddr_in *) gaddr; + + if (gaddrlen < sizeof(struct sockaddr_in)) { + errno = EINVAL; + return -1; + } + + if (!gaddr_in->sin_addr.s_addr) { + gdhcp_addr = *gaddr_in; + gdhcp_addr.sin_addr = slirp->vdhcp_startaddr; + gaddr = (struct sockaddr *) &gdhcp_addr; + gaddrlen = sizeof(gdhcp_addr); + } + } else { + if (gaddrlen < sizeof(struct sockaddr_in6)) { + errno = EINVAL; + return -1; + } + + /* + * Libslirp currently only provides a stateless DHCPv6 server, thus + * we can't translate "addr-any" to the guest here. Instead, we defer + * performing the translation to when it's needed. See + * soassign_guest_addr_if_needed(). + */ + } + + if (flags & SLIRP_HOSTFWD_UDP) { + if (!udpx_listen(slirp, haddr, haddrlen, + gaddr, gaddrlen, + fwd_flags)) + return -1; + } else { + if (!tcpx_listen(slirp, haddr, haddrlen, + gaddr, gaddrlen, + fwd_flags)) + return -1; + } + return 0; +} + +/* TODO: IPv6 */ +static bool check_guestfwd(Slirp *slirp, struct in_addr *guest_addr, + int guest_port) +{ + struct gfwd_list *tmp_ptr; + + if (!guest_addr->s_addr) { + guest_addr->s_addr = slirp->vnetwork_addr.s_addr | + (htonl(0x0204) & ~slirp->vnetwork_mask.s_addr); + } + if ((guest_addr->s_addr & slirp->vnetwork_mask.s_addr) != + slirp->vnetwork_addr.s_addr || + guest_addr->s_addr == slirp->vhost_addr.s_addr || + guest_addr->s_addr == slirp->vnameserver_addr.s_addr) { + return false; + } + + /* check if the port is "bound" */ + for (tmp_ptr = slirp->guestfwd_list; tmp_ptr; tmp_ptr = tmp_ptr->ex_next) { + if (guest_port == tmp_ptr->ex_fport && + guest_addr->s_addr == tmp_ptr->ex_addr.s_addr) + return false; + } + + return true; +} + +int slirp_add_exec(Slirp *slirp, const char *cmdline, + struct in_addr *guest_addr, int guest_port) +{ + if (!check_guestfwd(slirp, guest_addr, guest_port)) { + return -1; + } + + add_exec(&slirp->guestfwd_list, cmdline, *guest_addr, htons(guest_port)); + return 0; +} + +int slirp_add_unix(Slirp *slirp, const char *unixsock, + struct in_addr *guest_addr, int guest_port) +{ +#ifdef G_OS_UNIX + if (!check_guestfwd(slirp, guest_addr, guest_port)) { + return -1; + } + + add_unix(&slirp->guestfwd_list, unixsock, *guest_addr, htons(guest_port)); + return 0; +#else + g_warn_if_reached(); + return -1; +#endif +} + +int slirp_add_guestfwd(Slirp *slirp, SlirpWriteCb write_cb, void *opaque, + struct in_addr *guest_addr, int guest_port) +{ + if (!check_guestfwd(slirp, guest_addr, guest_port)) { + return -1; + } + + add_guestfwd(&slirp->guestfwd_list, write_cb, opaque, *guest_addr, + htons(guest_port)); + return 0; +} + +int slirp_remove_guestfwd(Slirp *slirp, struct in_addr guest_addr, + int guest_port) +{ + return remove_guestfwd(&slirp->guestfwd_list, guest_addr, + htons(guest_port)); +} + +slirp_ssize_t slirp_send(struct socket *so, const void *buf, size_t len, int flags) +{ + if (so->s == -1 && so->guestfwd) { + /* XXX this blocks entire thread. Rewrite to use + * qemu_chr_fe_write and background I/O callbacks */ + so->guestfwd->write_cb(buf, len, so->guestfwd->opaque); + return len; + } + + if (so->s == -1) { + /* + * This should in theory not happen but it is hard to be + * sure because some code paths will end up with so->s == -1 + * on a failure but don't dispose of the struct socket. + * Check specifically, so we don't pass -1 to send(). + */ + errno = EBADF; + return -1; + } + + return send(so->s, buf, len, flags); +} + +struct socket *slirp_find_ctl_socket(Slirp *slirp, struct in_addr guest_addr, + int guest_port) +{ + struct socket *so; + + /* TODO: IPv6 */ + for (so = slirp->tcb.so_next; so != &slirp->tcb; so = so->so_next) { + if (so->so_faddr.s_addr == guest_addr.s_addr && + htons(so->so_fport) == guest_port) { + return so; + } + } + return NULL; +} + +size_t slirp_socket_can_recv(Slirp *slirp, struct in_addr guest_addr, + int guest_port) +{ + struct iovec iov[2]; + struct socket *so; + + so = slirp_find_ctl_socket(slirp, guest_addr, guest_port); + + if (!so || so->so_state & SS_NOFDREF) { + return 0; + } + + if (!CONN_CANFRCV(so) || so->so_snd.sb_cc >= (so->so_snd.sb_datalen / 2)) { + /* If the sb is already half full, we will wait for the guest to consume it, + * and notify again in sbdrop() when the sb becomes less than half full. */ + return 0; + } + + return sopreprbuf(so, iov, NULL); +} + +void slirp_socket_recv(Slirp *slirp, struct in_addr guest_addr, int guest_port, + const uint8_t *buf, int size) +{ + int ret; + struct socket *so = slirp_find_ctl_socket(slirp, guest_addr, guest_port); + + if (!so) + return; + + ret = soreadbuf(so, (const char *)buf, size); + + if (ret > 0) + tcp_output(sototcpcb(so)); +} + +void slirp_send_packet_all(Slirp *slirp, const void *buf, size_t len) +{ + slirp_ssize_t ret; + + if (len < ETH_MINLEN) { + char tmp[ETH_MINLEN]; + memcpy(tmp, buf, len); + memset(tmp + len, 0, ETH_MINLEN - len); + + ret = slirp->cb->send_packet(tmp, ETH_MINLEN, slirp->opaque); + } else { + ret = slirp->cb->send_packet(buf, len, slirp->opaque); + } + + if (ret < 0) { + g_critical("Failed to send packet, ret: %ld", (long)ret); + } else if (ret < len) { + DEBUG_ERROR("send_packet() didn't send all data: %ld < %lu", (long)ret, + (unsigned long)len); + } +} diff --git a/app/src/main/cpp/libslirp/src/slirp.h b/app/src/main/cpp/libslirp/src/slirp.h new file mode 100644 index 00000000..2715353f --- /dev/null +++ b/app/src/main/cpp/libslirp/src/slirp.h @@ -0,0 +1,386 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef SLIRP_H +#define SLIRP_H + +#ifdef _WIN32 + +/* as defined in sdkddkver.h */ +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 /* Windows 7 */ +#endif +/* reduces the number of implicitly included headers */ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include +#include +#include +#include +#include + +#else +#define O_BINARY 0 +#endif + +#ifndef _WIN32 +#include +#include +#include +#include +#include +#endif + +#ifdef __APPLE__ +#include +#endif + +#include "debug.h" +#include "util.h" + +#include "libslirp.h" +#include "ip.h" +#include "ip6.h" +#include "tcp.h" +#include "tcp_timer.h" +#include "tcp_var.h" +#include "tcpip.h" +#include "udp.h" +#include "ip_icmp.h" +#include "ip6_icmp.h" +#include "mbuf.h" +#include "sbuf.h" +#include "socket.h" +#include "if.h" +#include "main.h" +#include "misc.h" + +#include "bootp.h" +#include "tftp.h" + +#define ARPOP_REQUEST 1 /* ARP request */ +#define ARPOP_REPLY 2 /* ARP reply */ + +struct ethhdr { + unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ + unsigned char h_source[ETH_ALEN]; /* source ether addr */ + unsigned short h_proto; /* packet type ID field */ +}; + +SLIRP_PACKED_BEGIN +struct slirp_arphdr { + unsigned short ar_hrd; /* format of hardware address */ + unsigned short ar_pro; /* format of protocol address */ + unsigned char ar_hln; /* length of hardware address */ + unsigned char ar_pln; /* length of protocol address */ + unsigned short ar_op; /* ARP opcode (command) */ + + /* + * Ethernet looks like this : This bit is variable sized however... + */ + uint8_t ar_sha[ETH_ALEN]; /* sender hardware address */ + uint32_t ar_sip; /* sender IP address */ + uint8_t ar_tha[ETH_ALEN]; /* target hardware address */ + uint32_t ar_tip; /* target IP address */ +} SLIRP_PACKED_END; + +#define ARP_TABLE_SIZE 16 + +typedef struct ArpTable { + struct slirp_arphdr table[ARP_TABLE_SIZE]; + int next_victim; +} ArpTable; + +/* Add a new ARP entry for the given addresses */ +void arp_table_add(Slirp *slirp, uint32_t ip_addr, + const uint8_t ethaddr[ETH_ALEN]); + +/* Look for an ARP entry for the given IP address */ +bool arp_table_search(Slirp *slirp, uint32_t ip_addr, + uint8_t out_ethaddr[ETH_ALEN]); + +struct ndpentry { + uint8_t eth_addr[ETH_ALEN]; /* sender hardware address */ + struct in6_addr ip_addr; /* sender IP address */ +}; + +#define NDP_TABLE_SIZE 16 + +typedef struct NdpTable { + struct ndpentry table[NDP_TABLE_SIZE]; + /* + * The table is a cache with old entries overwritten when the table fills. + * Preserve the first entry: it is the guest, which is needed for lazy + * hostfwd guest address assignment. + */ + struct in6_addr guest_in6_addr; + int next_victim; +} NdpTable; + +/* Add a new NDP entry for the given addresses */ +void ndp_table_add(Slirp *slirp, struct in6_addr ip_addr, + uint8_t ethaddr[ETH_ALEN]); + +/* Look for an NDP entry for the given IPv6 address */ +bool ndp_table_search(Slirp *slirp, struct in6_addr ip_addr, + uint8_t out_ethaddr[ETH_ALEN]); + +/* Slirp configuration, specified by the application */ +struct Slirp { + int cfg_version; + + unsigned time_fasttimo; + unsigned last_slowtimo; + bool do_slowtimo; + + bool in_enabled, in6_enabled; + + /* virtual network configuration */ + struct in_addr vnetwork_addr; + struct in_addr vnetwork_mask; + struct in_addr vhost_addr; + struct in6_addr vprefix_addr6; + uint8_t vprefix_len; + struct in6_addr vhost_addr6; + bool disable_dhcp; /* slirp will not reply to any DHCP requests */ + struct in_addr vdhcp_startaddr; + struct in_addr vnameserver_addr; + struct in6_addr vnameserver_addr6; + + struct in_addr client_ipaddr; + char client_hostname[33]; + + int restricted; + struct gfwd_list *guestfwd_list; + + int if_mtu; + int if_mru; + + bool disable_host_loopback; + + uint32_t mfr_id; + uint8_t oob_eth_addr[ETH_ALEN]; + + /* mbuf states */ + struct slirp_quehead m_freelist; + struct slirp_quehead m_usedlist; + int mbuf_alloced; + + /* if states */ + struct slirp_quehead if_fastq; /* fast queue (for interactive data) */ + struct slirp_quehead if_batchq; /* queue for non-interactive data */ + bool if_start_busy; /* avoid if_start recursion */ + + /* ip states */ + struct ipq ipq; /* ip reass. queue */ + uint16_t ip_id; /* ip packet ctr, for ids */ + + /* bootp/dhcp states */ + BOOTPClient bootp_clients[NB_BOOTP_CLIENTS]; + char *bootp_filename; + size_t vdnssearch_len; + uint8_t *vdnssearch; + char *vdomainname; + + /* tcp states */ + struct socket tcb; + struct socket *tcp_last_so; + tcp_seq tcp_iss; /* tcp initial send seq # */ + uint32_t tcp_now; /* for RFC 1323 timestamps */ + + /* udp states */ + struct socket udb; + struct socket *udp_last_so; + + /* icmp states */ + struct socket icmp; + struct socket *icmp_last_so; + + /* tftp states */ + char *tftp_prefix; + struct tftp_session tftp_sessions[TFTP_SESSIONS_MAX]; + char *tftp_server_name; + + ArpTable arp_table; + NdpTable ndp_table; + + GRand *grand; + void *ra_timer; + + bool enable_emu; + + const SlirpCb *cb; + void *opaque; + + struct sockaddr_in *outbound_addr; + struct sockaddr_in6 *outbound_addr6; + bool disable_dns; /* slirp will not redirect/serve any DNS packet */ +}; + +/* + * Send one packet from each session. + * If there are packets on the fastq, they are sent FIFO, before + * everything else. Then we choose the first packet from each + * batchq session (socket) and send it. + * For example, if there are 3 ftp sessions fighting for bandwidth, + * one packet will be sent from the first session, then one packet + * from the second session, then one packet from the third. + */ +void if_start(Slirp *); + +/* Get the address of the DNS server on the host side */ +int get_dns_addr(struct in_addr *pdns_addr); + +/* Get the IPv6 address of the DNS server on the host side */ +int get_dns6_addr(struct in6_addr *pdns6_addr, uint32_t *scope_id); + +/* ncsi.c */ + +/* Process NCSI packet coming from the guest */ +void ncsi_input(Slirp *slirp, const uint8_t *pkt, int pkt_len); + +#ifndef _WIN32 +#include +#endif + +/* Whether we should send TCP keepalive packets */ +extern bool slirp_do_keepalive; + +#define TCP_MAXIDLE (TCPTV_KEEPCNT * TCPTV_KEEPINTVL) + +/* dnssearch.c */ +/* Translate from vdnssearch in configuration, into Slirp */ +int translate_dnssearch(Slirp *s, const char **names); + +/* cksum.c */ +/* Compute the checksum of the mbuf */ +int cksum(struct mbuf *m, int len); +/* Compute the checksum of the mbuf which contains an IPv6 packet */ +int ip6_cksum(struct mbuf *m); + +/* if.c */ +/* Called from slirp_new */ +void if_init(Slirp *); +/* Queue packet into an output queue (fast or batch), for sending to the guest */ +void if_output(struct socket *, struct mbuf *); + +/* ip_input.c */ +/* Called from slirp_new */ +void ip_init(Slirp *); +/* Called from slirp_cleanup */ +void ip_cleanup(Slirp *); +/* Process IPv4 packet coming from the guest */ +void ip_input(struct mbuf *); +/* + * IP timer processing; + * if a timer expires on a reassembly + * queue, discard it. + */ +void ip_slowtimo(Slirp *); +/* + * Strip out IP options, at higher + * level protocol in the kernel. + */ +void ip_stripoptions(register struct mbuf *); + +/* ip_output.c */ +/* Send IPv4 packet to the guest */ +int ip_output(struct socket *, struct mbuf *); + +/* ip6_input.c */ +/* Called from slirp_new, but after other initialization */ +void ip6_post_init(Slirp *); +/* Called from slirp_cleanup */ +void ip6_cleanup(Slirp *); +/* Process IPv6 packet coming from the guest */ +void ip6_input(struct mbuf *); + +/* ip6_output */ +/* Send IPv6 packet to the guest */ +int ip6_output(struct socket *, struct mbuf *, int fast); + +/* tcp_input.c */ +/* Process TCP datagram coming from the guest */ +void tcp_input(register struct mbuf *, int, struct socket *, unsigned short af); +/* Determine a reasonable value for maxseg size */ +int tcp_mss(register struct tcpcb *, unsigned offer); + +/* tcp_output.c */ +/* Send TCP datagram to the guest */ +int tcp_output(register struct tcpcb *); +/* Start/restart persistence timer */ +void tcp_setpersist(register struct tcpcb *); + +/* tcp_subr.c */ +/* Called from slirp_new */ +void tcp_init(Slirp *); +/* Called from slirp_cleanup */ +void tcp_cleanup(Slirp *); +/* + * Create template to be used to send tcp packets on a connection. + * Call after host entry created, fills + * in a skeletal tcp/ip header, minimizing the amount of work + * necessary when the connection is used. + */ +void tcp_template(struct tcpcb *); +/* + * Send a single message to the TCP at address specified by + * the given TCP/IP header. + */ +void tcp_respond(struct tcpcb *, register struct tcpiphdr *, + register struct mbuf *, tcp_seq, tcp_seq, int, unsigned short); +/* + * Create a new TCP control block, making an + * empty reassembly queue and hooking it to the argument + * protocol control block. + */ +struct tcpcb *tcp_newtcpcb(struct socket *); +/* + * Close a TCP control block: + * discard all space held by the tcp + * discard internet protocol block + * wake up any sleepers + */ +struct tcpcb *tcp_close(register struct tcpcb *); +/* The Internet socket got closed, tell the guest */ +void tcp_sockclosed(struct tcpcb *); +/* + * Connect to a host on the Internet + * Called by tcp_input + */ +int tcp_fconnect(struct socket *, unsigned short af); +/* Accept the connection from the Internet, and connect to the guest */ +void tcp_connect(struct socket *); +/* Attach a TCPCB to a socket */ +void tcp_attach(struct socket *); +/* * Return TOS according to the ports */ +uint8_t tcp_tos(struct socket *); +/* + * We received a packet from the guest. + * + * Emulate programs that try and connect to us + * This includes ftp (the data connection is + * initiated by the server) and IRC (DCC CHAT and + * DCC SEND) for now + */ +int tcp_emu(struct socket *, struct mbuf *); +/* Configure the socket, now that the guest completed accepting the connection */ +int tcp_ctl(struct socket *); +/* + * Drop a TCP connection, reporting + * the specified error. If connection is synchronized, + * then send a RST to peer. + */ +struct tcpcb *tcp_drop(struct tcpcb *tp, int err); + +/* Find the socket for the guest address and port */ +struct socket *slirp_find_ctl_socket(Slirp *slirp, struct in_addr guest_addr, + int guest_port); + +/* Send a frame to the virtual Ethernet board, i.e. call the application send_packet callback */ +void slirp_send_packet_all(Slirp *slirp, const void *buf, size_t len); + +/* Create a new timer, i.e. call the application timer_new callback */ +void *slirp_timer_new(Slirp *slirp, SlirpTimerId id, void *cb_opaque); + +#endif diff --git a/app/src/main/cpp/libslirp/src/socket.c b/app/src/main/cpp/libslirp/src/socket.c new file mode 100644 index 00000000..51a13647 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/socket.c @@ -0,0 +1,1249 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" +#include "ip_icmp.h" +#ifdef __sun__ +#include +#endif +#ifdef __linux__ +#include +#endif + +static void sofcantrcvmore(struct socket *so); +static void sofcantsendmore(struct socket *so); + +struct socket *solookup(struct socket **last, struct socket *head, + struct sockaddr_storage *lhost, + struct sockaddr_storage *fhost) +{ + struct socket *so = *last; + + /* Optimisation */ + if (so != head && sockaddr_equal(&(so->lhost.ss), lhost) && + (!fhost || sockaddr_equal(&so->fhost.ss, fhost))) { + return so; + } + + for (so = head->so_next; so != head; so = so->so_next) { + if (sockaddr_equal(&(so->lhost.ss), lhost) && + (!fhost || sockaddr_equal(&so->fhost.ss, fhost))) { + *last = so; + return so; + } + } + + return (struct socket *)NULL; +} + +/* + * Create a new socket, initialise the fields + * It is the responsibility of the caller to + * slirp_insque() it into the correct linked-list + */ +struct socket *socreate(Slirp *slirp, int type) +{ + struct socket *so = g_new(struct socket, 1); + + memset(so, 0, sizeof(struct socket)); + so->so_type = type; + so->so_state = SS_NOFDREF; + so->s = -1; + so->s_aux = -1; + so->slirp = slirp; + so->pollfds_idx = -1; + + return so; +} + +/* + * Remove references to so from the given message queue. + */ +static void soqfree(struct socket *so, struct slirp_quehead *qh) +{ + struct mbuf *ifq; + + for (ifq = (struct mbuf *)qh->qh_link; (struct slirp_quehead *)ifq != qh; + ifq = ifq->m_next) { + if (ifq->m_so == so) { + struct mbuf *ifm; + ifq->m_so = NULL; + for (ifm = ifq->m_nextpkt; ifm != ifq; ifm = ifm->m_nextpkt) { + ifm->m_so = NULL; + } + } + } +} + +/* + * slirp_remque and free a socket, clobber cache + */ +void sofree(struct socket *so) +{ + Slirp *slirp = so->slirp; + + if (so->s_aux != -1) { + closesocket(so->s_aux); + } + + soqfree(so, &slirp->if_fastq); + soqfree(so, &slirp->if_batchq); + + if (so == slirp->tcp_last_so) { + slirp->tcp_last_so = &slirp->tcb; + } else if (so == slirp->udp_last_so) { + slirp->udp_last_so = &slirp->udb; + } else if (so == slirp->icmp_last_so) { + slirp->icmp_last_so = &slirp->icmp; + } + m_free(so->so_m); + + if (so->so_next && so->so_prev) + slirp_remque(so); /* crashes if so is not in a queue */ + + if (so->so_tcpcb) { + g_free(so->so_tcpcb); + } + g_free(so); +} + +size_t sopreprbuf(struct socket *so, struct iovec *iov, int *np) +{ + int n, lss, total; + struct sbuf *sb = &so->so_snd; + int len = sb->sb_datalen - sb->sb_cc; + int mss = so->so_tcpcb->t_maxseg; + + DEBUG_CALL("sopreprbuf"); + DEBUG_ARG("so = %p", so); + + if (len <= 0) + return 0; + + iov[0].iov_base = sb->sb_wptr; + iov[1].iov_base = NULL; + iov[1].iov_len = 0; + if (sb->sb_wptr < sb->sb_rptr) { + iov[0].iov_len = sb->sb_rptr - sb->sb_wptr; + /* Should never succeed, but... */ + if (iov[0].iov_len > len) + iov[0].iov_len = len; + if (iov[0].iov_len > mss) + iov[0].iov_len -= iov[0].iov_len % mss; + n = 1; + } else { + iov[0].iov_len = (sb->sb_data + sb->sb_datalen) - sb->sb_wptr; + /* Should never succeed, but... */ + if (iov[0].iov_len > len) + iov[0].iov_len = len; + len -= iov[0].iov_len; + if (len) { + iov[1].iov_base = sb->sb_data; + iov[1].iov_len = sb->sb_rptr - sb->sb_data; + if (iov[1].iov_len > len) + iov[1].iov_len = len; + total = iov[0].iov_len + iov[1].iov_len; + if (total > mss) { + lss = total % mss; + if (iov[1].iov_len > lss) { + iov[1].iov_len -= lss; + n = 2; + } else { + lss -= iov[1].iov_len; + iov[0].iov_len -= lss; + n = 1; + } + } else + n = 2; + } else { + if (iov[0].iov_len > mss) + iov[0].iov_len -= iov[0].iov_len % mss; + n = 1; + } + } + if (np) + *np = n; + + return iov[0].iov_len + (n - 1) * iov[1].iov_len; +} + +/* + * Read from so's socket into sb_snd, updating all relevant sbuf fields + * NOTE: This will only be called if it is select()ed for reading, so + * a read() of 0 (or less) means it's disconnected + */ +int soread(struct socket *so) +{ + int n, nn; + size_t buf_len; + struct sbuf *sb = &so->so_snd; + struct iovec iov[2]; + + DEBUG_CALL("soread"); + DEBUG_ARG("so = %p", so); + + /* + * No need to check if there's enough room to read. + * soread wouldn't have been called if there weren't + */ + buf_len = sopreprbuf(so, iov, &n); + assert(buf_len != 0); + + nn = recv(so->s, iov[0].iov_base, iov[0].iov_len, 0); + if (nn <= 0) { + if (nn < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + else { + int err; + socklen_t elen = sizeof err; + struct sockaddr_storage addr; + struct sockaddr *paddr = (struct sockaddr *)&addr; + socklen_t alen = sizeof addr; + + err = errno; + if (nn == 0) { + int shutdown_wr = so->so_state & SS_FCANTSENDMORE; + + if (!shutdown_wr && getpeername(so->s, paddr, &alen) < 0) { + err = errno; + } else { + getsockopt(so->s, SOL_SOCKET, SO_ERROR, &err, &elen); + } + } + + DEBUG_MISC(" --- soread() disconnected, nn = %d, errno = %d-%s", nn, + errno, strerror(errno)); + sofcantrcvmore(so); + + if (err == ECONNABORTED || err == ECONNRESET || err == ECONNREFUSED || + err == ENOTCONN || err == EPIPE) { + tcp_drop(sototcpcb(so), err); + } else { + tcp_sockclosed(sototcpcb(so)); + } + return -1; + } + } + + /* + * If there was no error, try and read the second time round + * We read again if n = 2 (ie, there's another part of the buffer) + * and we read as much as we could in the first read + * We don't test for <= 0 this time, because there legitimately + * might not be any more data (since the socket is non-blocking), + * a close will be detected on next iteration. + * A return of -1 won't (shouldn't) happen, since it didn't happen above + */ + if (n == 2 && nn == iov[0].iov_len) { + int ret; + ret = recv(so->s, iov[1].iov_base, iov[1].iov_len, 0); + if (ret > 0) + nn += ret; + } + + DEBUG_MISC(" ... read nn = %d bytes", nn); + + /* Update fields */ + sb->sb_cc += nn; + sb->sb_wptr += nn; + if (sb->sb_wptr >= (sb->sb_data + sb->sb_datalen)) + sb->sb_wptr -= sb->sb_datalen; + return nn; +} + +int soreadbuf(struct socket *so, const char *buf, int size) +{ + int n, nn, copy = size; + struct sbuf *sb = &so->so_snd; + struct iovec iov[2]; + + DEBUG_CALL("soreadbuf"); + DEBUG_ARG("so = %p", so); + + /* + * No need to check if there's enough room to read. + * soread wouldn't have been called if there weren't + */ + assert(size > 0); + if (sopreprbuf(so, iov, &n) < size) + goto err; + + nn = MIN(iov[0].iov_len, copy); + memcpy(iov[0].iov_base, buf, nn); + + copy -= nn; + buf += nn; + + if (copy == 0) + goto done; + + memcpy(iov[1].iov_base, buf, copy); + +done: + /* Update fields */ + sb->sb_cc += size; + sb->sb_wptr += size; + if (sb->sb_wptr >= (sb->sb_data + sb->sb_datalen)) + sb->sb_wptr -= sb->sb_datalen; + return size; +err: + + sofcantrcvmore(so); + tcp_sockclosed(sototcpcb(so)); + g_critical("soreadbuf buffer too small"); + return -1; +} + +/* + * Get urgent data + * + * When the socket is created, we set it SO_OOBINLINE, + * so when OOB data arrives, we soread() it and everything + * in the send buffer is sent as urgent data + */ +int sorecvoob(struct socket *so) +{ + struct tcpcb *tp = sototcpcb(so); + int ret; + + DEBUG_CALL("sorecvoob"); + DEBUG_ARG("so = %p", so); + + /* + * We take a guess at how much urgent data has arrived. + * In most situations, when urgent data arrives, the next + * read() should get all the urgent data. This guess will + * be wrong however if more data arrives just after the + * urgent data, or the read() doesn't return all the + * urgent data. + */ + ret = soread(so); + if (ret > 0) { + tp->snd_up = tp->snd_una + so->so_snd.sb_cc; + tp->t_force = 1; + tcp_output(tp); + tp->t_force = 0; + } + + return ret; +} + +/* + * Send urgent data + * There's a lot duplicated code here, but... + */ +int sosendoob(struct socket *so) +{ + struct sbuf *sb = &so->so_rcv; + char buff[2048]; /* XXX Shouldn't be sending more oob data than this */ + + int n; + + DEBUG_CALL("sosendoob"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("sb->sb_cc = %d", sb->sb_cc); + + if (so->so_urgc > sizeof(buff)) + so->so_urgc = sizeof(buff); /* XXXX */ + + if (sb->sb_rptr < sb->sb_wptr) { + /* We can send it directly */ + n = slirp_send(so, sb->sb_rptr, so->so_urgc, + (MSG_OOB)); /* |MSG_DONTWAIT)); */ + } else { + /* + * Since there's no sendv or sendtov like writev, + * we must copy all data to a linear buffer then + * send it all + */ + uint32_t urgc = so->so_urgc; /* Amount of room left in buff */ + int len = (sb->sb_data + sb->sb_datalen) - sb->sb_rptr; + if (len > urgc) { + len = urgc; + } + memcpy(buff, sb->sb_rptr, len); + urgc -= len; + if (urgc) { + /* We still have some room for the rest */ + n = sb->sb_wptr - sb->sb_data; + if (n > urgc) { + n = urgc; + } + memcpy((buff + len), sb->sb_data, n); + len += n; + } + n = slirp_send(so, buff, len, (MSG_OOB)); /* |MSG_DONTWAIT)); */ +#ifdef SLIRP_DEBUG + if (n != len) { + DEBUG_ERROR("Didn't send all data urgently XXXXX"); + } +#endif + } + + if (n < 0) { + return n; + } + so->so_urgc -= n; + DEBUG_MISC(" ---2 sent %d bytes urgent data, %d urgent bytes left", n, + so->so_urgc); + + sb->sb_cc -= n; + sb->sb_rptr += n; + if (sb->sb_rptr >= (sb->sb_data + sb->sb_datalen)) + sb->sb_rptr -= sb->sb_datalen; + + return n; +} + +/* + * Write data from so_rcv to so's socket, + * updating all sbuf field as necessary + */ +int sowrite(struct socket *so) +{ + int n, nn; + struct sbuf *sb = &so->so_rcv; + int len = sb->sb_cc; + struct iovec iov[2]; + + DEBUG_CALL("sowrite"); + DEBUG_ARG("so = %p", so); + + if (so->so_urgc) { + uint32_t expected = so->so_urgc; + if (sosendoob(so) < expected) { + /* Treat a short write as a fatal error too, + * rather than continuing on and sending the urgent + * data as if it were non-urgent and leaving the + * so_urgc count wrong. + */ + goto err_disconnected; + } + if (sb->sb_cc == 0) + return 0; + } + + /* + * No need to check if there's something to write, + * sowrite wouldn't have been called otherwise + */ + + iov[0].iov_base = sb->sb_rptr; + iov[1].iov_base = NULL; + iov[1].iov_len = 0; + if (sb->sb_rptr < sb->sb_wptr) { + iov[0].iov_len = sb->sb_wptr - sb->sb_rptr; + /* Should never succeed, but... */ + if (iov[0].iov_len > len) + iov[0].iov_len = len; + n = 1; + } else { + iov[0].iov_len = (sb->sb_data + sb->sb_datalen) - sb->sb_rptr; + if (iov[0].iov_len > len) + iov[0].iov_len = len; + len -= iov[0].iov_len; + if (len) { + iov[1].iov_base = sb->sb_data; + iov[1].iov_len = sb->sb_wptr - sb->sb_data; + if (iov[1].iov_len > len) + iov[1].iov_len = len; + n = 2; + } else + n = 1; + } + /* Check if there's urgent data to send, and if so, send it */ + + nn = slirp_send(so, iov[0].iov_base, iov[0].iov_len, 0); + /* This should never happen, but people tell me it does *shrug* */ + if (nn < 0 && (errno == EAGAIN || errno == EINTR)) + return 0; + + if (nn <= 0) { + goto err_disconnected; + } + + if (n == 2 && nn == iov[0].iov_len) { + int ret; + ret = slirp_send(so, iov[1].iov_base, iov[1].iov_len, 0); + if (ret > 0) + nn += ret; + } + DEBUG_MISC(" ... wrote nn = %d bytes", nn); + + /* Update sbuf */ + sb->sb_cc -= nn; + sb->sb_rptr += nn; + if (sb->sb_rptr >= (sb->sb_data + sb->sb_datalen)) + sb->sb_rptr -= sb->sb_datalen; + + /* + * If in DRAIN mode, and there's no more data, set + * it CANTSENDMORE + */ + if ((so->so_state & SS_FWDRAIN) && sb->sb_cc == 0) + sofcantsendmore(so); + + return nn; + +err_disconnected: + DEBUG_MISC(" --- sowrite disconnected, so->so_state = %x, errno = %d", + so->so_state, errno); + sofcantsendmore(so); + tcp_sockclosed(sototcpcb(so)); + return -1; +} + +/* + * recvfrom() a UDP socket + */ +void sorecvfrom(struct socket *so) +{ + struct sockaddr_storage addr; + struct sockaddr_storage saddr, daddr; + socklen_t addrlen = sizeof(struct sockaddr_storage); + char buff[256]; + +#ifdef __linux__ + ssize_t size; + struct msghdr msg; + struct iovec iov; + char control[1024]; + + /* First look for errors */ + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &saddr; + msg.msg_namelen = sizeof(saddr); + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + iov.iov_base = buff; + iov.iov_len = sizeof(buff); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + size = recvmsg(so->s, &msg, MSG_ERRQUEUE); + if (size >= 0) { + struct cmsghdr *cmsg; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + + if (cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == IP_RECVERR) { + struct sock_extended_err *ee = + (struct sock_extended_err *) CMSG_DATA(cmsg); + + if (ee->ee_origin == SO_EE_ORIGIN_ICMP) { + /* Got an ICMP error, forward it */ + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *) SO_EE_OFFENDER(ee); + icmp_forward_error(so->so_m, ee->ee_type, ee->ee_code, + 0, NULL, &sin->sin_addr); + } + } + else if (cmsg->cmsg_level == IPPROTO_IPV6 && + cmsg->cmsg_type == IPV6_RECVERR) { + struct sock_extended_err *ee = + (struct sock_extended_err *) CMSG_DATA(cmsg); + + if (ee->ee_origin == SO_EE_ORIGIN_ICMP6) { + /* Got an ICMPv6 error, forward it */ + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *) SO_EE_OFFENDER(ee); + icmp6_forward_error(so->so_m, ee->ee_type, ee->ee_code, + &sin6->sin6_addr); + } + } + } + return; + } +#endif + + DEBUG_CALL("sorecvfrom"); + DEBUG_ARG("so = %p", so); + + if (so->so_type == IPPROTO_ICMP) { /* This is a "ping" reply */ + int len; + + len = recvfrom(so->s, buff, 256, 0, (struct sockaddr *)&addr, &addrlen); + /* XXX Check if reply is "correct"? */ + + if (len == -1 || len == 0) { + uint8_t code = ICMP_UNREACH_PORT; + + if (errno == EHOSTUNREACH) + code = ICMP_UNREACH_HOST; + else if (errno == ENETUNREACH) + code = ICMP_UNREACH_NET; + + DEBUG_MISC(" udp icmp rx errno = %d-%s", errno, strerror(errno)); + icmp_send_error(so->so_m, ICMP_UNREACH, code, 0, strerror(errno)); + } else { + icmp_reflect(so->so_m); + so->so_m = NULL; /* Don't m_free() it again! */ + } + /* No need for this socket anymore, udp_detach it */ + udp_detach(so); + } else if (so->so_type == IPPROTO_ICMPV6) { /* This is a "ping" reply */ + int len; + + len = recvfrom(so->s, buff, 256, 0, (struct sockaddr *)&addr, &addrlen); + /* XXX Check if reply is "correct"? */ + + if (len == -1 || len == 0) { + uint8_t code = ICMP6_UNREACH_PORT; + + if (errno == EHOSTUNREACH) + code = ICMP6_UNREACH_ADDRESS; + else if (errno == ENETUNREACH) + code = ICMP6_UNREACH_NO_ROUTE; + + DEBUG_MISC(" udp icmp6 rx errno = %d-%s", errno, strerror(errno)); + icmp6_send_error(so->so_m, ICMP_UNREACH, code); + } else { + icmp6_reflect(so->so_m); + so->so_m = NULL; /* Don't m_free() it again! */ + } + /* No need for this socket anymore, udp_detach it */ + udp_detach(so); + } else { /* A "normal" UDP packet */ + struct mbuf *m; + int len; +#ifdef _WIN32 + unsigned long n; +#else + int n; +#endif + + if (ioctlsocket(so->s, FIONREAD, &n) != 0) { + DEBUG_MISC(" ioctlsocket errno = %d-%s\n", errno, strerror(errno)); + return; + } + + m = m_get(so->slirp); + if (!m) { + return; + } + switch (so->so_ffamily) { + case AF_INET: + m->m_data += IF_MAXLINKHDR + sizeof(struct udpiphdr); + break; + case AF_INET6: + m->m_data += + IF_MAXLINKHDR + sizeof(struct ip6) + sizeof(struct udphdr); + break; + default: + g_assert_not_reached(); + } + + /* + * XXX Shouldn't FIONREAD packets destined for port 53, + * but I don't know the max packet size for DNS lookups + */ + len = M_FREEROOM(m); + /* if (so->so_fport != htons(53)) { */ + + if (n > len) { + n = (m->m_data - m->m_dat) + m->m_len + n + 1; + m_inc(m, n); + len = M_FREEROOM(m); + } + /* } */ + + m->m_len = recvfrom(so->s, m->m_data, len, 0, (struct sockaddr *)&addr, + &addrlen); + DEBUG_MISC(" did recvfrom %d, errno = %d-%s", m->m_len, errno, + strerror(errno)); + if (m->m_len < 0) { + if (errno == ENOTCONN) { + /* + * UDP socket got burnt, e.g. by suspend on iOS. Tear it down + * and let it get re-created if the guest still needs it + */ + udp_detach(so); + } else { + /* Report error as ICMP */ + switch (so->so_lfamily) { + uint8_t code; + case AF_INET: + code = ICMP_UNREACH_PORT; + + if (errno == EHOSTUNREACH) { + code = ICMP_UNREACH_HOST; + } else if (errno == ENETUNREACH) { + code = ICMP_UNREACH_NET; + } + + DEBUG_MISC(" rx error, tx icmp ICMP_UNREACH:%i", code); + icmp_send_error(so->so_m, ICMP_UNREACH, code, 0, + strerror(errno)); + break; + case AF_INET6: + code = ICMP6_UNREACH_PORT; + + if (errno == EHOSTUNREACH) { + code = ICMP6_UNREACH_ADDRESS; + } else if (errno == ENETUNREACH) { + code = ICMP6_UNREACH_NO_ROUTE; + } + + DEBUG_MISC(" rx error, tx icmp6 ICMP_UNREACH:%i", code); + icmp6_send_error(so->so_m, ICMP6_UNREACH, code); + break; + default: + g_assert_not_reached(); + } + m_free(m); + } + } else { + /* + * Hack: domain name lookup will be used the most for UDP, + * and since they'll only be used once there's no need + * for the 4 minute (or whatever) timeout... So we time them + * out much quicker (10 seconds for now...) + */ + if (so->so_expire) { + if (so->so_fport == htons(53)) + so->so_expire = curtime + SO_EXPIREFAST; + else + so->so_expire = curtime + SO_EXPIRE; + } + + /* + * If this packet was destined for CTL_ADDR, + * make it look like that's where it came from + */ + saddr = addr; + sotranslate_in(so, &saddr); + + /* Perform lazy guest IP address resolution if needed. */ + if (so->so_state & SS_HOSTFWD) { + if (soassign_guest_addr_if_needed(so) < 0) { + DEBUG_MISC(" guest address not available yet"); + switch (so->so_lfamily) { + case AF_INET: + icmp_send_error(so->so_m, ICMP_UNREACH, + ICMP_UNREACH_HOST, 0, + "guest address not available yet"); + break; + case AF_INET6: + icmp6_send_error(so->so_m, ICMP6_UNREACH, + ICMP6_UNREACH_ADDRESS); + break; + default: + g_assert_not_reached(); + } + m_free(m); + return; + } + } + daddr = so->lhost.ss; + + switch (so->so_ffamily) { + case AF_INET: + udp_output(so, m, (struct sockaddr_in *)&saddr, + (struct sockaddr_in *)&daddr, so->so_iptos); + break; + case AF_INET6: + udp6_output(so, m, (struct sockaddr_in6 *)&saddr, + (struct sockaddr_in6 *)&daddr); + break; + default: + g_assert_not_reached(); + } + } /* rx error */ + } /* if ping packet */ +} + +/* + * sendto() a socket + */ +int sosendto(struct socket *so, struct mbuf *m) +{ + int ret; + struct sockaddr_storage addr; + + DEBUG_CALL("sosendto"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m = %p", m); + + addr = so->fhost.ss; + DEBUG_CALL(" sendto()ing)"); + if (sotranslate_out(so, &addr) < 0) { + return -1; + } + + /* Don't care what port we get */ + ret = sendto(so->s, m->m_data, m->m_len, 0, (struct sockaddr *)&addr, + sockaddr_size(&addr)); + if (ret < 0) + return -1; + + /* + * Kill the socket if there's no reply in 4 minutes, + * but only if it's an expirable socket + */ + if (so->so_expire) + so->so_expire = curtime + SO_EXPIRE; + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_ISFCONNECTED; /* So that it gets select()ed */ + return 0; +} + +struct socket *tcpx_listen(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *laddr, socklen_t laddrlen, + int flags) +{ + struct socket *so; + int s, opt = 1; + socklen_t addrlen; + + DEBUG_CALL("tcpx_listen"); + /* AF_INET6 addresses are bigger than AF_INET, so this is big enough. */ + char addrstr[INET6_ADDRSTRLEN]; + char portstr[6]; + int ret; + switch (haddr->sa_family) { + case AF_INET: + case AF_INET6: + ret = getnameinfo(haddr, haddrlen, addrstr, sizeof(addrstr), portstr, sizeof(portstr), NI_NUMERICHOST|NI_NUMERICSERV); + g_assert(ret == 0); + DEBUG_ARG("hfamily = INET"); + DEBUG_ARG("haddr = %s", addrstr); + DEBUG_ARG("hport = %s", portstr); + break; +#ifndef _WIN32 + case AF_UNIX: + DEBUG_ARG("hfamily = UNIX"); + DEBUG_ARG("hpath = %s", ((struct sockaddr_un *) haddr)->sun_path); + break; +#endif + default: + g_assert_not_reached(); + } + switch (laddr->sa_family) { + case AF_INET: + case AF_INET6: + ret = getnameinfo(laddr, laddrlen, addrstr, sizeof(addrstr), portstr, sizeof(portstr), NI_NUMERICHOST|NI_NUMERICSERV); + g_assert(ret == 0); + DEBUG_ARG("laddr = %s", addrstr); + DEBUG_ARG("lport = %s", portstr); + break; + default: + g_assert_not_reached(); + } + DEBUG_ARG("flags = %x", flags); + + /* + * SS_HOSTFWD sockets can be accepted multiple times, so they can't be + * SS_FACCEPTONCE. Also, SS_HOSTFWD connections can be accepted and + * immediately closed if the guest address isn't available yet, which is + * incompatible with the "accept once" concept. Correct code will never + * request both, so disallow their combination by assertion. + */ + g_assert(!((flags & SS_HOSTFWD) && (flags & SS_FACCEPTONCE))); + + so = socreate(slirp, IPPROTO_TCP); + + /* Don't tcp_attach... we don't need so_snd nor so_rcv */ + so->so_tcpcb = tcp_newtcpcb(so); + slirp_insque(so, &slirp->tcb); + + /* + * SS_FACCEPTONCE sockets must time out. + */ + if (flags & SS_FACCEPTONCE) + so->so_tcpcb->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT * 2; + + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= (SS_FACCEPTCONN | flags); + + sockaddr_copy(&so->lhost.sa, sizeof(so->lhost), laddr, laddrlen); + + s = slirp_socket(haddr->sa_family, SOCK_STREAM, 0); + if ((s < 0) || + (haddr->sa_family == AF_INET6 && slirp_socket_set_v6only(s, (flags & SS_HOSTFWD_V6ONLY) != 0) < 0) || + (slirp_socket_set_fast_reuse(s) < 0) || + (bind(s, haddr, haddrlen) < 0) || + (listen(s, 1) < 0)) { + int tmperrno = errno; /* Don't clobber the real reason we failed */ + if (s >= 0) { + closesocket(s); + } + sofree(so); + /* Restore the real errno */ +#ifdef _WIN32 + WSASetLastError(tmperrno); +#else + errno = tmperrno; +#endif + return NULL; + } + setsockopt(s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(int)); + slirp_socket_set_nodelay(s); + + addrlen = sizeof(so->fhost); + getsockname(s, &so->fhost.sa, &addrlen); + sotranslate_accept(so); + + so->s = s; + return so; +} + +struct socket *tcp_listen(Slirp *slirp, uint32_t haddr, unsigned hport, + uint32_t laddr, unsigned lport, int flags) +{ + struct sockaddr_in hsa, lsa; + + memset(&hsa, 0, sizeof(hsa)); + hsa.sin_family = AF_INET; + hsa.sin_addr.s_addr = haddr; + hsa.sin_port = hport; + + memset(&lsa, 0, sizeof(lsa)); + lsa.sin_family = AF_INET; + lsa.sin_addr.s_addr = laddr; + lsa.sin_port = lport; + + return tcpx_listen(slirp, (const struct sockaddr *) &hsa, sizeof(hsa), (struct sockaddr *) &lsa, sizeof(lsa), flags); +} + +/* + * Various session state calls + * XXX Should be #define's + * The socket state stuff needs work, these often get call 2 or 3 + * times each when only 1 was needed + */ +void soisfconnecting(struct socket *so) +{ + so->so_state &= ~(SS_NOFDREF | SS_ISFCONNECTED | SS_FCANTRCVMORE | + SS_FCANTSENDMORE | SS_FWDRAIN); + so->so_state |= SS_ISFCONNECTING; /* Clobber other states */ +} + +void soisfconnected(struct socket *so) +{ + so->so_state &= ~(SS_ISFCONNECTING | SS_FWDRAIN | SS_NOFDREF); + so->so_state |= SS_ISFCONNECTED; /* Clobber other states */ +} + +static void sofcantrcvmore(struct socket *so) +{ + if ((so->so_state & SS_NOFDREF) == 0) { + shutdown(so->s, 0); + } + so->so_state &= ~(SS_ISFCONNECTING); + if (so->so_state & SS_FCANTSENDMORE) { + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; /* Don't select it */ + } else { + so->so_state |= SS_FCANTRCVMORE; + } +} + +static void sofcantsendmore(struct socket *so) +{ + if ((so->so_state & SS_NOFDREF) == 0) { + shutdown(so->s, 1); /* send FIN to fhost */ + } + so->so_state &= ~(SS_ISFCONNECTING); + if (so->so_state & SS_FCANTRCVMORE) { + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; /* as above */ + } else { + so->so_state |= SS_FCANTSENDMORE; + } +} + +void sofwdrain(struct socket *so) +{ + if (so->so_rcv.sb_cc) + so->so_state |= SS_FWDRAIN; + else + sofcantsendmore(so); +} + +static bool sotranslate_out4(Slirp *s, struct socket *so, struct sockaddr_in *sin) +{ + if (!s->disable_dns && so->so_faddr.s_addr == s->vnameserver_addr.s_addr) { + return so->so_fport == htons(53) && get_dns_addr(&sin->sin_addr) >= 0; + } + + if (so->so_faddr.s_addr == s->vhost_addr.s_addr || + so->so_faddr.s_addr == 0xffffffff) { + if (s->disable_host_loopback) { + return false; + } + + sin->sin_addr = loopback_addr; + } + + return true; +} + +static bool sotranslate_out6(Slirp *s, struct socket *so, struct sockaddr_in6 *sin) +{ + if (!s->disable_dns && in6_equal(&so->so_faddr6, &s->vnameserver_addr6)) { + uint32_t scope_id; + if (so->so_fport == htons(53) && get_dns6_addr(&sin->sin6_addr, &scope_id) >= 0) { + sin->sin6_scope_id = scope_id; + return true; + } + return false; + } + + if (in6_equal_net(&so->so_faddr6, &s->vprefix_addr6, s->vprefix_len) || + in6_equal(&so->so_faddr6, &(struct in6_addr)ALLNODES_MULTICAST)) { + if (s->disable_host_loopback) { + return false; + } + + sin->sin6_addr = in6addr_loopback; + } + + return true; +} + + +int sotranslate_out(struct socket *so, struct sockaddr_storage *addr) +{ + bool ok = true; + + switch (addr->ss_family) { + case AF_INET: + ok = sotranslate_out4(so->slirp, so, (struct sockaddr_in *)addr); + break; + case AF_INET6: + ok = sotranslate_out6(so->slirp, so, (struct sockaddr_in6 *)addr); + break; + } + + if (!ok) { + errno = EPERM; + return -1; + } + + return 0; +} + +void sotranslate_in(struct socket *so, struct sockaddr_storage *addr) +{ + Slirp *slirp = so->slirp; + struct sockaddr_in *sin = (struct sockaddr_in *)addr; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr; + + switch (addr->ss_family) { + case AF_INET: + if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + uint32_t inv_mask = ~slirp->vnetwork_mask.s_addr; + + if ((so->so_faddr.s_addr & inv_mask) == inv_mask) { + sin->sin_addr = slirp->vhost_addr; + } else if (sin->sin_addr.s_addr == loopback_addr.s_addr || + so->so_faddr.s_addr != slirp->vhost_addr.s_addr) { + sin->sin_addr = so->so_faddr; + } + } + break; + + case AF_INET6: + if (in6_equal_net(&so->so_faddr6, &slirp->vprefix_addr6, + slirp->vprefix_len)) { + if (in6_equal(&sin6->sin6_addr, &in6addr_loopback) || + !in6_equal(&so->so_faddr6, &slirp->vhost_addr6)) { + sin6->sin6_addr = so->so_faddr6; + } + } + break; + + default: + break; + } +} + +void sotranslate_accept(struct socket *so) +{ + Slirp *slirp = so->slirp; + + switch (so->so_ffamily) { + case AF_INET: + if (so->so_faddr.s_addr == INADDR_ANY || + (so->so_faddr.s_addr & loopback_mask) == + (loopback_addr.s_addr & loopback_mask)) { + so->so_faddr = slirp->vhost_addr; + } + break; + + case AF_INET6: + if (in6_equal(&so->so_faddr6, &in6addr_any) || + in6_equal(&so->so_faddr6, &in6addr_loopback)) { + so->so_faddr6 = slirp->vhost_addr6; + } + break; + + case AF_UNIX: { + /* Translate Unix socket to random ephemeral source port. We obtain + * this source port by binding to port 0 so that the OS allocates a + * port for us. If this fails, we fall back to choosing a random port + * with a random number generator. */ + int s; + struct sockaddr_in in_addr; + struct sockaddr_in6 in6_addr; + socklen_t in_addr_len; + + if (so->slirp->in_enabled) { + so->so_ffamily = AF_INET; + so->so_faddr = slirp->vhost_addr; + so->so_fport = 0; + + switch (so->so_type) { + case IPPROTO_TCP: + s = slirp_socket(PF_INET, SOCK_STREAM, 0); + break; + case IPPROTO_UDP: + s = slirp_socket(PF_INET, SOCK_DGRAM, 0); + break; + default: + g_assert_not_reached(); + break; + } + if (s < 0) { + g_error("Ephemeral slirp_socket() allocation failed"); + goto unix2inet_cont; + } + memset(&in_addr, 0, sizeof(in_addr)); + in_addr.sin_family = AF_INET; + in_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + in_addr.sin_port = htons(0); + if (bind(s, (struct sockaddr *) &in_addr, sizeof(in_addr))) { + g_error("Ephemeral bind() failed"); + closesocket(s); + goto unix2inet_cont; + } + in_addr_len = sizeof(in_addr); + if (getsockname(s, (struct sockaddr *) &in_addr, &in_addr_len)) { + g_error("Ephemeral getsockname() failed"); + closesocket(s); + goto unix2inet_cont; + } + so->s_aux = s; + so->so_fport = in_addr.sin_port; + +unix2inet_cont: + if (!so->so_fport) { + g_warning("Falling back to random port allocation"); + so->so_fport = htons(g_rand_int_range(slirp->grand, 49152, 65536)); + } + } else if (so->slirp->in6_enabled) { + so->so_ffamily = AF_INET6; + so->so_faddr6 = slirp->vhost_addr6; + so->so_fport6 = 0; + + switch (so->so_type) { + case IPPROTO_TCP: + s = slirp_socket(PF_INET6, SOCK_STREAM, 0); + break; + case IPPROTO_UDP: + s = slirp_socket(PF_INET6, SOCK_DGRAM, 0); + break; + default: + g_assert_not_reached(); + break; + } + if (s < 0) { + g_error("Ephemeral slirp_socket() allocation failed"); + goto unix2inet6_cont; + } + memset(&in6_addr, 0, sizeof(in6_addr)); + in6_addr.sin6_family = AF_INET6; + in6_addr.sin6_addr = in6addr_loopback; + in6_addr.sin6_port = htons(0); + if (bind(s, (struct sockaddr *) &in6_addr, sizeof(in6_addr))) { + g_error("Ephemeral bind() failed"); + closesocket(s); + goto unix2inet6_cont; + } + in_addr_len = sizeof(in6_addr); + if (getsockname(s, (struct sockaddr *) &in6_addr, &in_addr_len)) { + g_error("Ephemeral getsockname() failed"); + closesocket(s); + goto unix2inet6_cont; + } + so->s_aux = s; + so->so_fport6 = in6_addr.sin6_port; + +unix2inet6_cont: + if (!so->so_fport6) { + g_warning("Falling back to random port allocation"); + so->so_fport6 = htons(g_rand_int_range(slirp->grand, 49152, 65536)); + } + } else { + g_assert_not_reached(); + } + break; + } /* case AF_UNIX */ + + default: + break; + } +} + +void sodrop(struct socket *s, int num) +{ + if (sbdrop(&s->so_snd, num)) { + s->slirp->cb->notify(s->slirp->opaque); + } +} + +/* + * Translate "addr-any" in so->lhost to the guest's actual address. + * Returns 0 for success, or -1 if the guest doesn't have an address yet + * with errno set to EHOSTUNREACH. + * + * The guest address is taken from the first entry in the ARP table for IPv4 + * and the first entry in the NDP table for IPv6. + * Note: The IPv4 path isn't exercised yet as all hostfwd "" guest translations + * are handled immediately by using slirp->vdhcp_startaddr. + */ +int soassign_guest_addr_if_needed(struct socket *so) +{ + Slirp *slirp = so->slirp; + /* AF_INET6 addresses are bigger than AF_INET, so this is big enough. */ + char addrstr[INET6_ADDRSTRLEN]; + char portstr[6]; + + g_assert(so->so_state & SS_HOSTFWD); + + switch (so->so_ffamily) { + case AF_INET: + if (so->so_laddr.s_addr == INADDR_ANY) { + g_assert_not_reached(); + } + break; + + case AF_INET6: + if (in6_zero(&so->so_laddr6)) { + int ret; + if (in6_zero(&slirp->ndp_table.guest_in6_addr)) { + errno = EHOSTUNREACH; + return -1; + } + so->so_laddr6 = slirp->ndp_table.guest_in6_addr; + ret = getnameinfo((const struct sockaddr *) &so->lhost.ss, + sizeof(so->lhost.ss), addrstr, sizeof(addrstr), + portstr, sizeof(portstr), + NI_NUMERICHOST|NI_NUMERICSERV); + g_assert(ret == 0); + DEBUG_MISC("%s: new ip = [%s]:%s", __func__, addrstr, portstr); + } + break; + + default: + break; + } + + return 0; +} diff --git a/app/src/main/cpp/libslirp/src/socket.h b/app/src/main/cpp/libslirp/src/socket.h new file mode 100644 index 00000000..27d3b8a6 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/socket.h @@ -0,0 +1,236 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#ifndef SLIRP_SOCKET_H +#define SLIRP_SOCKET_H + +#include + +#ifndef _WIN32 +#include +#endif + +#include "misc.h" +#include "sbuf.h" + +#define SO_EXPIRE 240000 +#define SO_EXPIREFAST 10000 + +/* Helps unify some in/in6 routines. */ +union in4or6_addr { + struct in_addr addr4; + struct in6_addr addr6; +}; +typedef union in4or6_addr in4or6_addr; + +/* + * Our socket structure + */ + +union slirp_sockaddr { + struct sockaddr sa; + struct sockaddr_storage ss; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +}; + +struct socket { + struct socket *so_next, *so_prev; /* For a linked list of sockets */ + + int s; /* The actual socket */ + int s_aux; /* An auxiliary socket for miscellaneous use. Currently used to + * reserve OS ports in UNIX-to-inet translation. */ + struct gfwd_list *guestfwd; + + int pollfds_idx; /* GPollFD GArray index */ + + Slirp *slirp; /* managing slirp instance */ + + /* XXX union these with not-yet-used sbuf params */ + struct mbuf *so_m; /* Pointer to the original SYN packet, + * for non-blocking connect()'s, and + * PING reply's */ + struct tcpiphdr *so_ti; /* Pointer to the original ti within + * so_mconn, for non-blocking connections */ + uint32_t so_urgc; + union slirp_sockaddr fhost; /* Foreign host */ +#define so_faddr fhost.sin.sin_addr +#define so_fport fhost.sin.sin_port +#define so_faddr6 fhost.sin6.sin6_addr +#define so_fport6 fhost.sin6.sin6_port +#define so_ffamily fhost.ss.ss_family + + union slirp_sockaddr lhost; /* Local host */ +#define so_laddr lhost.sin.sin_addr +#define so_lport lhost.sin.sin_port +#define so_laddr6 lhost.sin6.sin6_addr +#define so_lport6 lhost.sin6.sin6_port +#define so_lfamily lhost.ss.ss_family + + uint8_t so_iptos; /* Type of service */ + uint8_t so_emu; /* Is the socket emulated? */ + + uint8_t so_type; /* Protocol of the socket. May be 0 if loading old + * states. */ + int32_t so_state; /* internal state flags SS_*, below */ + + struct tcpcb *so_tcpcb; /* pointer to TCP protocol control block */ + unsigned so_expire; /* When the socket will expire */ + + int so_queued; /* Number of packets queued from this socket */ + int so_nqueued; /* Number of packets queued in a row + * Used to determine when to "downgrade" a session + * from fastq to batchq */ + + struct sbuf so_rcv; /* Receive buffer */ + struct sbuf so_snd; /* Send buffer */ +}; + + +/* + * Socket state bits. (peer means the host on the Internet, + * local host means the host on the other end of the modem) + */ +#define SS_NOFDREF 0x001 /* No fd reference */ + +#define SS_ISFCONNECTING \ + 0x002 /* Socket is connecting to peer (non-blocking connect()'s) */ +#define SS_ISFCONNECTED 0x004 /* Socket is connected to peer */ +#define SS_FCANTRCVMORE \ + 0x008 /* Socket can't receive more from peer (for half-closes) */ +#define SS_FCANTSENDMORE \ + 0x010 /* Socket can't send more to peer (for half-closes) */ +#define SS_FWDRAIN \ + 0x040 /* We received a FIN, drain data and set SS_FCANTSENDMORE */ + +#define SS_CTL 0x080 +#define SS_FACCEPTCONN \ + 0x100 /* Socket is accepting connections from a host on the internet */ +#define SS_FACCEPTONCE \ + 0x200 /* If set, the SS_FACCEPTCONN socket will die after one accept */ + +#define SS_PERSISTENT_MASK 0xf000 /* Unremovable state bits */ +#define SS_HOSTFWD 0x1000 /* Socket describes host->guest forwarding */ +#define SS_INCOMING \ + 0x2000 /* Connection was initiated by a host on the internet */ +#define SS_HOSTFWD_V6ONLY 0x4000 /* Only bind on v6 addresses */ + +/* Check that two addresses are equal */ +static inline int sockaddr_equal(const struct sockaddr_storage *a, + const struct sockaddr_storage *b) +{ + if (a->ss_family != b->ss_family) { + return 0; + } + + switch (a->ss_family) { + case AF_INET: { + const struct sockaddr_in *a4 = (const struct sockaddr_in *)a; + const struct sockaddr_in *b4 = (const struct sockaddr_in *)b; + return a4->sin_addr.s_addr == b4->sin_addr.s_addr && + a4->sin_port == b4->sin_port; + } + case AF_INET6: { + const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)a; + const struct sockaddr_in6 *b6 = (const struct sockaddr_in6 *)b; + return (in6_equal(&a6->sin6_addr, &b6->sin6_addr) && + a6->sin6_port == b6->sin6_port); + } +#ifndef _WIN32 + case AF_UNIX: { + const struct sockaddr_un *aun = (const struct sockaddr_un *)a; + const struct sockaddr_un *bun = (const struct sockaddr_un *)b; + return strncmp(aun->sun_path, bun->sun_path, sizeof(aun->sun_path)) == 0; + } +#endif + default: + g_assert_not_reached(); + } + + return 0; +} + +/* Get the size of an address */ +static inline socklen_t sockaddr_size(const struct sockaddr_storage *a) +{ + switch (a->ss_family) { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); +#ifndef _WIN32 + case AF_UNIX: + return sizeof(struct sockaddr_un); +#endif + default: + g_assert_not_reached(); + } +} + +/* Copy an address */ +static inline void sockaddr_copy(struct sockaddr *dst, socklen_t dstlen, const struct sockaddr *src, socklen_t srclen) +{ + socklen_t len = sockaddr_size((const struct sockaddr_storage *) src); + g_assert(len <= srclen); + g_assert(len <= dstlen); + memcpy(dst, src, len); +} + +/* Find the socket corresponding to lhost & fhost, trying last as a guess */ +struct socket *solookup(struct socket **last, struct socket *head, + struct sockaddr_storage *lhost, struct sockaddr_storage *fhost); +/* Create a new socket */ +struct socket *socreate(Slirp *, int); +/* Release a socket */ +void sofree(struct socket *); +/* Receive the available data from the Internet socket and queue it on the sb */ +int soread(struct socket *); +/* Receive the available OOB data from the Internet socket and try to send it immediately */ +int sorecvoob(struct socket *); +/* Send OOB data to the Internet socket */ +int sosendoob(struct socket *); +/* Send data to the Internet socket */ +int sowrite(struct socket *); +/* Receive the available data from the Internet UDP socket, and send it to the guest */ +void sorecvfrom(struct socket *); +/* Send data to the Internet UDP socket */ +int sosendto(struct socket *, struct mbuf *); +/* Listen for incoming TCPv4 connections on this haddr+hport */ +struct socket *tcp_listen(Slirp *, uint32_t haddr, unsigned hport, uint32_t laddr, unsigned lport, int flags); +/* + * Listen for incoming TCP connections on this haddr + * On failure errno contains the reason. + */ +struct socket *tcpx_listen(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *laddr, socklen_t laddrlen, + int flags); +/* Note that the socket is connecting */ +void soisfconnecting(register struct socket *); +/* Note that the socket is connected */ +void soisfconnected(register struct socket *); +/* + * Set write drain mode + * Set CANTSENDMORE once all data has been write()n + */ +void sofwdrain(struct socket *); +struct iovec; /* For win32 */ +/* Prepare iov for storing into the sb */ +size_t sopreprbuf(struct socket *so, struct iovec *iov, int *np); +/* Get data from the buffer and queue it on the sb */ +int soreadbuf(struct socket *so, const char *buf, int size); + +/* Translate addr into host addr when it is a virtual address, before sending to the Internet */ +int sotranslate_out(struct socket *, struct sockaddr_storage *); +/* Translate addr into virtual address when it is host, before sending to the guest */ +void sotranslate_in(struct socket *, struct sockaddr_storage *); +/* Translate connections from localhost to the real hostname */ +void sotranslate_accept(struct socket *); +/* Drop num bytes from the reading end of the socket */ +void sodrop(struct socket *, int num); +/* Forwarding a connection to the guest, try to find the guest address to use, fill lhost with it */ +int soassign_guest_addr_if_needed(struct socket *so); + +#endif /* SLIRP_SOCKET_H */ diff --git a/app/src/main/cpp/libslirp/src/state.c b/app/src/main/cpp/libslirp/src/state.c new file mode 100644 index 00000000..f10edf07 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/state.c @@ -0,0 +1,381 @@ +/* SPDX-License-Identifier: MIT */ +/* + * libslirp + * + * Copyright (c) 2004-2008 Fabrice Bellard + * + * 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 "slirp.h" +#include "vmstate.h" +#include "stream.h" + +static int slirp_tcp_post_load(void *opaque, int version) +{ + tcp_template((struct tcpcb *)opaque); + + return 0; +} + +static const VMStateDescription vmstate_slirp_tcp = { + .name = "slirp-tcp", + .version_id = 0, + .post_load = slirp_tcp_post_load, + .fields = (VMStateField[]){ VMSTATE_INT16(t_state, struct tcpcb), + VMSTATE_INT16_ARRAY(t_timer, struct tcpcb, + TCPT_NTIMERS), + VMSTATE_INT16(t_rxtshift, struct tcpcb), + VMSTATE_INT16(t_rxtcur, struct tcpcb), + VMSTATE_INT16(t_dupacks, struct tcpcb), + VMSTATE_UINT16(t_maxseg, struct tcpcb), + VMSTATE_UINT8(t_force, struct tcpcb), + VMSTATE_UINT16(t_flags, struct tcpcb), + VMSTATE_UINT32(snd_una, struct tcpcb), + VMSTATE_UINT32(snd_nxt, struct tcpcb), + VMSTATE_UINT32(snd_up, struct tcpcb), + VMSTATE_UINT32(snd_wl1, struct tcpcb), + VMSTATE_UINT32(snd_wl2, struct tcpcb), + VMSTATE_UINT32(iss, struct tcpcb), + VMSTATE_UINT32(snd_wnd, struct tcpcb), + VMSTATE_UINT32(rcv_wnd, struct tcpcb), + VMSTATE_UINT32(rcv_nxt, struct tcpcb), + VMSTATE_UINT32(rcv_up, struct tcpcb), + VMSTATE_UINT32(irs, struct tcpcb), + VMSTATE_UINT32(rcv_adv, struct tcpcb), + VMSTATE_UINT32(snd_max, struct tcpcb), + VMSTATE_UINT32(snd_cwnd, struct tcpcb), + VMSTATE_UINT32(snd_ssthresh, struct tcpcb), + VMSTATE_INT16(t_idle, struct tcpcb), + VMSTATE_INT16(t_rtt, struct tcpcb), + VMSTATE_UINT32(t_rtseq, struct tcpcb), + VMSTATE_INT16(t_srtt, struct tcpcb), + VMSTATE_INT16(t_rttvar, struct tcpcb), + VMSTATE_UINT16(t_rttmin, struct tcpcb), + VMSTATE_UINT32(max_sndwnd, struct tcpcb), + VMSTATE_UINT8(t_oobflags, struct tcpcb), + VMSTATE_UINT8(t_iobc, struct tcpcb), + VMSTATE_INT16(t_softerror, struct tcpcb), + VMSTATE_UINT8(snd_scale, struct tcpcb), + VMSTATE_UINT8(rcv_scale, struct tcpcb), + VMSTATE_UINT8(request_r_scale, struct tcpcb), + VMSTATE_UINT8(requested_s_scale, struct tcpcb), + VMSTATE_UINT32(ts_recent, struct tcpcb), + VMSTATE_UINT32(ts_recent_age, struct tcpcb), + VMSTATE_UINT32(last_ack_sent, struct tcpcb), + VMSTATE_END_OF_LIST() } +}; + +/* The sbuf has a pair of pointers that are migrated as offsets; + * we calculate the offsets and restore the pointers using + * pre_save/post_load on a tmp structure. + */ +struct sbuf_tmp { + struct sbuf *parent; + uint32_t roff, woff; +}; + +static int sbuf_tmp_pre_save(void *opaque) +{ + struct sbuf_tmp *tmp = opaque; + tmp->woff = tmp->parent->sb_wptr - tmp->parent->sb_data; + tmp->roff = tmp->parent->sb_rptr - tmp->parent->sb_data; + + return 0; +} + +static int sbuf_tmp_post_load(void *opaque, int version) +{ + struct sbuf_tmp *tmp = opaque; + uint32_t requested_len = tmp->parent->sb_datalen; + + /* Allocate the buffer space used by the field after the tmp */ + sbreserve(tmp->parent, tmp->parent->sb_datalen); + + if (tmp->woff >= requested_len || tmp->roff >= requested_len) { + g_critical("invalid sbuf offsets r/w=%u/%u len=%u", tmp->roff, + tmp->woff, requested_len); + return -EINVAL; + } + + tmp->parent->sb_wptr = tmp->parent->sb_data + tmp->woff; + tmp->parent->sb_rptr = tmp->parent->sb_data + tmp->roff; + + return 0; +} + + +static const VMStateDescription vmstate_slirp_sbuf_tmp = { + .name = "slirp-sbuf-tmp", + .post_load = sbuf_tmp_post_load, + .pre_save = sbuf_tmp_pre_save, + .version_id = 0, + .fields = (VMStateField[]){ VMSTATE_UINT32(woff, struct sbuf_tmp), + VMSTATE_UINT32(roff, struct sbuf_tmp), + VMSTATE_END_OF_LIST() } +}; + +static const VMStateDescription vmstate_slirp_sbuf = { + .name = "slirp-sbuf", + .version_id = 0, + .fields = (VMStateField[]){ VMSTATE_UINT32(sb_cc, struct sbuf), + VMSTATE_UINT32(sb_datalen, struct sbuf), + VMSTATE_WITH_TMP(struct sbuf, struct sbuf_tmp, + vmstate_slirp_sbuf_tmp), + VMSTATE_VBUFFER_UINT32(sb_data, struct sbuf, 0, + NULL, sb_datalen), + VMSTATE_END_OF_LIST() } +}; + +static bool slirp_older_than_v4(void *opaque, int version_id) +{ + return version_id < 4; +} + +static bool slirp_family_inet(void *opaque, int version_id) +{ + union slirp_sockaddr *ssa = (union slirp_sockaddr *)opaque; + return ssa->ss.ss_family == AF_INET; +} + +static int slirp_socket_pre_load(void *opaque) +{ + struct socket *so = opaque; + + tcp_attach(so); + /* Older versions don't load these fields */ + so->so_ffamily = AF_INET; + so->so_lfamily = AF_INET; + return 0; +} + +#ifndef _WIN32 +#define VMSTATE_SIN4_ADDR(f, s, t) VMSTATE_UINT32_TEST(f, s, t) +#else +/* Win uses u_long rather than uint32_t - but it's still 32bits long */ +#define VMSTATE_SIN4_ADDR(f, s, t) \ + VMSTATE_SINGLE_TEST(f, s, t, 0, slirp_vmstate_info_uint32, u_long) +#endif + +/* The OS provided ss_family field isn't that portable; it's size + * and type varies (16/8 bit, signed, unsigned) + * and the values it contains aren't fully portable. + */ +typedef struct SS_FamilyTmpStruct { + union slirp_sockaddr *parent; + uint16_t portable_family; +} SS_FamilyTmpStruct; + +#define SS_FAMILY_MIG_IPV4 2 /* Linux, BSD, Win... */ +#define SS_FAMILY_MIG_IPV6 10 /* Linux */ +#define SS_FAMILY_MIG_OTHER 0xffff + +static int ss_family_pre_save(void *opaque) +{ + SS_FamilyTmpStruct *tss = opaque; + + tss->portable_family = SS_FAMILY_MIG_OTHER; + + if (tss->parent->ss.ss_family == AF_INET) { + tss->portable_family = SS_FAMILY_MIG_IPV4; + } else if (tss->parent->ss.ss_family == AF_INET6) { + tss->portable_family = SS_FAMILY_MIG_IPV6; + } + + return 0; +} + +static int ss_family_post_load(void *opaque, int version_id) +{ + SS_FamilyTmpStruct *tss = opaque; + + switch (tss->portable_family) { + case SS_FAMILY_MIG_IPV4: + tss->parent->ss.ss_family = AF_INET; + break; + case SS_FAMILY_MIG_IPV6: + case 23: /* compatibility: AF_INET6 from mingw */ + case 28: /* compatibility: AF_INET6 from FreeBSD sys/socket.h */ + tss->parent->ss.ss_family = AF_INET6; + break; + default: + g_critical("invalid ss_family type %x", tss->portable_family); + return -EINVAL; + } + + return 0; +} + +static const VMStateDescription vmstate_slirp_ss_family = { + .name = "slirp-socket-addr/ss_family", + .pre_save = ss_family_pre_save, + .post_load = ss_family_post_load, + .fields = + (VMStateField[]){ VMSTATE_UINT16(portable_family, SS_FamilyTmpStruct), + VMSTATE_END_OF_LIST() } +}; + +static const VMStateDescription vmstate_slirp_socket_addr = { + .name = "slirp-socket-addr", + .version_id = 4, + .fields = + (VMStateField[]){ + VMSTATE_WITH_TMP(union slirp_sockaddr, SS_FamilyTmpStruct, + vmstate_slirp_ss_family), + VMSTATE_SIN4_ADDR(sin.sin_addr.s_addr, union slirp_sockaddr, + slirp_family_inet), + VMSTATE_UINT16_TEST(sin.sin_port, union slirp_sockaddr, + slirp_family_inet), + +#if 0 + /* Untested: Needs checking by someone with IPv6 test */ + VMSTATE_BUFFER_TEST(sin6.sin6_addr, union slirp_sockaddr, + slirp_family_inet6), + VMSTATE_UINT16_TEST(sin6.sin6_port, union slirp_sockaddr, + slirp_family_inet6), + VMSTATE_UINT32_TEST(sin6.sin6_flowinfo, union slirp_sockaddr, + slirp_family_inet6), + VMSTATE_UINT32_TEST(sin6.sin6_scope_id, union slirp_sockaddr, + slirp_family_inet6), +#endif + + VMSTATE_END_OF_LIST() } +}; + +static const VMStateDescription vmstate_slirp_socket = { + .name = "slirp-socket", + .version_id = 4, + .pre_load = slirp_socket_pre_load, + .fields = + (VMStateField[]){ + VMSTATE_UINT32(so_urgc, struct socket), + /* Pre-v4 versions */ + VMSTATE_SIN4_ADDR(so_faddr.s_addr, struct socket, + slirp_older_than_v4), + VMSTATE_SIN4_ADDR(so_laddr.s_addr, struct socket, + slirp_older_than_v4), + VMSTATE_UINT16_TEST(so_fport, struct socket, slirp_older_than_v4), + VMSTATE_UINT16_TEST(so_lport, struct socket, slirp_older_than_v4), + /* v4 and newer */ + VMSTATE_STRUCT(fhost, struct socket, 4, vmstate_slirp_socket_addr, + union slirp_sockaddr), + VMSTATE_STRUCT(lhost, struct socket, 4, vmstate_slirp_socket_addr, + union slirp_sockaddr), + + VMSTATE_UINT8(so_iptos, struct socket), + VMSTATE_UINT8(so_emu, struct socket), + VMSTATE_UINT8(so_type, struct socket), + VMSTATE_INT32(so_state, struct socket), + VMSTATE_STRUCT(so_rcv, struct socket, 0, vmstate_slirp_sbuf, + struct sbuf), + VMSTATE_STRUCT(so_snd, struct socket, 0, vmstate_slirp_sbuf, + struct sbuf), + VMSTATE_STRUCT_POINTER(so_tcpcb, struct socket, vmstate_slirp_tcp, + struct tcpcb), + VMSTATE_END_OF_LIST() } +}; + +static const VMStateDescription vmstate_slirp_bootp_client = { + .name = "slirp_bootpclient", + .fields = (VMStateField[]){ VMSTATE_UINT16(allocated, BOOTPClient), + VMSTATE_BUFFER(macaddr, BOOTPClient), + VMSTATE_END_OF_LIST() } +}; + +static const VMStateDescription vmstate_slirp = { + .name = "slirp", + .version_id = 4, + .fields = (VMStateField[]){ VMSTATE_UINT16_V(ip_id, Slirp, 2), + VMSTATE_STRUCT_ARRAY( + bootp_clients, Slirp, NB_BOOTP_CLIENTS, 3, + vmstate_slirp_bootp_client, BOOTPClient), + VMSTATE_END_OF_LIST() } +}; + +int slirp_state_save(Slirp *slirp, SlirpWriteCb write_cb, void *opaque) +{ + struct gfwd_list *ex_ptr; + SlirpOStream f = { + .write_cb = write_cb, + .opaque = opaque, + }; + + for (ex_ptr = slirp->guestfwd_list; ex_ptr; ex_ptr = ex_ptr->ex_next) + if (ex_ptr->write_cb) { + struct socket *so; + so = slirp_find_ctl_socket(slirp, ex_ptr->ex_addr, + ntohs(ex_ptr->ex_fport)); + if (!so) { + continue; + } + + slirp_ostream_write_u8(&f, 42); + slirp_vmstate_save_state(&f, &vmstate_slirp_socket, so); + } + slirp_ostream_write_u8(&f, 0); + + slirp_vmstate_save_state(&f, &vmstate_slirp, slirp); + + return 0; +} + + +int slirp_state_load(Slirp *slirp, int version_id, SlirpReadCb read_cb, + void *opaque) +{ + struct gfwd_list *ex_ptr; + SlirpIStream f = { + .read_cb = read_cb, + .opaque = opaque, + }; + + while (slirp_istream_read_u8(&f)) { + int ret; + struct socket *so = socreate(slirp, -1); + + ret = + slirp_vmstate_load_state(&f, &vmstate_slirp_socket, so, version_id); + if (ret < 0) { + return ret; + } + + if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) != + slirp->vnetwork_addr.s_addr) { + return -EINVAL; + } + for (ex_ptr = slirp->guestfwd_list; ex_ptr; ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->write_cb && + so->so_faddr.s_addr == ex_ptr->ex_addr.s_addr && + so->so_fport == ex_ptr->ex_fport) { + break; + } + } + if (!ex_ptr) { + return -EINVAL; + } + + so->guestfwd = ex_ptr; + } + + return slirp_vmstate_load_state(&f, &vmstate_slirp, slirp, version_id); +} + +int slirp_state_version(void) +{ + return 4; +} diff --git a/app/src/main/cpp/libslirp/src/stream.c b/app/src/main/cpp/libslirp/src/stream.c new file mode 100644 index 00000000..804f0235 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/stream.c @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: MIT */ +/* + * libslirp io streams + * + * Copyright (c) 2018 Red Hat, Inc. + * + * 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 "stream.h" +//#include + +bool slirp_istream_read(SlirpIStream *f, void *buf, size_t size) +{ + return f->read_cb(buf, size, f->opaque) == size; +} + +bool slirp_ostream_write(SlirpOStream *f, const void *buf, size_t size) +{ + return f->write_cb(buf, size, f->opaque) == size; +} + +uint8_t slirp_istream_read_u8(SlirpIStream *f) +{ + uint8_t b; + + if (slirp_istream_read(f, &b, sizeof(b))) { + return b; + } + + return 0; +} + +bool slirp_ostream_write_u8(SlirpOStream *f, uint8_t b) +{ + return slirp_ostream_write(f, &b, sizeof(b)); +} + +uint16_t slirp_istream_read_u16(SlirpIStream *f) +{ + uint16_t b; + + if (slirp_istream_read(f, &b, sizeof(b))) { + return GUINT16_FROM_BE(b); + } + + return 0; +} + +bool slirp_ostream_write_u16(SlirpOStream *f, uint16_t b) +{ + b = GUINT16_TO_BE(b); + return slirp_ostream_write(f, &b, sizeof(b)); +} + +uint32_t slirp_istream_read_u32(SlirpIStream *f) +{ + uint32_t b; + + if (slirp_istream_read(f, &b, sizeof(b))) { + return GUINT32_FROM_BE(b); + } + + return 0; +} + +bool slirp_ostream_write_u32(SlirpOStream *f, uint32_t b) +{ + b = GUINT32_TO_BE(b); + return slirp_ostream_write(f, &b, sizeof(b)); +} + +int16_t slirp_istream_read_i16(SlirpIStream *f) +{ + int16_t b; + + if (slirp_istream_read(f, &b, sizeof(b))) { + return GINT16_FROM_BE(b); + } + + return 0; +} + +bool slirp_ostream_write_i16(SlirpOStream *f, int16_t b) +{ + b = GINT16_TO_BE(b); + return slirp_ostream_write(f, &b, sizeof(b)); +} + +int32_t slirp_istream_read_i32(SlirpIStream *f) +{ + int32_t b; + + if (slirp_istream_read(f, &b, sizeof(b))) { + return GINT32_FROM_BE(b); + } + + return 0; +} + +bool slirp_ostream_write_i32(SlirpOStream *f, int32_t b) +{ + b = GINT32_TO_BE(b); + return slirp_ostream_write(f, &b, sizeof(b)); +} diff --git a/app/src/main/cpp/libslirp/src/stream.h b/app/src/main/cpp/libslirp/src/stream.h new file mode 100644 index 00000000..4cdc2369 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/stream.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef STREAM_H_ +#define STREAM_H_ + +#include "libslirp.h" + +typedef struct SlirpIStream { + SlirpReadCb read_cb; + void *opaque; +} SlirpIStream; + +typedef struct SlirpOStream { + SlirpWriteCb write_cb; + void *opaque; +} SlirpOStream; + +/* Read exactly size bytes from stream, return 1 if all ok, 0 otherwise */ +bool slirp_istream_read(SlirpIStream *f, void *buf, size_t size); +/* Write exactly size bytes to stream, return 1 if all ok, 0 otherwise */ +bool slirp_ostream_write(SlirpOStream *f, const void *buf, size_t size); + +/* Read exactly one byte from stream, return it, otherwise return 0 */ +uint8_t slirp_istream_read_u8(SlirpIStream *f); +/* Write exactly one byte to stream, return 1 if all ok, 0 otherwise */ +bool slirp_ostream_write_u8(SlirpOStream *f, uint8_t b); + +/* Read exactly two bytes from big-endian stream, return it, otherwise return 0 */ +uint16_t slirp_istream_read_u16(SlirpIStream *f); +/* Write exactly two bytes to big-endian stream, return 1 if all ok, 0 otherwise */ +bool slirp_ostream_write_u16(SlirpOStream *f, uint16_t b); + +/* Read exactly four bytes from big-endian stream, return it, otherwise return 0 */ +uint32_t slirp_istream_read_u32(SlirpIStream *f); +/* Write exactly four bytes to big-endian stream, return 1 if all ok, 0 otherwise */ +bool slirp_ostream_write_u32(SlirpOStream *f, uint32_t b); + +/* Read exactly two bytes from big-endian stream (signed), return it, otherwise return 0 */ +int16_t slirp_istream_read_i16(SlirpIStream *f); +/* Write exactly two bytes to big-endian stream (signed), return 1 if all ok, 0 otherwise */ +bool slirp_ostream_write_i16(SlirpOStream *f, int16_t b); + +/* Read exactly four bytes from big-endian stream (signed), return it, otherwise return 0 */ +int32_t slirp_istream_read_i32(SlirpIStream *f); +/* Write exactly four bytes to big-endian stream (signed), return 1 if all ok, 0 otherwise */ +bool slirp_ostream_write_i32(SlirpOStream *f, int32_t b); + +#endif /* STREAM_H_ */ diff --git a/app/src/main/cpp/libslirp/src/tcp.h b/app/src/main/cpp/libslirp/src/tcp.h new file mode 100644 index 00000000..f835e063 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/tcp.h @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp.h 8.1 (Berkeley) 6/10/93 + * tcp.h,v 1.3 1994/08/21 05:27:34 paul Exp + */ + +#ifndef TCP_H +#define TCP_H + +//#include + +typedef uint32_t tcp_seq; + +#define PR_SLOWHZ 2 /* 2 slow timeouts per second (approx) */ +#define PR_FASTHZ 5 /* 5 fast timeouts per second (not important) */ + +#define TCP_SNDSPACE 1024 * 128 +#define TCP_RCVSPACE 1024 * 128 +#define TCP_MAXSEG_MAX 32768 + +/* + * TCP header. + * Per RFC 793, September, 1981. + */ +#define tcphdr slirp_tcphdr +struct tcphdr { + uint16_t th_sport; /* source port */ + uint16_t th_dport; /* destination port */ + tcp_seq th_seq; /* sequence number */ + tcp_seq th_ack; /* acknowledgement number */ +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t th_off : 4, /* data offset */ + th_x2 : 4; /* (unused) */ +#else + uint8_t th_x2 : 4, /* (unused) */ + th_off : 4; /* data offset */ +#endif + uint8_t th_flags; + uint16_t th_win; /* window */ + uint16_t th_sum; /* checksum */ + uint16_t th_urp; /* urgent pointer */ +}; + +#include "tcp_var.h" + +#ifndef TH_FIN +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 +#endif + +#ifndef TCPOPT_EOL +#define TCPOPT_EOL 0 +#define TCPOPT_NOP 1 +#define TCPOPT_MAXSEG 2 +#define TCPOPT_WINDOW 3 +#define TCPOPT_SACK_PERMITTED 4 /* Experimental */ +#define TCPOPT_SACK 5 /* Experimental */ +#define TCPOPT_TIMESTAMP 8 + +#define TCPOPT_TSTAMP_HDR \ + (TCPOPT_NOP << 24 | TCPOPT_NOP << 16 | TCPOPT_TIMESTAMP << 8 | \ + TCPOLEN_TIMESTAMP) +#endif + +#ifndef TCPOLEN_MAXSEG +#define TCPOLEN_MAXSEG 4 +#define TCPOLEN_WINDOW 3 +#define TCPOLEN_SACK_PERMITTED 2 +#define TCPOLEN_TIMESTAMP 10 +#define TCPOLEN_TSTAMP_APPA (TCPOLEN_TIMESTAMP + 2) /* appendix A */ +#endif + +#undef TCP_MAXWIN +#define TCP_MAXWIN 65535 /* largest value for (unscaled) window */ + +#undef TCP_MAX_WINSHIFT +#define TCP_MAX_WINSHIFT 14 /* maximum window shift */ + +/* + * User-settable options (used with setsockopt). + * + * We don't use the system headers on unix because we have conflicting + * local structures. We can't avoid the system definitions on Windows, + * so we undefine them. + */ +#undef TCP_NODELAY +#define TCP_NODELAY 0x01 /* don't delay send to coalesce packets */ +#undef TCP_MAXSEG + +/* + * TCP FSM state definitions. + * Per RFC793, September, 1981. + */ + +#define TCP_NSTATES 11 + +#define TCPS_CLOSED 0 /* closed */ +#define TCPS_LISTEN 1 /* listening for connection */ +#define TCPS_SYN_SENT 2 /* active, have sent syn */ +#define TCPS_SYN_RECEIVED 3 /* have send and received syn */ +/* states < TCPS_ESTABLISHED are those where connections not established */ +#define TCPS_ESTABLISHED 4 /* established */ +#define TCPS_CLOSE_WAIT 5 /* rcvd fin, waiting for close */ +/* states > TCPS_CLOSE_WAIT are those where user has closed */ +#define TCPS_FIN_WAIT_1 6 /* have closed, sent fin */ +#define TCPS_CLOSING 7 /* closed xchd FIN; await FIN ACK */ +#define TCPS_LAST_ACK 8 /* had fin and close; await FIN ACK */ +/* states > TCPS_CLOSE_WAIT && < TCPS_FIN_WAIT_2 await ACK of FIN */ +#define TCPS_FIN_WAIT_2 9 /* have closed, fin is acked */ +#define TCPS_TIME_WAIT 10 /* in 2*msl quiet wait after close */ + +#define TCPS_HAVERCVDSYN(s) ((s) >= TCPS_SYN_RECEIVED) +#define TCPS_HAVEESTABLISHED(s) ((s) >= TCPS_ESTABLISHED) +#define TCPS_HAVERCVDFIN(s) ((s) >= TCPS_TIME_WAIT) + +/* + * TCP sequence numbers are 32 bit integers operated + * on with modular arithmetic. These macros can be + * used to compare such integers. + */ +#define SEQ_LT(a, b) ((int)((a) - (b)) < 0) +#define SEQ_LEQ(a, b) ((int)((a) - (b)) <= 0) +#define SEQ_GT(a, b) ((int)((a) - (b)) > 0) +#define SEQ_GEQ(a, b) ((int)((a) - (b)) >= 0) + +/* + * Macros to initialize tcp sequence numbers for + * send and receive from initial send and receive + * sequence numbers. + */ +#define tcp_rcvseqinit(tp) (tp)->rcv_adv = (tp)->rcv_nxt = (tp)->irs + 1 + +#define tcp_sendseqinit(tp) \ + (tp)->snd_una = (tp)->snd_nxt = (tp)->snd_max = (tp)->snd_up = (tp)->iss + +#define TCP_ISSINCR (125 * 1024) /* increment for tcp_iss each second */ + +#endif diff --git a/app/src/main/cpp/libslirp/src/tcp_input.c b/app/src/main/cpp/libslirp/src/tcp_input.c new file mode 100644 index 00000000..728c26dd --- /dev/null +++ b/app/src/main/cpp/libslirp/src/tcp_input.c @@ -0,0 +1,1566 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_input.c 8.5 (Berkeley) 4/10/94 + * tcp_input.c,v 1.10 1994/10/13 18:36:32 wollman Exp + */ + +/* + * Changes and additions relating to SLiRP + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" +#include "ip_icmp.h" + +#define TCPREXMTTHRESH 3 + +#define TCP_PAWS_IDLE (24 * 24 * 60 * 60 * PR_SLOWHZ) + +/* for modulo comparisons of timestamps */ +#define TSTMP_LT(a, b) ((int)((a) - (b)) < 0) +#define TSTMP_GEQ(a, b) ((int)((a) - (b)) >= 0) + +/* + * Insert segment ti into reassembly queue of tcp with + * control block tp. Return TH_FIN if reassembly now includes + * a segment with FIN. + * Set DELACK for segments received in order, but ack immediately + * when segments are out of order (so fast retransmit can work). + */ + +static void tcp_dooptions(struct tcpcb *tp, uint8_t *cp, int cnt, + struct tcpiphdr *ti); +static void tcp_xmit_timer(register struct tcpcb *tp, int rtt); + +static int tcp_reass(register struct tcpcb *tp, register struct tcpiphdr *ti, + struct mbuf *m) +{ + register struct tcpiphdr *q; + struct socket *so = tp->t_socket; + int flags; + + /* + * Call with ti==NULL after become established to + * force pre-ESTABLISHED data up to user socket. + */ + if (ti == NULL) + goto present; + + /* + * Find a segment which begins after this one does. + */ + for (q = tcpfrag_list_first(tp); !tcpfrag_list_end(q, tp); + q = tcpiphdr_next(q)) + if (SEQ_GT(q->ti_seq, ti->ti_seq)) + break; + + /* + * If there is a preceding segment, it may provide some of + * our data already. If so, drop the data from the incoming + * segment. If it provides all of our data, drop us. + */ + if (!tcpfrag_list_end(tcpiphdr_prev(q), tp)) { + register int i; + q = tcpiphdr_prev(q); + /* conversion to int (in i) handles seq wraparound */ + i = q->ti_seq + q->ti_len - ti->ti_seq; + if (i > 0) { + if (i >= ti->ti_len) { + m_free(m); + /* + * Try to present any queued data + * at the left window edge to the user. + * This is needed after the 3-WHS + * completes. + */ + goto present; /* ??? */ + } + m_adj(m, i); + ti->ti_len -= i; + ti->ti_seq += i; + } + q = tcpiphdr_next(q); + } + ti->ti_mbuf = m; + + /* + * While we overlap succeeding segments trim them or, + * if they are completely covered, dequeue them. + */ + while (!tcpfrag_list_end(q, tp)) { + register int i = (ti->ti_seq + ti->ti_len) - q->ti_seq; + if (i <= 0) + break; + if (i < q->ti_len) { + q->ti_seq += i; + q->ti_len -= i; + m_adj(q->ti_mbuf, i); + break; + } + q = tcpiphdr_next(q); + m = tcpiphdr_prev(q)->ti_mbuf; + slirp_remque(tcpiphdr2qlink(tcpiphdr_prev(q))); + m_free(m); + } + + /* + * Stick new segment in its place. + */ + slirp_insque(tcpiphdr2qlink(ti), tcpiphdr2qlink(tcpiphdr_prev(q))); + +present: + /* + * Present data to user, advancing rcv_nxt through + * completed sequence space. + */ + if (!TCPS_HAVEESTABLISHED(tp->t_state)) + return (0); + ti = tcpfrag_list_first(tp); + if (tcpfrag_list_end(ti, tp) || ti->ti_seq != tp->rcv_nxt) + return (0); + if (tp->t_state == TCPS_SYN_RECEIVED && ti->ti_len) + return (0); + do { + tp->rcv_nxt += ti->ti_len; + flags = ti->ti_flags & TH_FIN; + slirp_remque(tcpiphdr2qlink(ti)); + m = ti->ti_mbuf; + ti = tcpiphdr_next(ti); + if (so->so_state & SS_FCANTSENDMORE) + m_free(m); + else { + if (so->so_emu) { + if (tcp_emu(so, m)) + sbappend(so, m); + } else + sbappend(so, m); + } + } while (!tcpfrag_list_end(ti, tp) && ti->ti_seq == tp->rcv_nxt); + return (flags); +} + +/* + * TCP input routine, follows pages 65-76 of the + * protocol specification dated September, 1981 very closely. + */ +void tcp_input(struct mbuf *m, int iphlen, struct socket *inso, + unsigned short af) +{ + struct ip save_ip, *ip; + struct ip6 save_ip6, *ip6; + register struct tcpiphdr *ti; + char *optp = NULL; + int optlen = 0; + int len, tlen, off; + register struct tcpcb *tp = NULL; + register int tiflags; + struct socket *so = NULL; + int todrop, acked, ourfinisacked, needoutput = 0; + int iss = 0; + uint32_t tiwin; + int ret; + struct sockaddr_storage lhost, fhost; + struct sockaddr_in *lhost4, *fhost4; + struct sockaddr_in6 *lhost6, *fhost6; + struct gfwd_list *ex_ptr; + Slirp *slirp; + + DEBUG_CALL("tcp_input"); + DEBUG_ARG("m = %p iphlen = %2d inso = %p", m, iphlen, inso); + + memset(&lhost, 0, sizeof(struct sockaddr_storage)); + memset(&fhost, 0, sizeof(struct sockaddr_storage)); + + /* + * If called with m == 0, then we're continuing the connect + */ + if (m == NULL) { + so = inso; + slirp = so->slirp; + + /* Re-set a few variables */ + tp = sototcpcb(so); + m = so->so_m; + so->so_m = NULL; + ti = so->so_ti; + tiwin = ti->ti_win; + tiflags = ti->ti_flags; + + goto cont_conn; + } + slirp = m->slirp; + switch (af) { + case AF_INET: + M_DUP_DEBUG(slirp, m, 0, + sizeof(struct qlink) + sizeof(struct tcpiphdr) - sizeof(struct ip) - sizeof(struct tcphdr)); + break; + case AF_INET6: + M_DUP_DEBUG(slirp, m, 0, + sizeof(struct qlink) + sizeof(struct tcpiphdr) - sizeof(struct ip6) - sizeof(struct tcphdr)); + break; + } + + ip = mtod(m, struct ip *); + ip6 = mtod(m, struct ip6 *); + + switch (af) { + case AF_INET: + if (iphlen > sizeof(struct ip)) { + ip_stripoptions(m); + iphlen = sizeof(struct ip); + } + /* XXX Check if too short */ + + + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + save_ip = *ip; + save_ip.ip_len += iphlen; + + /* + * Get IP and TCP header together in first mbuf. + * Note: IP leaves IP header in first mbuf. + */ + m->m_data -= + sizeof(struct tcpiphdr) - sizeof(struct ip) - sizeof(struct tcphdr); + m->m_len += + sizeof(struct tcpiphdr) - sizeof(struct ip) - sizeof(struct tcphdr); + ti = mtod(m, struct tcpiphdr *); + + /* + * Checksum extended TCP header and data. + */ + tlen = ip->ip_len; + tcpiphdr2qlink(ti)->next = tcpiphdr2qlink(ti)->prev = NULL; + memset(&ti->ih_mbuf, 0, sizeof(struct mbuf_ptr)); + memset(&ti->ti, 0, sizeof(ti->ti)); + ti->ti_x0 = 0; + ti->ti_src = save_ip.ip_src; + ti->ti_dst = save_ip.ip_dst; + ti->ti_pr = save_ip.ip_p; + ti->ti_len = htons((uint16_t)tlen); + break; + + case AF_INET6: + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + save_ip6 = *ip6; + /* + * Get IP and TCP header together in first mbuf. + * Note: IP leaves IP header in first mbuf. + */ + m->m_data -= sizeof(struct tcpiphdr) - + (sizeof(struct ip6) + sizeof(struct tcphdr)); + m->m_len += sizeof(struct tcpiphdr) - + (sizeof(struct ip6) + sizeof(struct tcphdr)); + ti = mtod(m, struct tcpiphdr *); + + tlen = ip6->ip_pl; + tcpiphdr2qlink(ti)->next = tcpiphdr2qlink(ti)->prev = NULL; + memset(&ti->ih_mbuf, 0, sizeof(struct mbuf_ptr)); + memset(&ti->ti, 0, sizeof(ti->ti)); + ti->ti_x0 = 0; + ti->ti_src6 = save_ip6.ip_src; + ti->ti_dst6 = save_ip6.ip_dst; + ti->ti_nh6 = save_ip6.ip_nh; + ti->ti_len = htons((uint16_t)tlen); + break; + + default: + g_assert_not_reached(); + } + + len = ((sizeof(struct tcpiphdr) - sizeof(struct tcphdr)) + tlen); + if (cksum(m, len)) { + goto drop; + } + + /* + * Check that TCP offset makes sense, + * pull out TCP options and adjust length. XXX + */ + off = ti->ti_off << 2; + if (off < sizeof(struct tcphdr) || off > tlen) { + goto drop; + } + tlen -= off; + ti->ti_len = tlen; + if (off > sizeof(struct tcphdr)) { + optlen = off - sizeof(struct tcphdr); + optp = mtod(m, char *) + sizeof(struct tcpiphdr); + } + tiflags = ti->ti_flags; + + /* + * Convert TCP protocol specific fields to host format. + */ + NTOHL(ti->ti_seq); + NTOHL(ti->ti_ack); + NTOHS(ti->ti_win); + NTOHS(ti->ti_urp); + + /* + * Drop TCP, IP headers and TCP options. + */ + m->m_data += sizeof(struct tcpiphdr) + off - sizeof(struct tcphdr); + m->m_len -= sizeof(struct tcpiphdr) + off - sizeof(struct tcphdr); + + /* + * Locate pcb for segment. + */ +findso: + lhost.ss_family = af; + fhost.ss_family = af; + switch (af) { + case AF_INET: + lhost4 = (struct sockaddr_in *)&lhost; + lhost4->sin_addr = ti->ti_src; + lhost4->sin_port = ti->ti_sport; + fhost4 = (struct sockaddr_in *)&fhost; + fhost4->sin_addr = ti->ti_dst; + fhost4->sin_port = ti->ti_dport; + break; + case AF_INET6: + lhost6 = (struct sockaddr_in6 *)&lhost; + lhost6->sin6_addr = ti->ti_src6; + lhost6->sin6_port = ti->ti_sport; + fhost6 = (struct sockaddr_in6 *)&fhost; + fhost6->sin6_addr = ti->ti_dst6; + fhost6->sin6_port = ti->ti_dport; + break; + default: + g_assert_not_reached(); + } + + so = solookup(&slirp->tcp_last_so, &slirp->tcb, &lhost, &fhost); + + /* + * If the state is CLOSED (i.e., TCB does not exist) then + * all data in the incoming segment is discarded. + * If the TCB exists but is in CLOSED state, it is embryonic, + * but should either do a listen or a connect soon. + * + * state == CLOSED means we've done socreate() but haven't + * attached it to a protocol yet... + * + * XXX If a TCB does not exist, and the TH_SYN flag is + * the only flag set, then create a session, mark it + * as if it was LISTENING, and continue... + */ + if (so == NULL) { + /* TODO: IPv6 */ + if (slirp->restricted) { + /* Any hostfwds will have an existing socket, so we only get here + * for non-hostfwd connections. These should be dropped, unless it + * happens to be a guestfwd. + */ + for (ex_ptr = slirp->guestfwd_list; ex_ptr; + ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->ex_fport == ti->ti_dport && + ti->ti_dst.s_addr == ex_ptr->ex_addr.s_addr) { + break; + } + } + if (!ex_ptr) { + goto dropwithreset; + } + } + + if ((tiflags & (TH_SYN | TH_FIN | TH_RST | TH_URG | TH_ACK)) != TH_SYN) + goto dropwithreset; + + so = socreate(slirp, IPPROTO_TCP); + tcp_attach(so); + + sbreserve(&so->so_snd, TCP_SNDSPACE); + sbreserve(&so->so_rcv, TCP_RCVSPACE); + + so->lhost.ss = lhost; + so->fhost.ss = fhost; + + so->so_iptos = tcp_tos(so); + if (so->so_iptos == 0) { + switch (af) { + case AF_INET: + so->so_iptos = ((struct ip *)ti)->ip_tos; + break; + case AF_INET6: + break; + default: + g_assert_not_reached(); + } + } + + tp = sototcpcb(so); + tp->t_state = TCPS_LISTEN; + } + + /* + * If this is a still-connecting socket, this probably + * a retransmit of the SYN. Whether it's a retransmit SYN + * or something else, we nuke it. + */ + if (so->so_state & SS_ISFCONNECTING) + goto drop; + + tp = sototcpcb(so); + + /* XXX Should never fail */ + if (tp == NULL) + goto dropwithreset; + if (tp->t_state == TCPS_CLOSED) + goto drop; + + tiwin = ti->ti_win; + + /* + * Segment received on connection. + * Reset idle time and keep-alive timer. + */ + tp->t_idle = 0; + if (slirp_do_keepalive) + tp->t_timer[TCPT_KEEP] = TCPTV_KEEPINTVL; + else + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_IDLE; + + /* + * Process options if not in LISTEN state, + * else do it below (after getting remote address). + */ + if (optp && tp->t_state != TCPS_LISTEN) + tcp_dooptions(tp, (uint8_t *)optp, optlen, ti); + + /* + * Header prediction: check for the two common cases + * of a uni-directional data xfer. If the packet has + * no control flags, is in-sequence, the window didn't + * change and we're not retransmitting, it's a + * candidate. If the length is zero and the ack moved + * forward, we're the sender side of the xfer. Just + * free the data acked & wake any higher level process + * that was blocked waiting for space. If the length + * is non-zero and the ack didn't move, we're the + * receiver side. If we're getting packets in-order + * (the reassembly queue is empty), add the data to + * the socket buffer and note that we need a delayed ack. + * + * XXX Some of these tests are not needed + * eg: the tiwin == tp->snd_wnd prevents many more + * predictions.. with no *real* advantage.. + */ + if (tp->t_state == TCPS_ESTABLISHED && + (tiflags & (TH_SYN | TH_FIN | TH_RST | TH_URG | TH_ACK)) == TH_ACK && + ti->ti_seq == tp->rcv_nxt && tiwin && tiwin == tp->snd_wnd && + tp->snd_nxt == tp->snd_max) { + if (ti->ti_len == 0) { + if (SEQ_GT(ti->ti_ack, tp->snd_una) && + SEQ_LEQ(ti->ti_ack, tp->snd_max) && + tp->snd_cwnd >= tp->snd_wnd) { + /* + * this is a pure ack for outstanding data. + */ + if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) + tcp_xmit_timer(tp, tp->t_rtt); + acked = ti->ti_ack - tp->snd_una; + sodrop(so, acked); + tp->snd_una = ti->ti_ack; + m_free(m); + + /* + * If all outstanding data are acked, stop + * retransmit timer, otherwise restart timer + * using current (possibly backed-off) value. + * If process is waiting for space, + * wakeup/selwakeup/signal. If data + * are ready to send, let tcp_output + * decide between more output or persist. + */ + if (tp->snd_una == tp->snd_max) + tp->t_timer[TCPT_REXMT] = 0; + else if (tp->t_timer[TCPT_PERSIST] == 0) + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + + /* + * This is called because sowwakeup might have + * put data into so_snd. Since we don't so sowwakeup, + * we don't need this.. XXX??? + */ + if (so->so_snd.sb_cc) + tcp_output(tp); + + return; + } + } else if (ti->ti_ack == tp->snd_una && tcpfrag_list_empty(tp) && + ti->ti_len <= sbspace(&so->so_rcv)) { + /* + * this is a pure, in-sequence data packet + * with nothing on the reassembly queue and + * we have enough buffer space to take it. + */ + tp->rcv_nxt += ti->ti_len; + /* + * Add data to socket buffer. + */ + if (so->so_emu) { + if (tcp_emu(so, m)) + sbappend(so, m); + } else + sbappend(so, m); + + /* + * If this is a short packet, then ACK now - with Nagel + * congestion avoidance sender won't send more until + * he gets an ACK. + * + * It is better to not delay acks at all to maximize + * TCP throughput. See RFC 2581. + */ + tp->t_flags |= TF_ACKNOW; + tcp_output(tp); + return; + } + } /* header prediction */ + /* + * Calculate amount of space in receive window, + * and then do TCP input processing. + * Receive window is amount of space in rcv queue, + * but not less than advertised window. + */ + { + int win; + win = sbspace(&so->so_rcv); + if (win < 0) + win = 0; + tp->rcv_wnd = MAX(win, (int)(tp->rcv_adv - tp->rcv_nxt)); + } + + switch (tp->t_state) { + /* + * If the state is LISTEN then ignore segment if it contains an RST. + * If the segment contains an ACK then it is bad and send a RST. + * If it does not contain a SYN then it is not interesting; drop it. + * Don't bother responding if the destination was a broadcast. + * Otherwise initialize tp->rcv_nxt, and tp->irs, select an initial + * tp->iss, and send a segment: + * + * Also initialize tp->snd_nxt to tp->iss+1 and tp->snd_una to tp->iss. + * Fill in remote peer address fields if not previously specified. + * Enter SYN_RECEIVED state, and process any other fields of this + * segment in this state. + */ + case TCPS_LISTEN: { + if (tiflags & TH_RST) + goto drop; + if (tiflags & TH_ACK) + goto dropwithreset; + if ((tiflags & TH_SYN) == 0) + goto drop; + + /* + * This has way too many gotos... + * But a bit of spaghetti code never hurt anybody :) + */ + + /* + * If this is destined for the control address, then flag to + * tcp_ctl once connected, otherwise connect + */ + /* TODO: IPv6 */ + if (af == AF_INET && + (so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + if (so->so_faddr.s_addr != slirp->vhost_addr.s_addr && + so->so_faddr.s_addr != slirp->vnameserver_addr.s_addr) { + /* May be an add exec */ + for (ex_ptr = slirp->guestfwd_list; ex_ptr; + ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->ex_fport == so->so_fport && + so->so_faddr.s_addr == ex_ptr->ex_addr.s_addr) { + so->so_state |= SS_CTL; + break; + } + } + if (so->so_state & SS_CTL) { + goto cont_input; + } + } + /* CTL_ALIAS: Do nothing, tcp_fconnect will be called on it */ + } + + if (so->so_emu & EMU_NOCONNECT) { + so->so_emu &= ~EMU_NOCONNECT; + goto cont_input; + } + + if ((tcp_fconnect(so, so->so_ffamily) == -1) && (errno != EAGAIN) && + (errno != EINPROGRESS) && (errno != EWOULDBLOCK)) { + uint8_t code; + DEBUG_MISC(" tcp fconnect errno = %d-%s", errno, strerror(errno)); + if (errno == ECONNREFUSED) { + /* ACK the SYN, send RST to refuse the connection */ + tcp_respond(tp, ti, m, ti->ti_seq + 1, (tcp_seq)0, + TH_RST | TH_ACK, af); + } else { + switch (af) { + case AF_INET: + code = ICMP_UNREACH_NET; + if (errno == EHOSTUNREACH) { + code = ICMP_UNREACH_HOST; + } + break; + case AF_INET6: + code = ICMP6_UNREACH_NO_ROUTE; + if (errno == EHOSTUNREACH) { + code = ICMP6_UNREACH_ADDRESS; + } + break; + default: + g_assert_not_reached(); + } + HTONL(ti->ti_seq); /* restore tcp header */ + HTONL(ti->ti_ack); + HTONS(ti->ti_win); + HTONS(ti->ti_urp); + m->m_data -= + sizeof(struct tcpiphdr) + off - sizeof(struct tcphdr); + m->m_len += + sizeof(struct tcpiphdr) + off - sizeof(struct tcphdr); + switch (af) { + case AF_INET: + m->m_data += sizeof(struct tcpiphdr) - sizeof(struct ip) - + sizeof(struct tcphdr); + m->m_len -= sizeof(struct tcpiphdr) - sizeof(struct ip) - + sizeof(struct tcphdr); + *ip = save_ip; + icmp_send_error(m, ICMP_UNREACH, code, 0, strerror(errno)); + break; + case AF_INET6: + m->m_data += sizeof(struct tcpiphdr) - + (sizeof(struct ip6) + sizeof(struct tcphdr)); + m->m_len -= sizeof(struct tcpiphdr) - + (sizeof(struct ip6) + sizeof(struct tcphdr)); + *ip6 = save_ip6; + icmp6_send_error(m, ICMP6_UNREACH, code); + break; + default: + g_assert_not_reached(); + } + } + tcp_close(tp); + m_free(m); + } else { + /* + * Haven't connected yet, save the current mbuf + * and ti, and return + * XXX Some OS's don't tell us whether the connect() + * succeeded or not. So we must time it out. + */ + so->so_m = m; + so->so_ti = ti; + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT; + tp->t_state = TCPS_SYN_RECEIVED; + /* + * Initialize receive sequence numbers now so that we can send a + * valid RST if the remote end rejects our connection. + */ + tp->irs = ti->ti_seq; + tcp_rcvseqinit(tp); + tcp_template(tp); + } + return; + + cont_conn: + /* m==NULL + * Check if the connect succeeded + */ + if (so->so_state & SS_NOFDREF) { + tp = tcp_close(tp); + goto dropwithreset; + } + cont_input: + tcp_template(tp); + + if (optp) + tcp_dooptions(tp, (uint8_t *)optp, optlen, ti); + + if (iss) + tp->iss = iss; + else + tp->iss = slirp->tcp_iss; + slirp->tcp_iss += TCP_ISSINCR / 2; + tp->irs = ti->ti_seq; + tcp_sendseqinit(tp); + tcp_rcvseqinit(tp); + tp->t_flags |= TF_ACKNOW; + tp->t_state = TCPS_SYN_RECEIVED; + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT; + goto trimthenstep6; + } /* case TCPS_LISTEN */ + + /* + * If the state is SYN_SENT: + * if seg contains an ACK, but not for our SYN, drop the input. + * if seg contains a RST, then drop the connection. + * if seg does not contain SYN, then drop it. + * Otherwise this is an acceptable SYN segment + * initialize tp->rcv_nxt and tp->irs + * if seg contains ack then advance tp->snd_una + * if SYN has been acked change to ESTABLISHED else SYN_RCVD state + * arrange for segment to be acked (eventually) + * continue processing rest of data/controls, beginning with URG + */ + case TCPS_SYN_SENT: + if (getenv("SLIRP_FUZZING") && + /* Align seq numbers on what the fuzzing trace says */ + tp->iss == 1 && ti->ti_ack != 0) { + tp->iss = ti->ti_ack - 1; + tp->snd_max = tp->iss + 1; + } + + if ((tiflags & TH_ACK) && + (SEQ_LEQ(ti->ti_ack, tp->iss) || SEQ_GT(ti->ti_ack, tp->snd_max))) + goto dropwithreset; + + if (tiflags & TH_RST) { + if (tiflags & TH_ACK) { + tcp_drop(tp, 0); /* XXX Check t_softerror! */ + } + goto drop; + } + + if ((tiflags & TH_SYN) == 0) + goto drop; + if (tiflags & TH_ACK) { + tp->snd_una = ti->ti_ack; + if (SEQ_LT(tp->snd_nxt, tp->snd_una)) + tp->snd_nxt = tp->snd_una; + } + + tp->t_timer[TCPT_REXMT] = 0; + tp->irs = ti->ti_seq; + tcp_rcvseqinit(tp); + tp->t_flags |= TF_ACKNOW; + if (tiflags & TH_ACK && SEQ_GT(tp->snd_una, tp->iss)) { + soisfconnected(so); + tp->t_state = TCPS_ESTABLISHED; + + tcp_reass(tp, (struct tcpiphdr *)0, (struct mbuf *)0); + /* + * if we didn't have to retransmit the SYN, + * use its rtt as our initial srtt & rtt var. + */ + if (tp->t_rtt) + tcp_xmit_timer(tp, tp->t_rtt); + } else + tp->t_state = TCPS_SYN_RECEIVED; + + trimthenstep6: + /* + * Advance ti->ti_seq to correspond to first data byte. + * If data, trim to stay within window, + * dropping FIN if necessary. + */ + ti->ti_seq++; + if (ti->ti_len > tp->rcv_wnd) { + todrop = ti->ti_len - tp->rcv_wnd; + m_adj(m, -todrop); + ti->ti_len = tp->rcv_wnd; + tiflags &= ~TH_FIN; + } + tp->snd_wl1 = ti->ti_seq - 1; + tp->rcv_up = ti->ti_seq; + goto step6; + } /* switch tp->t_state */ + /* + * States other than LISTEN or SYN_SENT. + * Check that at least some bytes of segment are within + * receive window. If segment begins before rcv_nxt, + * drop leading data (and SYN); if nothing left, just ack. + */ + todrop = tp->rcv_nxt - ti->ti_seq; + if (todrop > 0) { + if (tiflags & TH_SYN) { + tiflags &= ~TH_SYN; + ti->ti_seq++; + if (ti->ti_urp > 1) + ti->ti_urp--; + else + tiflags &= ~TH_URG; + todrop--; + } + /* + * Following if statement from Stevens, vol. 2, p. 960. + */ + if (todrop > ti->ti_len || + (todrop == ti->ti_len && (tiflags & TH_FIN) == 0)) { + /* + * Any valid FIN must be to the left of the window. + * At this point the FIN must be a duplicate or out + * of sequence; drop it. + */ + tiflags &= ~TH_FIN; + + /* + * Send an ACK to resynchronize and drop any data. + * But keep on processing for RST or ACK. + */ + tp->t_flags |= TF_ACKNOW; + todrop = ti->ti_len; + } + m_adj(m, todrop); + ti->ti_seq += todrop; + ti->ti_len -= todrop; + if (ti->ti_urp > todrop) + ti->ti_urp -= todrop; + else { + tiflags &= ~TH_URG; + ti->ti_urp = 0; + } + } + /* + * If new data are received on a connection after the + * user processes are gone, then RST the other end. + */ + if ((so->so_state & SS_NOFDREF) && tp->t_state > TCPS_CLOSE_WAIT && + ti->ti_len) { + tp = tcp_close(tp); + goto dropwithreset; + } + + /* + * If segment ends after window, drop trailing data + * (and PUSH and FIN); if nothing left, just ACK. + */ + todrop = (ti->ti_seq + ti->ti_len) - (tp->rcv_nxt + tp->rcv_wnd); + if (todrop > 0) { + if (todrop >= ti->ti_len) { + /* + * If a new connection request is received + * while in TIME_WAIT, drop the old connection + * and start over if the sequence numbers + * are above the previous ones. + */ + if (tiflags & TH_SYN && tp->t_state == TCPS_TIME_WAIT && + SEQ_GT(ti->ti_seq, tp->rcv_nxt)) { + iss = tp->rcv_nxt + TCP_ISSINCR; + tp = tcp_close(tp); + goto findso; + } + /* + * If window is closed can only take segments at + * window edge, and have to drop data and PUSH from + * incoming segments. Continue processing, but + * remember to ack. Otherwise, drop segment + * and ack. + */ + if (tp->rcv_wnd == 0 && ti->ti_seq == tp->rcv_nxt) { + tp->t_flags |= TF_ACKNOW; + } else { + goto dropafterack; + } + } + m_adj(m, -todrop); + ti->ti_len -= todrop; + tiflags &= ~(TH_PUSH | TH_FIN); + } + + /* + * If the RST bit is set examine the state: + * SYN_RECEIVED STATE: + * If passive open, return to LISTEN state. + * If active open, inform user that connection was refused. + * ESTABLISHED, FIN_WAIT_1, FIN_WAIT2, CLOSE_WAIT STATES: + * Inform user that connection was reset, and close tcb. + * CLOSING, LAST_ACK, TIME_WAIT STATES + * Close the tcb. + */ + if (tiflags & TH_RST) + switch (tp->t_state) { + case TCPS_SYN_RECEIVED: + case TCPS_ESTABLISHED: + case TCPS_FIN_WAIT_1: + case TCPS_FIN_WAIT_2: + case TCPS_CLOSE_WAIT: + tp->t_state = TCPS_CLOSED; + tcp_close(tp); + goto drop; + + case TCPS_CLOSING: + case TCPS_LAST_ACK: + case TCPS_TIME_WAIT: + tcp_close(tp); + goto drop; + } + + /* + * If a SYN is in the window, then this is an + * error and we send an RST and drop the connection. + */ + if (tiflags & TH_SYN) { + tp = tcp_drop(tp, 0); + goto dropwithreset; + } + + /* + * If the ACK bit is off we drop the segment and return. + */ + if ((tiflags & TH_ACK) == 0) + goto drop; + + /* + * Ack processing. + */ + switch (tp->t_state) { + /* + * In SYN_RECEIVED state if the ack ACKs our SYN then enter + * ESTABLISHED state and continue processing, otherwise + * send an RST. una<=ack<=max + */ + case TCPS_SYN_RECEIVED: + if (getenv("SLIRP_FUZZING") && + /* Align seq numbers on what the fuzzing trace says */ + tp->iss == 1 && ti->ti_ack != 0) { + tp->iss = ti->ti_ack - 1; + tp->snd_max = tp->iss + 1; + tp->snd_una = ti->ti_ack; + } + + if (SEQ_GT(tp->snd_una, ti->ti_ack) || SEQ_GT(ti->ti_ack, tp->snd_max)) + goto dropwithreset; + tp->t_state = TCPS_ESTABLISHED; + /* + * The sent SYN is ack'ed with our sequence number +1 + * The first data byte already in the buffer will get + * lost if no correction is made. This is only needed for + * SS_CTL since the buffer is empty otherwise. + * tp->snd_una++; or: + */ + tp->snd_una = ti->ti_ack; + if (so->so_state & SS_CTL) { + /* So tcp_ctl reports the right state */ + ret = tcp_ctl(so); + if (ret == 1) { + soisfconnected(so); + so->so_state &= ~SS_CTL; /* success XXX */ + } else if (ret == 2) { + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; /* CTL_CMD */ + } else { + needoutput = 1; + tp->t_state = TCPS_FIN_WAIT_1; + } + } else { + soisfconnected(so); + } + + tcp_reass(tp, (struct tcpiphdr *)0, (struct mbuf *)0); + tp->snd_wl1 = ti->ti_seq - 1; + /* Avoid ack processing; snd_una==ti_ack => dup ack */ + goto synrx_to_est; + /* fall into ... */ + + /* + * In ESTABLISHED state: drop duplicate ACKs; ACK out of range + * ACKs. If the ack is in the range + * tp->snd_una < ti->ti_ack <= tp->snd_max + * then advance tp->snd_una to ti->ti_ack and drop + * data from the retransmission queue. If this ACK reflects + * more up to date window information we update our window information. + */ + case TCPS_ESTABLISHED: + case TCPS_FIN_WAIT_1: + case TCPS_FIN_WAIT_2: + case TCPS_CLOSE_WAIT: + case TCPS_CLOSING: + case TCPS_LAST_ACK: + case TCPS_TIME_WAIT: + + if (SEQ_LEQ(ti->ti_ack, tp->snd_una)) { + if (ti->ti_len == 0 && tiwin == tp->snd_wnd) { + DEBUG_MISC(" dup ack m = %p so = %p", m, so); + /* + * If we have outstanding data (other than + * a window probe), this is a completely + * duplicate ack (ie, window info didn't + * change), the ack is the biggest we've + * seen and we've seen exactly our rexmt + * threshold of them, assume a packet + * has been dropped and retransmit it. + * Kludge snd_nxt & the congestion + * window so we send only this one + * packet. + * + * We know we're losing at the current + * window size so do congestion avoidance + * (set ssthresh to half the current window + * and pull our congestion window back to + * the new ssthresh). + * + * Dup acks mean that packets have left the + * network (they're now cached at the receiver) + * so bump cwnd by the amount in the receiver + * to keep a constant cwnd packets in the + * network. + */ + if (tp->t_timer[TCPT_REXMT] == 0 || ti->ti_ack != tp->snd_una) + tp->t_dupacks = 0; + else if (++tp->t_dupacks == TCPREXMTTHRESH) { + tcp_seq onxt = tp->snd_nxt; + unsigned win = + MIN(tp->snd_wnd, tp->snd_cwnd) / 2 / tp->t_maxseg; + + if (win < 2) + win = 2; + tp->snd_ssthresh = win * tp->t_maxseg; + tp->t_timer[TCPT_REXMT] = 0; + tp->t_rtt = 0; + tp->snd_nxt = ti->ti_ack; + tp->snd_cwnd = tp->t_maxseg; + tcp_output(tp); + tp->snd_cwnd = + tp->snd_ssthresh + tp->t_maxseg * tp->t_dupacks; + if (SEQ_GT(onxt, tp->snd_nxt)) + tp->snd_nxt = onxt; + goto drop; + } else if (tp->t_dupacks > TCPREXMTTHRESH) { + tp->snd_cwnd += tp->t_maxseg; + tcp_output(tp); + goto drop; + } + } else + tp->t_dupacks = 0; + break; + } + synrx_to_est: + /* + * If the congestion window was inflated to account + * for the other side's cached packets, retract it. + */ + if (tp->t_dupacks > TCPREXMTTHRESH && tp->snd_cwnd > tp->snd_ssthresh) + tp->snd_cwnd = tp->snd_ssthresh; + tp->t_dupacks = 0; + if (SEQ_GT(ti->ti_ack, tp->snd_max)) { + goto dropafterack; + } + acked = ti->ti_ack - tp->snd_una; + + /* + * If transmit timer is running and timed sequence + * number was acked, update smoothed round trip time. + * Since we now have an rtt measurement, cancel the + * timer backoff (cf., Phil Karn's retransmit alg.). + * Recompute the initial retransmit timer. + */ + if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) + tcp_xmit_timer(tp, tp->t_rtt); + + /* + * If all outstanding data is acked, stop retransmit + * timer and remember to restart (more output or persist). + * If there is more data to be acked, restart retransmit + * timer, using current (possibly backed-off) value. + */ + if (ti->ti_ack == tp->snd_max) { + tp->t_timer[TCPT_REXMT] = 0; + needoutput = 1; + } else if (tp->t_timer[TCPT_PERSIST] == 0) + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + /* + * When new data is acked, open the congestion window. + * If the window gives us less than ssthresh packets + * in flight, open exponentially (maxseg per packet). + * Otherwise open linearly: maxseg per window + * (maxseg^2 / cwnd per packet). + */ + { + register unsigned cw = tp->snd_cwnd; + register unsigned incr = tp->t_maxseg; + + if (cw > tp->snd_ssthresh) + incr = incr * incr / cw; + tp->snd_cwnd = MIN(cw + incr, TCP_MAXWIN << tp->snd_scale); + } + if (acked > so->so_snd.sb_cc) { + tp->snd_wnd -= so->so_snd.sb_cc; + sodrop(so, (int)so->so_snd.sb_cc); + ourfinisacked = 1; + } else { + sodrop(so, acked); + tp->snd_wnd -= acked; + ourfinisacked = 0; + } + tp->snd_una = ti->ti_ack; + if (SEQ_LT(tp->snd_nxt, tp->snd_una)) + tp->snd_nxt = tp->snd_una; + + switch (tp->t_state) { + /* + * In FIN_WAIT_1 STATE in addition to the processing + * for the ESTABLISHED state if our FIN is now acknowledged + * then enter FIN_WAIT_2. + */ + case TCPS_FIN_WAIT_1: + if (ourfinisacked) { + /* + * If we can't receive any more + * data, then closing user can proceed. + * Starting the timer is contrary to the + * specification, but if we don't get a FIN + * we'll hang forever. + */ + if (so->so_state & SS_FCANTRCVMORE) { + tp->t_timer[TCPT_2MSL] = TCP_MAXIDLE; + } + tp->t_state = TCPS_FIN_WAIT_2; + } + break; + + /* + * In CLOSING STATE in addition to the processing for + * the ESTABLISHED state if the ACK acknowledges our FIN + * then enter the TIME-WAIT state, otherwise ignore + * the segment. + */ + case TCPS_CLOSING: + if (ourfinisacked) { + tp->t_state = TCPS_TIME_WAIT; + tcp_canceltimers(tp); + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + } + break; + + /* + * In LAST_ACK, we may still be waiting for data to drain + * and/or to be acked, as well as for the ack of our FIN. + * If our FIN is now acknowledged, delete the TCB, + * enter the closed state and return. + */ + case TCPS_LAST_ACK: + if (ourfinisacked) { + tcp_close(tp); + goto drop; + } + break; + + /* + * In TIME_WAIT state the only thing that should arrive + * is a retransmission of the remote FIN. Acknowledge + * it and restart the finack timer. + */ + case TCPS_TIME_WAIT: + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + goto dropafterack; + } + } /* switch(tp->t_state) */ + +step6: + /* + * Update window information. + * Don't look at window if no ACK: TAC's send garbage on first SYN. + */ + if ((tiflags & TH_ACK) && + (SEQ_LT(tp->snd_wl1, ti->ti_seq) || + (tp->snd_wl1 == ti->ti_seq && + (SEQ_LT(tp->snd_wl2, ti->ti_ack) || + (tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd))))) { + tp->snd_wnd = tiwin; + tp->snd_wl1 = ti->ti_seq; + tp->snd_wl2 = ti->ti_ack; + if (tp->snd_wnd > tp->max_sndwnd) + tp->max_sndwnd = tp->snd_wnd; + needoutput = 1; + } + + /* + * Process segments with URG. + */ + if ((tiflags & TH_URG) && ti->ti_urp && + TCPS_HAVERCVDFIN(tp->t_state) == 0) { + /* + * This is a kludge, but if we receive and accept + * random urgent pointers, we'll crash in + * soreceive. It's hard to imagine someone + * actually wanting to send this much urgent data. + */ + if (ti->ti_urp + so->so_rcv.sb_cc > so->so_rcv.sb_datalen) { + ti->ti_urp = 0; + tiflags &= ~TH_URG; + goto dodata; + } + /* + * If this segment advances the known urgent pointer, + * then mark the data stream. This should not happen + * in CLOSE_WAIT, CLOSING, LAST_ACK or TIME_WAIT STATES since + * a FIN has been received from the remote side. + * In these states we ignore the URG. + * + * According to RFC961 (Assigned Protocols), + * the urgent pointer points to the last octet + * of urgent data. We continue, however, + * to consider it to indicate the first octet + * of data past the urgent section as the original + * spec states (in one of two places). + */ + if (SEQ_GT(ti->ti_seq + ti->ti_urp, tp->rcv_up)) { + tp->rcv_up = ti->ti_seq + ti->ti_urp; + so->so_urgc = + so->so_rcv.sb_cc + (tp->rcv_up - tp->rcv_nxt); /* -1; */ + tp->rcv_up = ti->ti_seq + ti->ti_urp; + } + } else + /* + * If no out of band data is expected, + * pull receive urgent pointer along + * with the receive window. + */ + if (SEQ_GT(tp->rcv_nxt, tp->rcv_up)) + tp->rcv_up = tp->rcv_nxt; +dodata: + + /* + * If this is a small packet, then ACK now - with Nagel + * congestion avoidance sender won't send more until + * he gets an ACK. + */ + if (ti->ti_len && (unsigned)ti->ti_len <= 5 && + ((struct tcpiphdr_2 *)ti)->first_char == (char)27) { + tp->t_flags |= TF_ACKNOW; + } + + /* + * Process the segment text, merging it into the TCP sequencing queue, + * and arranging for acknowledgment of receipt if necessary. + * This process logically involves adjusting tp->rcv_wnd as data + * is presented to the user (this happens in tcp_usrreq.c, + * case PRU_RCVD). If a FIN has already been received on this + * connection then we just ignore the text. + */ + if ((ti->ti_len || (tiflags & TH_FIN)) && + TCPS_HAVERCVDFIN(tp->t_state) == 0) { + + /* + * segment is the next to be received on an established + * connection, and the queue is empty, avoid linkage into and + * removal from the queue and repetition of various + * conversions from tcp_reass(). + */ + if (ti->ti_seq == tp->rcv_nxt && tcpfrag_list_empty(tp) && + tp->t_state == TCPS_ESTABLISHED) { + tp->t_flags |= TF_DELACK; + tp->rcv_nxt += ti->ti_len; + tiflags = ti->ti_flags & TH_FIN; + if (so->so_emu) { + if (tcp_emu(so, m)) + sbappend(so, m); + } else + sbappend(so, m); + } else { + tiflags = tcp_reass(tp, ti, m); + tp->t_flags |= TF_ACKNOW; + } + } else { + m_free(m); + tiflags &= ~TH_FIN; + } + + /* + * If FIN is received ACK the FIN and let the user know + * that the connection is closing. + */ + if (tiflags & TH_FIN) { + if (TCPS_HAVERCVDFIN(tp->t_state) == 0) { + /* + * If we receive a FIN we can't send more data, + * set it SS_FDRAIN + * Shutdown the socket if there is no rx data in the + * buffer. + * soread() is called on completion of shutdown() and + * will got to TCPS_LAST_ACK, and use tcp_output() + * to send the FIN. + */ + sofwdrain(so); + + tp->t_flags |= TF_ACKNOW; + tp->rcv_nxt++; + } + switch (tp->t_state) { + /* + * In SYN_RECEIVED and ESTABLISHED STATES + * enter the CLOSE_WAIT state. + */ + case TCPS_SYN_RECEIVED: + case TCPS_ESTABLISHED: + if (so->so_emu == EMU_CTL) /* no shutdown on socket */ + tp->t_state = TCPS_LAST_ACK; + else + tp->t_state = TCPS_CLOSE_WAIT; + break; + + /* + * If still in FIN_WAIT_1 STATE FIN has not been acked so + * enter the CLOSING state. + */ + case TCPS_FIN_WAIT_1: + tp->t_state = TCPS_CLOSING; + break; + + /* + * In FIN_WAIT_2 state enter the TIME_WAIT state, + * starting the time-wait timer, turning off the other + * standard timers. + */ + case TCPS_FIN_WAIT_2: + tp->t_state = TCPS_TIME_WAIT; + tcp_canceltimers(tp); + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + break; + + /* + * In TIME_WAIT state restart the 2 MSL time_wait timer. + */ + case TCPS_TIME_WAIT: + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + break; + } + } + + /* + * Return any desired output. + */ + if (needoutput || (tp->t_flags & TF_ACKNOW)) { + tcp_output(tp); + } + return; + +dropafterack: + /* + * Generate an ACK dropping incoming segment if it occupies + * sequence space, where the ACK reflects our state. + */ + if (tiflags & TH_RST) + goto drop; + m_free(m); + tp->t_flags |= TF_ACKNOW; + tcp_output(tp); + return; + +dropwithreset: + /* reuses m if m!=NULL, m_free() unnecessary */ + if (tiflags & TH_ACK) + tcp_respond(tp, ti, m, (tcp_seq)0, ti->ti_ack, TH_RST, af); + else { + if (tiflags & TH_SYN) + ti->ti_len++; + tcp_respond(tp, ti, m, ti->ti_seq + ti->ti_len, (tcp_seq)0, + TH_RST | TH_ACK, af); + } + + return; + +drop: + /* + * Drop space held by incoming segment and return. + */ + m_free(m); +} + +static void tcp_dooptions(struct tcpcb *tp, uint8_t *cp, int cnt, + struct tcpiphdr *ti) +{ + uint16_t mss; + int opt, optlen; + + DEBUG_CALL("tcp_dooptions"); + DEBUG_ARG("tp = %p cnt=%i", tp, cnt); + + for (; cnt > 0; cnt -= optlen, cp += optlen) { + opt = cp[0]; + if (opt == TCPOPT_EOL) + break; + if (opt == TCPOPT_NOP) + optlen = 1; + else { + optlen = cp[1]; + if (optlen <= 0) + break; + } + switch (opt) { + default: + continue; + + case TCPOPT_MAXSEG: + if (optlen != TCPOLEN_MAXSEG) + continue; + if (!(ti->ti_flags & TH_SYN)) + continue; + memcpy((char *)&mss, (char *)cp + 2, sizeof(mss)); + NTOHS(mss); + tcp_mss(tp, mss); /* sets t_maxseg */ + break; + } + } +} + +/* + * Collect new round-trip time estimate + * and update averages and current timeout. + */ + +static void tcp_xmit_timer(register struct tcpcb *tp, int rtt) +{ + register short delta; + + DEBUG_CALL("tcp_xmit_timer"); + DEBUG_ARG("tp = %p", tp); + DEBUG_ARG("rtt = %d", rtt); + + if (tp->t_srtt != 0) { + /* + * srtt is stored as fixed point with 3 bits after the + * binary point (i.e., scaled by 8). The following magic + * is equivalent to the smoothing algorithm in rfc793 with + * an alpha of .875 (srtt = rtt/8 + srtt*7/8 in fixed + * point). Adjust rtt to origin 0. + */ + delta = rtt - 1 - (tp->t_srtt >> TCP_RTT_SHIFT); + if ((tp->t_srtt += delta) <= 0) + tp->t_srtt = 1; + /* + * We accumulate a smoothed rtt variance (actually, a + * smoothed mean difference), then set the retransmit + * timer to smoothed rtt + 4 times the smoothed variance. + * rttvar is stored as fixed point with 2 bits after the + * binary point (scaled by 4). The following is + * equivalent to rfc793 smoothing with an alpha of .75 + * (rttvar = rttvar*3/4 + |delta| / 4). This replaces + * rfc793's wired-in beta. + */ + if (delta < 0) + delta = -delta; + delta -= (tp->t_rttvar >> TCP_RTTVAR_SHIFT); + if ((tp->t_rttvar += delta) <= 0) + tp->t_rttvar = 1; + } else { + /* + * No rtt measurement yet - use the unsmoothed rtt. + * Set the variance to half the rtt (so our first + * retransmit happens at 3*rtt). + */ + tp->t_srtt = rtt << TCP_RTT_SHIFT; + tp->t_rttvar = rtt << (TCP_RTTVAR_SHIFT - 1); + } + tp->t_rtt = 0; + tp->t_rxtshift = 0; + + /* + * the retransmit should happen at rtt + 4 * rttvar. + * Because of the way we do the smoothing, srtt and rttvar + * will each average +1/2 tick of bias. When we compute + * the retransmit timer, we want 1/2 tick of rounding and + * 1 extra tick because of +-1/2 tick uncertainty in the + * firing of the timer. The bias will give us exactly the + * 1.5 tick we need. But, because the bias is + * statistical, we have to test that we don't drop below + * the minimum feasible timer (which is 2 ticks). + */ + TCPT_RANGESET(tp->t_rxtcur, TCP_REXMTVAL(tp), (short)tp->t_rttmin, + TCPTV_REXMTMAX); /* XXX */ + + /* + * We received an ack for a packet that wasn't retransmitted; + * it is probably safe to discard any error indications we've + * received recently. This isn't quite right, but close enough + * for now (a route might have failed after we sent a segment, + * and the return path might not be symmetrical). + */ + tp->t_softerror = 0; +} + +/* + * Determine a reasonable value for maxseg size. + * If the route is known, check route for mtu. + * If none, use an mss that can be handled on the outgoing + * interface without forcing IP to fragment; if bigger than + * an mbuf cluster (MCLBYTES), round down to nearest multiple of MCLBYTES + * to utilize large mbufs. If no route is found, route has no mtu, + * or the destination isn't local, use a default, hopefully conservative + * size (usually 512 or the default IP max size, but no more than the mtu + * of the interface), as we can't discover anything about intervening + * gateways or networks. We also initialize the congestion/slow start + * window to be a single segment if the destination isn't local. + * While looking at the routing entry, we also initialize other path-dependent + * parameters from pre-set or cached values in the routing entry. + */ + +int tcp_mss(struct tcpcb *tp, unsigned offer) +{ + struct socket *so = tp->t_socket; + int mss; + + DEBUG_CALL("tcp_mss"); + DEBUG_ARG("tp = %p", tp); + DEBUG_ARG("offer = %d", offer); + + switch (so->so_ffamily) { + case AF_INET: + mss = MIN(so->slirp->if_mtu, so->slirp->if_mru) - + sizeof(struct tcphdr) - sizeof(struct ip); + break; + case AF_INET6: + mss = MIN(so->slirp->if_mtu, so->slirp->if_mru) - + sizeof(struct tcphdr) - sizeof(struct ip6); + break; + default: + g_assert_not_reached(); + } + + if (offer) + mss = MIN(mss, offer); + mss = MAX(mss, 32); + if (mss < tp->t_maxseg || offer != 0) + tp->t_maxseg = MIN(mss, TCP_MAXSEG_MAX); + + tp->snd_cwnd = mss; + + sbreserve(&so->so_snd, + TCP_SNDSPACE + + ((TCP_SNDSPACE % mss) ? (mss - (TCP_SNDSPACE % mss)) : 0)); + sbreserve(&so->so_rcv, + TCP_RCVSPACE + + ((TCP_RCVSPACE % mss) ? (mss - (TCP_RCVSPACE % mss)) : 0)); + + DEBUG_MISC(" returning mss = %d", mss); + + return mss; +} diff --git a/app/src/main/cpp/libslirp/src/tcp_output.c b/app/src/main/cpp/libslirp/src/tcp_output.c new file mode 100644 index 00000000..77c1204a --- /dev/null +++ b/app/src/main/cpp/libslirp/src/tcp_output.c @@ -0,0 +1,513 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_output.c 8.3 (Berkeley) 12/30/93 + * tcp_output.c,v 1.3 1994/09/15 10:36:55 davidg Exp + */ + +/* + * Changes and additions relating to SLiRP + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" + +static const uint8_t tcp_outflags[TCP_NSTATES] = { + TH_RST | TH_ACK, 0, TH_SYN, TH_SYN | TH_ACK, + TH_ACK, TH_ACK, TH_FIN | TH_ACK, TH_FIN | TH_ACK, + TH_FIN | TH_ACK, TH_ACK, TH_ACK, +}; + + +#undef MAX_TCPOPTLEN +#define MAX_TCPOPTLEN 32 /* max # bytes that go in options */ + +/* + * Tcp output routine: figure out what should be sent and send it. + */ +int tcp_output(struct tcpcb *tp) +{ + register struct socket *so = tp->t_socket; + register long len, win; + int off, flags, error; + register struct mbuf *m; + register struct tcpiphdr *ti, tcpiph_save; + struct ip *ip; + struct ip6 *ip6; + uint8_t opt[MAX_TCPOPTLEN]; + unsigned optlen, hdrlen; + int idle, sendalot; + + DEBUG_CALL("tcp_output"); + DEBUG_ARG("tp = %p", tp); + + /* + * Determine length of data that should be transmitted, + * and flags that will be used. + * If there is some data or critical controls (SYN, RST) + * to send, then transmit; otherwise, investigate further. + */ + idle = (tp->snd_max == tp->snd_una); + if (idle && tp->t_idle >= tp->t_rxtcur) + /* + * We have been idle for "a while" and no acks are + * expected to clock out any data we send -- + * slow start to get ack "clock" running again. + */ + tp->snd_cwnd = tp->t_maxseg; +again: + sendalot = 0; + off = tp->snd_nxt - tp->snd_una; + win = MIN(tp->snd_wnd, tp->snd_cwnd); + + flags = tcp_outflags[tp->t_state]; + + DEBUG_MISC(" --- tcp_output flags = 0x%x", flags); + + /* + * If in persist timeout with window of 0, send 1 byte. + * Otherwise, if window is small but nonzero + * and timer expired, we will send what we can + * and go to transmit state. + */ + if (tp->t_force) { + if (win == 0) { + /* + * If we still have some data to send, then + * clear the FIN bit. Usually this would + * happen below when it realizes that we + * aren't sending all the data. However, + * if we have exactly 1 byte of unset data, + * then it won't clear the FIN bit below, + * and if we are in persist state, we wind + * up sending the packet without recording + * that we sent the FIN bit. + * + * We can't just blindly clear the FIN bit, + * because if we don't have any more data + * to send then the probe will be the FIN + * itself. + */ + if (off < so->so_snd.sb_cc) + flags &= ~TH_FIN; + win = 1; + } else { + tp->t_timer[TCPT_PERSIST] = 0; + tp->t_rxtshift = 0; + } + } + + len = MIN(so->so_snd.sb_cc, win) - off; + + if (len < 0) { + /* + * If FIN has been sent but not acked, + * but we haven't been called to retransmit, + * len will be -1. Otherwise, window shrank + * after we sent into it. If window shrank to 0, + * cancel pending retransmit and pull snd_nxt + * back to (closed) window. We will enter persist + * state below. If the window didn't close completely, + * just wait for an ACK. + */ + len = 0; + if (win == 0) { + tp->t_timer[TCPT_REXMT] = 0; + tp->snd_nxt = tp->snd_una; + } + } + + if (len > tp->t_maxseg) { + len = tp->t_maxseg; + sendalot = 1; + } + if (SEQ_LT(tp->snd_nxt + len, tp->snd_una + so->so_snd.sb_cc)) + flags &= ~TH_FIN; + + win = sbspace(&so->so_rcv); + + /* + * Sender silly window avoidance. If connection is idle + * and can send all data, a maximum segment, + * at least a maximum default-size segment do it, + * or are forced, do it; otherwise don't bother. + * If peer's buffer is tiny, then send + * when window is at least half open. + * If retransmitting (possibly after persist timer forced us + * to send into a small window), then must resend. + */ + if (len) { + if (len == tp->t_maxseg) + goto send; + if ((1 || idle || tp->t_flags & TF_NODELAY) && + len + off >= so->so_snd.sb_cc) + goto send; + if (tp->t_force) + goto send; + if (len >= tp->max_sndwnd / 2 && tp->max_sndwnd > 0) + goto send; + if (SEQ_LT(tp->snd_nxt, tp->snd_max)) + goto send; + } + + /* + * Compare available window to amount of window + * known to peer (as advertised window less + * next expected input). If the difference is at least two + * max size segments, or at least 50% of the maximum possible + * window, then want to send a window update to peer. + */ + if (win > 0) { + /* + * "adv" is the amount we can increase the window, + * taking into account that we are limited by + * TCP_MAXWIN << tp->rcv_scale. + */ + long adv = MIN(win, (long)TCP_MAXWIN << tp->rcv_scale) - + (tp->rcv_adv - tp->rcv_nxt); + + if (adv >= (long)(2 * tp->t_maxseg)) + goto send; + if (2 * adv >= (long)so->so_rcv.sb_datalen) + goto send; + } + + /* + * Send if we owe peer an ACK. + */ + if (tp->t_flags & TF_ACKNOW) + goto send; + if (flags & (TH_SYN | TH_RST)) + goto send; + if (SEQ_GT(tp->snd_up, tp->snd_una)) + goto send; + /* + * If our state indicates that FIN should be sent + * and we have not yet done so, or we're retransmitting the FIN, + * then we need to send. + */ + if (flags & TH_FIN && + ((tp->t_flags & TF_SENTFIN) == 0 || tp->snd_nxt == tp->snd_una)) + goto send; + + /* + * TCP window updates are not reliable, rather a polling protocol + * using ``persist'' packets is used to insure receipt of window + * updates. The three ``states'' for the output side are: + * idle not doing retransmits or persists + * persisting to move a small or zero window + * (re)transmitting and thereby not persisting + * + * tp->t_timer[TCPT_PERSIST] + * is set when we are in persist state. + * tp->t_force + * is set when we are called to send a persist packet. + * tp->t_timer[TCPT_REXMT] + * is set when we are retransmitting + * The output side is idle when both timers are zero. + * + * If send window is too small, there is data to transmit, and no + * retransmit or persist is pending, then go to persist state. + * If nothing happens soon, send when timer expires: + * if window is nonzero, transmit what we can, + * otherwise force out a byte. + */ + if (so->so_snd.sb_cc && tp->t_timer[TCPT_REXMT] == 0 && + tp->t_timer[TCPT_PERSIST] == 0) { + tp->t_rxtshift = 0; + tcp_setpersist(tp); + } + + /* + * No reason to send a segment, just return. + */ + return (0); + +send: + /* + * Before ESTABLISHED, force sending of initial options + * unless TCP set not to do any options. + * NOTE: we assume that the IP/TCP header plus TCP options + * always fit in a single mbuf, leaving room for a maximum + * link header, i.e. + * max_linkhdr + sizeof (struct tcpiphdr) + optlen <= MHLEN + */ + optlen = 0; + hdrlen = sizeof(struct tcpiphdr); + if (flags & TH_SYN) { + tp->snd_nxt = tp->iss; + if ((tp->t_flags & TF_NOOPT) == 0) { + uint16_t mss; + + opt[0] = TCPOPT_MAXSEG; + opt[1] = 4; + mss = htons((uint16_t)tcp_mss(tp, 0)); + memcpy((char *)(opt + 2), (char *)&mss, sizeof(mss)); + optlen = 4; + } + } + + hdrlen += optlen; + + /* + * Adjust data length if insertion of options will + * bump the packet length beyond the t_maxseg length. + */ + if (len > tp->t_maxseg - optlen) { + len = tp->t_maxseg - optlen; + sendalot = 1; + } + + /* + * Grab a header mbuf, attaching a copy of data to + * be transmitted, and initialize the header from + * the template for sends on this connection. + */ + if (len) { + m = m_get(so->slirp); + if (m == NULL) { + error = 1; + goto out; + } + m->m_data += IF_MAXLINKHDR; + m->m_len = hdrlen; + + sbcopy(&so->so_snd, off, (int)len, mtod(m, char *) + hdrlen); + m->m_len += len; + + /* + * If we're sending everything we've got, set PUSH. + * (This will keep happy those implementations which only + * give data to the user when a buffer fills or + * a PUSH comes in.) + */ + if (off + len == so->so_snd.sb_cc) + flags |= TH_PUSH; + } else { + m = m_get(so->slirp); + if (m == NULL) { + error = 1; + goto out; + } + m->m_data += IF_MAXLINKHDR; + m->m_len = hdrlen; + } + + ti = mtod(m, struct tcpiphdr *); + + memcpy((char *)ti, &tp->t_template, sizeof(struct tcpiphdr)); + + /* + * Fill in fields, remembering maximum advertised + * window for use in delaying messages about window sizes. + * If resending a FIN, be sure not to use a new sequence number. + */ + if (flags & TH_FIN && tp->t_flags & TF_SENTFIN && + tp->snd_nxt == tp->snd_max) + tp->snd_nxt--; + /* + * If we are doing retransmissions, then snd_nxt will + * not reflect the first unsent octet. For ACK only + * packets, we do not want the sequence number of the + * retransmitted packet, we want the sequence number + * of the next unsent octet. So, if there is no data + * (and no SYN or FIN), use snd_max instead of snd_nxt + * when filling in ti_seq. But if we are in persist + * state, snd_max might reflect one byte beyond the + * right edge of the window, so use snd_nxt in that + * case, since we know we aren't doing a retransmission. + * (retransmit and persist are mutually exclusive...) + */ + if (len || (flags & (TH_SYN | TH_FIN)) || tp->t_timer[TCPT_PERSIST]) + ti->ti_seq = htonl(tp->snd_nxt); + else + ti->ti_seq = htonl(tp->snd_max); + ti->ti_ack = htonl(tp->rcv_nxt); + if (optlen) { + memcpy((char *)(ti + 1), (char *)opt, optlen); + ti->ti_off = (sizeof(struct tcphdr) + optlen) >> 2; + } + ti->ti_flags = flags; + /* + * Calculate receive window. Don't shrink window, + * but avoid silly window syndrome. + */ + if (win < (long)(so->so_rcv.sb_datalen / 4) && win < (long)tp->t_maxseg) + win = 0; + if (win > (long)TCP_MAXWIN << tp->rcv_scale) + win = (long)TCP_MAXWIN << tp->rcv_scale; + if (win < (long)(tp->rcv_adv - tp->rcv_nxt)) + win = (long)(tp->rcv_adv - tp->rcv_nxt); + ti->ti_win = htons((uint16_t)(win >> tp->rcv_scale)); + + if (SEQ_GT(tp->snd_up, tp->snd_una)) { + ti->ti_urp = htons((uint16_t)(tp->snd_up - ntohl(ti->ti_seq))); + ti->ti_flags |= TH_URG; + } else + /* + * If no urgent pointer to send, then we pull + * the urgent pointer to the left edge of the send window + * so that it doesn't drift into the send window on sequence + * number wraparound. + */ + tp->snd_up = tp->snd_una; /* drag it along */ + + /* + * Put TCP length in extended header, and then + * checksum extended header and data. + */ + if (len + optlen) + ti->ti_len = htons((uint16_t)(sizeof(struct tcphdr) + optlen + len)); + ti->ti_sum = cksum(m, (int)(hdrlen + len)); + + /* + * In transmit state, time the transmission and arrange for + * the retransmit. In persist state, just set snd_max. + */ + if (tp->t_force == 0 || tp->t_timer[TCPT_PERSIST] == 0) { + tcp_seq startseq = tp->snd_nxt; + + /* + * Advance snd_nxt over sequence space of this segment. + */ + if (flags & (TH_SYN | TH_FIN)) { + if (flags & TH_SYN) + tp->snd_nxt++; + if (flags & TH_FIN) { + tp->snd_nxt++; + tp->t_flags |= TF_SENTFIN; + } + } + tp->snd_nxt += len; + if (SEQ_GT(tp->snd_nxt, tp->snd_max)) { + tp->snd_max = tp->snd_nxt; + /* + * Time this transmission if not a retransmission and + * not currently timing anything. + */ + if (tp->t_rtt == 0) { + tp->t_rtt = 1; + tp->t_rtseq = startseq; + } + } + + /* + * Set retransmit timer if not currently set, + * and not doing an ack or a keep-alive probe. + * Initial value for retransmit timer is smoothed + * round-trip time + 2 * round-trip time variance. + * Initialize shift counter which is used for backoff + * of retransmit time. + */ + if (tp->t_timer[TCPT_REXMT] == 0 && tp->snd_nxt != tp->snd_una) { + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + if (tp->t_timer[TCPT_PERSIST]) { + tp->t_timer[TCPT_PERSIST] = 0; + tp->t_rxtshift = 0; + } + } + } else if (SEQ_GT(tp->snd_nxt + len, tp->snd_max)) + tp->snd_max = tp->snd_nxt + len; + + /* + * Fill in IP length and desired time to live and + * send to IP level. There should be a better way + * to handle ttl and tos; we could keep them in + * the template, but need a way to checksum without them. + */ + m->m_len = hdrlen + len; /* XXX Needed? m_len should be correct */ + tcpiph_save = *mtod(m, struct tcpiphdr *); + + switch (so->so_ffamily) { + case AF_INET: + m->m_data += + sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - sizeof(struct ip); + m->m_len -= + sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - sizeof(struct ip); + ip = mtod(m, struct ip *); + + ip->ip_len = m->m_len; + ip->ip_dst = tcpiph_save.ti_dst; + ip->ip_src = tcpiph_save.ti_src; + ip->ip_p = tcpiph_save.ti_pr; + + ip->ip_ttl = IPDEFTTL; + ip->ip_tos = so->so_iptos; + error = ip_output(so, m); + break; + + case AF_INET6: + m->m_data += sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - + sizeof(struct ip6); + m->m_len -= sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - + sizeof(struct ip6); + ip6 = mtod(m, struct ip6 *); + + ip6->ip_pl = tcpiph_save.ti_len; + ip6->ip_dst = tcpiph_save.ti_dst6; + ip6->ip_src = tcpiph_save.ti_src6; + ip6->ip_nh = tcpiph_save.ti_nh6; + + error = ip6_output(so, m, 0); + break; + + default: + g_assert_not_reached(); + } + + if (error) { + out: + return (error); + } + + /* + * Data sent (as far as we can tell). + * If this advertises a larger window than any other segment, + * then remember the size of the advertised window. + * Any pending ACK has now been sent. + */ + if (win > 0 && SEQ_GT(tp->rcv_nxt + win, tp->rcv_adv)) + tp->rcv_adv = tp->rcv_nxt + win; + tp->last_ack_sent = tp->rcv_nxt; + tp->t_flags &= ~(TF_ACKNOW | TF_DELACK); + if (sendalot) + goto again; + + return (0); +} + +void tcp_setpersist(struct tcpcb *tp) +{ + int t = ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1; + + TCPT_RANGESET(tp->t_timer[TCPT_PERSIST], t * tcp_backoff[tp->t_rxtshift], + TCPTV_PERSMIN, TCPTV_PERSMAX); + if (tp->t_rxtshift < TCP_MAXRXTSHIFT) + tp->t_rxtshift++; +} diff --git a/app/src/main/cpp/libslirp/src/tcp_subr.c b/app/src/main/cpp/libslirp/src/tcp_subr.c new file mode 100644 index 00000000..3cfe2860 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/tcp_subr.c @@ -0,0 +1,986 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_subr.c 8.1 (Berkeley) 6/10/93 + * tcp_subr.c,v 1.5 1994/10/08 22:39:58 phk Exp + */ + +/* + * Changes and additions relating to SLiRP + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" + +/* patchable/settable parameters for tcp */ +/* Don't do rfc1323 performance enhancements */ +#define TCP_DO_RFC1323 0 + +/* + * Tcp initialization + */ +void tcp_init(Slirp *slirp) +{ + slirp->tcp_iss = 1; /* wrong */ + slirp->tcb.so_next = slirp->tcb.so_prev = &slirp->tcb; + slirp->tcp_last_so = &slirp->tcb; +} + +void tcp_cleanup(Slirp *slirp) +{ + while (slirp->tcb.so_next != &slirp->tcb) { + tcp_close(sototcpcb(slirp->tcb.so_next)); + } +} + +void tcp_template(struct tcpcb *tp) +{ + struct socket *so = tp->t_socket; + register struct tcpiphdr *n = &tp->t_template; + + n->ti_mbuf = NULL; + memset(&n->ti, 0, sizeof(n->ti)); + n->ti_x0 = 0; + switch (so->so_ffamily) { + case AF_INET: + n->ti_pr = IPPROTO_TCP; + n->ti_len = htons(sizeof(struct tcphdr)); + n->ti_src = so->so_faddr; + n->ti_dst = so->so_laddr; + n->ti_sport = so->so_fport; + n->ti_dport = so->so_lport; + break; + + case AF_INET6: + n->ti_nh6 = IPPROTO_TCP; + n->ti_len = htons(sizeof(struct tcphdr)); + n->ti_src6 = so->so_faddr6; + n->ti_dst6 = so->so_laddr6; + n->ti_sport = so->so_fport6; + n->ti_dport = so->so_lport6; + break; + + default: + g_assert_not_reached(); + } + + n->ti_seq = 0; + n->ti_ack = 0; + n->ti_x2 = 0; + n->ti_off = 5; + n->ti_flags = 0; + n->ti_win = 0; + n->ti_sum = 0; + n->ti_urp = 0; +} + +/* + * Send a single message to the TCP at address specified by + * the given TCP/IP header. If m == 0, then we make a copy + * of the tcpiphdr at ti and send directly to the addressed host. + * This is used to force keep alive messages out using the TCP + * template for a connection tp->t_template. If flags are given + * then we send a message back to the TCP which originated the + * segment ti, and discard the mbuf containing it and any other + * attached mbufs. + * + * In any case the ack and sequence number of the transmitted + * segment are as specified by the parameters. + */ +void tcp_respond(struct tcpcb *tp, struct tcpiphdr *ti, struct mbuf *m, + tcp_seq ack, tcp_seq seq, int flags, unsigned short af) +{ + register int tlen; + int win = 0; + + DEBUG_CALL("tcp_respond"); + DEBUG_ARG("tp = %p", tp); + DEBUG_ARG("ti = %p", ti); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("ack = %u", ack); + DEBUG_ARG("seq = %u", seq); + DEBUG_ARG("flags = %x", flags); + + if (tp) + win = sbspace(&tp->t_socket->so_rcv); + if (m == NULL) { + if (!tp || (m = m_get(tp->t_socket->slirp)) == NULL) + return; + tlen = 0; + m->m_data += IF_MAXLINKHDR; + *mtod(m, struct tcpiphdr *) = *ti; + ti = mtod(m, struct tcpiphdr *); + switch (af) { + case AF_INET: + ti->ti.ti_i4.ih_x1 = 0; + break; + case AF_INET6: + ti->ti.ti_i6.ih_x1 = 0; + break; + default: + g_assert_not_reached(); + } + flags = TH_ACK; + } else { + /* + * ti points into m so the next line is just making + * the mbuf point to ti + */ + m->m_data = (char *)ti; + + m->m_len = sizeof(struct tcpiphdr); + tlen = 0; +#define xchg(a, b, type) \ + { \ + type t; \ + t = a; \ + a = b; \ + b = t; \ + } + switch (af) { + case AF_INET: + xchg(ti->ti_dst.s_addr, ti->ti_src.s_addr, uint32_t); + xchg(ti->ti_dport, ti->ti_sport, uint16_t); + break; + case AF_INET6: + xchg(ti->ti_dst6, ti->ti_src6, struct in6_addr); + xchg(ti->ti_dport, ti->ti_sport, uint16_t); + break; + default: + g_assert_not_reached(); + } +#undef xchg + } + ti->ti_len = htons((uint16_t)(sizeof(struct tcphdr) + tlen)); + tlen += sizeof(struct tcpiphdr); + m->m_len = tlen; + + ti->ti_mbuf = NULL; + ti->ti_x0 = 0; + ti->ti_seq = htonl(seq); + ti->ti_ack = htonl(ack); + ti->ti_x2 = 0; + ti->ti_off = sizeof(struct tcphdr) >> 2; + ti->ti_flags = flags; + if (tp) + ti->ti_win = htons((uint16_t)(win >> tp->rcv_scale)); + else + ti->ti_win = htons((uint16_t)win); + ti->ti_urp = 0; + ti->ti_sum = 0; + ti->ti_sum = cksum(m, tlen); + + struct tcpiphdr tcpiph_save = *(mtod(m, struct tcpiphdr *)); + struct ip *ip; + struct ip6 *ip6; + + switch (af) { + case AF_INET: + m->m_data += + sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - sizeof(struct ip); + m->m_len -= + sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - sizeof(struct ip); + ip = mtod(m, struct ip *); + ip->ip_len = m->m_len; + ip->ip_dst = tcpiph_save.ti_dst; + ip->ip_src = tcpiph_save.ti_src; + ip->ip_p = tcpiph_save.ti_pr; + + if (flags & TH_RST) { + ip->ip_ttl = MAXTTL; + } else { + ip->ip_ttl = IPDEFTTL; + } + + ip_output(NULL, m); + break; + + case AF_INET6: + m->m_data += sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - + sizeof(struct ip6); + m->m_len -= sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - + sizeof(struct ip6); + ip6 = mtod(m, struct ip6 *); + ip6->ip_pl = tcpiph_save.ti_len; + ip6->ip_dst = tcpiph_save.ti_dst6; + ip6->ip_src = tcpiph_save.ti_src6; + ip6->ip_nh = tcpiph_save.ti_nh6; + + ip6_output(NULL, m, 0); + break; + + default: + g_assert_not_reached(); + } +} + +struct tcpcb *tcp_newtcpcb(struct socket *so) +{ + register struct tcpcb *tp; + + tp = g_new0(struct tcpcb, 1); + tp->seg_next = tp->seg_prev = (struct tcpiphdr *)tp; + /* + * 40: length of IPv4 header (20) + TCP header (20) + * 60: length of IPv6 header (40) + TCP header (20) + */ + tp->t_maxseg = + MIN(so->slirp->if_mtu - ((so->so_ffamily == AF_INET) ? 40 : 60), + TCP_MAXSEG_MAX); + + tp->t_flags = TCP_DO_RFC1323 ? (TF_REQ_SCALE | TF_REQ_TSTMP) : 0; + tp->t_socket = so; + + /* + * Init srtt to TCPTV_SRTTBASE (0), so we can tell that we have no + * rtt estimate. Set rttvar so that srtt + 2 * rttvar gives + * reasonable initial retransmit time. + */ + tp->t_srtt = TCPTV_SRTTBASE; + tp->t_rttvar = TCPTV_SRTTDFLT << 2; + tp->t_rttmin = TCPTV_MIN; + + TCPT_RANGESET(tp->t_rxtcur, + ((TCPTV_SRTTBASE >> 2) + (TCPTV_SRTTDFLT << 2)) >> 1, + TCPTV_MIN, TCPTV_REXMTMAX); + + tp->snd_cwnd = TCP_MAXWIN << TCP_MAX_WINSHIFT; + tp->snd_ssthresh = TCP_MAXWIN << TCP_MAX_WINSHIFT; + tp->t_state = TCPS_CLOSED; + + so->so_tcpcb = tp; + + return (tp); +} + +struct tcpcb *tcp_drop(struct tcpcb *tp, int err) +{ + DEBUG_CALL("tcp_drop"); + DEBUG_ARG("tp = %p", tp); + DEBUG_ARG("errno = %d", errno); + + if (TCPS_HAVERCVDSYN(tp->t_state)) { + tp->t_state = TCPS_CLOSED; + tcp_output(tp); + } + return (tcp_close(tp)); +} + +struct tcpcb *tcp_close(struct tcpcb *tp) +{ + register struct tcpiphdr *t; + struct socket *so = tp->t_socket; + Slirp *slirp = so->slirp; + register struct mbuf *m; + + DEBUG_CALL("tcp_close"); + DEBUG_ARG("tp = %p", tp); + + /* free the reassembly queue, if any */ + t = tcpfrag_list_first(tp); + while (!tcpfrag_list_end(t, tp)) { + t = tcpiphdr_next(t); + m = tcpiphdr_prev(t)->ti_mbuf; + slirp_remque(tcpiphdr2qlink(tcpiphdr_prev(t))); + m_free(m); + } + g_free(tp); + so->so_tcpcb = NULL; + /* clobber input socket cache if we're closing the cached connection */ + if (so == slirp->tcp_last_so) + slirp->tcp_last_so = &slirp->tcb; + so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); + closesocket(so->s); + sbfree(&so->so_rcv); + sbfree(&so->so_snd); + sofree(so); + return ((struct tcpcb *)0); +} + +/* + * TCP protocol interface to socket abstraction. + */ + +/* + * User issued close, and wish to trail through shutdown states: + * if never received SYN, just forget it. If got a SYN from peer, + * but haven't sent FIN, then go to FIN_WAIT_1 state to send peer a FIN. + * If already got a FIN from peer, then almost done; go to LAST_ACK + * state. In all other cases, have already sent FIN to peer (e.g. + * after PRU_SHUTDOWN), and just have to play tedious game waiting + * for peer to send FIN or not respond to keep-alives, etc. + * We can let the user exit from the close as soon as the FIN is acked. + */ +void tcp_sockclosed(struct tcpcb *tp) +{ + DEBUG_CALL("tcp_sockclosed"); + DEBUG_ARG("tp = %p", tp); + + if (!tp) { + return; + } + + switch (tp->t_state) { + case TCPS_CLOSED: + case TCPS_LISTEN: + case TCPS_SYN_SENT: + tp->t_state = TCPS_CLOSED; + tcp_close(tp); + return; + + case TCPS_SYN_RECEIVED: + case TCPS_ESTABLISHED: + tp->t_state = TCPS_FIN_WAIT_1; + break; + + case TCPS_CLOSE_WAIT: + tp->t_state = TCPS_LAST_ACK; + break; + } + tcp_output(tp); +} + +/* + * Only do a connect, the tcp fields will be set in tcp_input + * return 0 if there's a result of the connect, + * else return -1 means we're still connecting + * The return value is almost always -1 since the socket is + * nonblocking. Connect returns after the SYN is sent, and does + * not wait for ACK+SYN. + */ +int tcp_fconnect(struct socket *so, unsigned short af) +{ + int ret = 0; + + DEBUG_CALL("tcp_fconnect"); + DEBUG_ARG("so = %p", so); + + ret = so->s = slirp_socket(af, SOCK_STREAM, 0); + if (ret >= 0) { + ret = slirp_bind_outbound(so, af); + if (ret < 0) { + // bind failed - close socket + closesocket(so->s); + so->s = -1; + return (ret); + } + } + + if (ret >= 0) { + int opt, s = so->s; + struct sockaddr_storage addr; + + slirp_set_nonblock(s); + so->slirp->cb->register_poll_fd(s, so->slirp->opaque); + slirp_socket_set_fast_reuse(s); + opt = 1; + setsockopt(s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt)); + opt = 1; + setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); + + addr = so->fhost.ss; + DEBUG_CALL(" connect()ing"); + if (sotranslate_out(so, &addr) < 0) { + return -1; + } + + /* We don't care what port we get */ + ret = connect(s, (struct sockaddr *)&addr, sockaddr_size(&addr)); + + /* + * If it's not in progress, it failed, so we just return 0, + * without clearing SS_NOFDREF + */ + soisfconnecting(so); + } + + return (ret); +} + +/* + * We have a problem. The correct thing to do would be + * to first connect to the local-host, and only if the + * connection is accepted, then do an accept() here. + * But, a) we need to know who's trying to connect + * to the socket to be able to SYN the local-host, and + * b) we are already connected to the foreign host by + * the time it gets to accept(), so... We simply accept + * here and SYN the local-host. + */ +void tcp_connect(struct socket *inso) +{ + Slirp *slirp = inso->slirp; + struct socket *so; + struct sockaddr_storage addr; + socklen_t addrlen; + struct tcpcb *tp; + int s, opt, ret; + /* AF_INET6 addresses are bigger than AF_INET, so this is big enough. */ + char addrstr[INET6_ADDRSTRLEN]; + char portstr[6]; + + DEBUG_CALL("tcp_connect"); + DEBUG_ARG("inso = %p", inso); + switch (inso->lhost.ss.ss_family) { + case AF_INET: + addrlen = sizeof(struct sockaddr_in); + break; + case AF_INET6: + addrlen = sizeof(struct sockaddr_in6); + break; + default: + g_assert_not_reached(); + } + ret = getnameinfo((const struct sockaddr *) &inso->lhost.ss, addrlen, addrstr, sizeof(addrstr), portstr, sizeof(portstr), NI_NUMERICHOST|NI_NUMERICSERV); + g_assert(ret == 0); + DEBUG_ARG("ip = [%s]:%s", addrstr, portstr); + DEBUG_ARG("so_state = 0x%x", inso->so_state); + + /* Perform lazy guest IP address resolution if needed. */ + if (inso->so_state & SS_HOSTFWD) { + /* + * We can only reject the connection request by accepting it and + * then immediately closing it. Note that SS_FACCEPTONCE sockets can't + * get here. + */ + if (soassign_guest_addr_if_needed(inso) < 0) { + /* + * Guest address isn't available yet. We could either try to defer + * completing this connection request until the guest address is + * available, or punt. It's easier to punt. Otherwise we need to + * complicate the mechanism by which we're called to defer calling + * us again until the guest address is available. + */ + DEBUG_MISC(" guest address not available yet"); + addrlen = sizeof(addr); + s = accept(inso->s, (struct sockaddr *)&addr, &addrlen); + if (s >= 0) { + close(s); + } + return; + } + } + + /* + * If it's an SS_ACCEPTONCE socket, no need to socreate() + * another socket, just use the accept() socket. + */ + if (inso->so_state & SS_FACCEPTONCE) { + /* FACCEPTONCE already have a tcpcb */ + so = inso; + } else { + so = socreate(slirp, IPPROTO_TCP); + tcp_attach(so); + so->lhost = inso->lhost; + so->so_ffamily = inso->so_ffamily; + } + + tcp_mss(sototcpcb(so), 0); + + addrlen = sizeof(addr); + s = accept(inso->s, (struct sockaddr *)&addr, &addrlen); + if (s < 0) { + tcp_close(sototcpcb(so)); /* This will sofree() as well */ + return; + } + slirp_set_nonblock(s); + so->slirp->cb->register_poll_fd(s, so->slirp->opaque); + slirp_socket_set_fast_reuse(s); + opt = 1; + setsockopt(s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(int)); + slirp_socket_set_nodelay(s); + + so->fhost.ss = addr; + sotranslate_accept(so); + + /* Close the accept() socket, set right state */ + if (inso->so_state & SS_FACCEPTONCE) { + /* If we only accept once, close the accept() socket */ + so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); + closesocket(so->s); + + /* Don't select it yet, even though we have an FD */ + /* if it's not FACCEPTONCE, it's already NOFDREF */ + so->so_state = SS_NOFDREF; + } + so->s = s; + so->so_state |= SS_INCOMING; + + so->so_iptos = tcp_tos(so); + tp = sototcpcb(so); + + tcp_template(tp); + + tp->t_state = TCPS_SYN_SENT; + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT; + tp->iss = slirp->tcp_iss; + slirp->tcp_iss += TCP_ISSINCR / 2; + tcp_sendseqinit(tp); + tcp_output(tp); +} + +void tcp_attach(struct socket *so) +{ + so->so_tcpcb = tcp_newtcpcb(so); + slirp_insque(so, &so->slirp->tcb); +} + +/* + * Set the socket's type of service field + */ +static const struct tos_t tcptos[] = { + { 0, 20, IPTOS_THROUGHPUT, 0 }, /* ftp data */ + { 21, 21, IPTOS_LOWDELAY, EMU_FTP }, /* ftp control */ + { 0, 23, IPTOS_LOWDELAY, 0 }, /* telnet */ + { 0, 80, IPTOS_THROUGHPUT, 0 }, /* WWW */ + { 0, 513, IPTOS_LOWDELAY, EMU_RLOGIN | EMU_NOCONNECT }, /* rlogin */ + { 0, 544, IPTOS_LOWDELAY, EMU_KSH }, /* kshell */ + { 0, 543, IPTOS_LOWDELAY, 0 }, /* klogin */ + { 0, 6667, IPTOS_THROUGHPUT, EMU_IRC }, /* IRC */ + { 0, 6668, IPTOS_THROUGHPUT, EMU_IRC }, /* IRC undernet */ + { 0, 7070, IPTOS_LOWDELAY, EMU_REALAUDIO }, /* RealAudio control */ + { 0, 113, IPTOS_LOWDELAY, EMU_IDENT }, /* identd protocol */ + { 0, 0, 0, 0 } +}; + +uint8_t tcp_tos(struct socket *so) +{ + int i = 0; + + while (tcptos[i].tos) { + if ((tcptos[i].fport && (ntohs(so->so_fport) == tcptos[i].fport)) || + (tcptos[i].lport && (ntohs(so->so_lport) == tcptos[i].lport))) { + if (so->slirp->enable_emu) + so->so_emu = tcptos[i].emu; + return tcptos[i].tos; + } + i++; + } + return 0; +} + +/* + * NOTE: It's possible to crash SLiRP by sending it + * unstandard strings to emulate... if this is a problem, + * more checks are needed here + * + * XXX Assumes the whole command came in one packet + * XXX If there is more than one command in the packet, the others may + * be truncated. + * XXX If the command is too long, it may be truncated. + * + * XXX Some ftp clients will have their TOS set to + * LOWDELAY and so Nagel will kick in. Because of this, + * we'll get the first letter, followed by the rest, so + * we simply scan for ORT instead of PORT... + * DCC doesn't have this problem because there's other stuff + * in the packet before the DCC command. + * + * Return 1 if the mbuf m is still valid and should be + * sbappend()ed + * + * NOTE: if you return 0 you MUST m_free() the mbuf! + */ +int tcp_emu(struct socket *so, struct mbuf *m) +{ + Slirp *slirp = so->slirp; + unsigned n1, n2, n3, n4, n5, n6; + char buff[257]; + uint32_t laddr; + unsigned lport; + char *bptr; + + DEBUG_CALL("tcp_emu"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m = %p", m); + + switch (so->so_emu) { + int x, i; + + /* TODO: IPv6 */ + case EMU_IDENT: + /* + * Identification protocol as per rfc-1413 + */ + + { + struct socket *tmpso; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(struct sockaddr_in); + char *eol = g_strstr_len(m->m_data, m->m_len, "\r\n"); + + if (!eol) { + return 1; + } + + *eol = '\0'; + if (sscanf(m->m_data, "%u%*[ ,]%u", &n1, &n2) == 2) { + HTONS(n1); + HTONS(n2); + /* n2 is the one on our host */ + for (tmpso = slirp->tcb.so_next; tmpso != &slirp->tcb; + tmpso = tmpso->so_next) { + if (tmpso->so_laddr.s_addr == so->so_laddr.s_addr && + tmpso->so_lport == n2 && + tmpso->so_faddr.s_addr == so->so_faddr.s_addr && + tmpso->so_fport == n1) { + if (getsockname(tmpso->s, (struct sockaddr *)&addr, + &addrlen) == 0) + n2 = addr.sin_port; + break; + } + } + NTOHS(n1); + NTOHS(n2); + m_inc(m, g_snprintf(NULL, 0, "%d,%d\r\n", n1, n2) + 1); + m->m_len = slirp_fmt(m->m_data, M_ROOM(m), "%d,%d\r\n", n1, n2); + } else { + *eol = '\r'; + } + + return 1; + } + + case EMU_FTP: /* ftp */ + m_inc(m, m->m_len + 1); + *(m->m_data + m->m_len) = 0; /* NUL terminate for strstr */ + if ((bptr = (char *)strstr(m->m_data, "ORT")) != NULL) { + /* + * Need to emulate the PORT command + */ + x = sscanf(bptr, "ORT %u,%u,%u,%u,%u,%u\r\n%256[^\177]", &n1, &n2, + &n3, &n4, &n5, &n6, buff); + if (x < 6) + return 1; + + laddr = htonl((n1 << 24) | (n2 << 16) | (n3 << 8) | (n4)); + lport = htons((n5 << 8) | (n6)); + + if ((so = tcp_listen(slirp, INADDR_ANY, 0, laddr, lport, + SS_FACCEPTONCE)) == NULL) { + return 1; + } + n6 = ntohs(so->so_fport); + + n5 = (n6 >> 8) & 0xff; + n6 &= 0xff; + + laddr = ntohl(so->so_faddr.s_addr); + + n1 = ((laddr >> 24) & 0xff); + n2 = ((laddr >> 16) & 0xff); + n3 = ((laddr >> 8) & 0xff); + n4 = (laddr & 0xff); + + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += slirp_fmt(bptr, M_FREEROOM(m), + "ORT %d,%d,%d,%d,%d,%d\r\n%s", + n1, n2, n3, n4, n5, n6, x == 7 ? buff : ""); + return 1; + } else if ((bptr = (char *)strstr(m->m_data, "27 Entering")) != NULL) { + /* + * Need to emulate the PASV response + */ + x = sscanf( + bptr, + "27 Entering Passive Mode (%u,%u,%u,%u,%u,%u)\r\n%256[^\177]", + &n1, &n2, &n3, &n4, &n5, &n6, buff); + if (x < 6) + return 1; + + laddr = htonl((n1 << 24) | (n2 << 16) | (n3 << 8) | (n4)); + lport = htons((n5 << 8) | (n6)); + + if ((so = tcp_listen(slirp, INADDR_ANY, 0, laddr, lport, + SS_FACCEPTONCE)) == NULL) { + return 1; + } + n6 = ntohs(so->so_fport); + + n5 = (n6 >> 8) & 0xff; + n6 &= 0xff; + + laddr = ntohl(so->so_faddr.s_addr); + + n1 = ((laddr >> 24) & 0xff); + n2 = ((laddr >> 16) & 0xff); + n3 = ((laddr >> 8) & 0xff); + n4 = (laddr & 0xff); + + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += slirp_fmt(bptr, M_FREEROOM(m), + "27 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n%s", + n1, n2, n3, n4, n5, n6, x == 7 ? buff : ""); + return 1; + } + + return 1; + + case EMU_KSH: + /* + * The kshell (Kerberos rsh) and shell services both pass + * a local port port number to carry signals to the server + * and stderr to the client. It is passed at the beginning + * of the connection as a NUL-terminated decimal ASCII string. + */ + so->so_emu = 0; + for (lport = 0, i = 0; i < m->m_len - 1; ++i) { + if (m->m_data[i] < '0' || m->m_data[i] > '9') + return 1; /* invalid number */ + lport *= 10; + lport += m->m_data[i] - '0'; + } + if (m->m_data[m->m_len - 1] == '\0' && lport != 0 && + (so = tcp_listen(slirp, INADDR_ANY, 0, so->so_laddr.s_addr, + htons(lport), SS_FACCEPTONCE)) != NULL) + m->m_len = slirp_fmt0(m->m_data, M_ROOM(m), + "%d", ntohs(so->so_fport)); + return 1; + + case EMU_IRC: + /* + * Need to emulate DCC CHAT, DCC SEND and DCC MOVE + */ + m_inc(m, m->m_len + 1); + *(m->m_data + m->m_len) = 0; /* NULL terminate the string for strstr */ + if ((bptr = (char *)strstr(m->m_data, "DCC")) == NULL) + return 1; + + /* The %256s is for the broken mIRC */ + if (sscanf(bptr, "DCC CHAT %256s %u %u", buff, &laddr, &lport) == 3) { + if ((so = tcp_listen(slirp, INADDR_ANY, 0, htonl(laddr), + htons(lport), SS_FACCEPTONCE)) == NULL) { + return 1; + } + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += slirp_fmt(bptr, M_FREEROOM(m), + "DCC CHAT chat %lu %u%c\n", + (unsigned long)ntohl(so->so_faddr.s_addr), + ntohs(so->so_fport), 1); + } else if (sscanf(bptr, "DCC SEND %256s %u %u %u", buff, &laddr, &lport, + &n1) == 4) { + if ((so = tcp_listen(slirp, INADDR_ANY, 0, htonl(laddr), + htons(lport), SS_FACCEPTONCE)) == NULL) { + return 1; + } + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += slirp_fmt(bptr, M_FREEROOM(m), + "DCC SEND %s %lu %u %u%c\n", buff, + (unsigned long)ntohl(so->so_faddr.s_addr), + ntohs(so->so_fport), n1, 1); + } else if (sscanf(bptr, "DCC MOVE %256s %u %u %u", buff, &laddr, &lport, + &n1) == 4) { + if ((so = tcp_listen(slirp, INADDR_ANY, 0, htonl(laddr), + htons(lport), SS_FACCEPTONCE)) == NULL) { + return 1; + } + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += slirp_fmt(bptr, M_FREEROOM(m), + "DCC MOVE %s %lu %u %u%c\n", buff, + (unsigned long)ntohl(so->so_faddr.s_addr), + ntohs(so->so_fport), n1, 1); + } + return 1; + + case EMU_REALAUDIO: + /* + * RealAudio emulation - JP. We must try to parse the incoming + * data and try to find the two characters that contain the + * port number. Then we redirect an udp port and replace the + * number with the real port we got. + * + * The 1.0 beta versions of the player are not supported + * any more. + * + * A typical packet for player version 1.0 (release version): + * + * 0000:50 4E 41 00 05 + * 0000:00 01 00 02 1B D7 00 00 67 E6 6C DC 63 00 12 50 ........g.l.c..P + * 0010:4E 43 4C 49 45 4E 54 20 31 30 31 20 41 4C 50 48 NCLIENT 101 ALPH + * 0020:41 6C 00 00 52 00 17 72 61 66 69 6C 65 73 2F 76 Al..R..rafiles/v + * 0030:6F 61 2F 65 6E 67 6C 69 73 68 5F 2E 72 61 79 42 oa/english_.rayB + * + * Now the port number 0x1BD7 is found at offset 0x04 of the + * Now the port number 0x1BD7 is found at offset 0x04 of the + * second packet. This time we received five bytes first and + * then the rest. You never know how many bytes you get. + * + * A typical packet for player version 2.0 (beta): + * + * 0000:50 4E 41 00 06 00 02 00 00 00 01 00 02 1B C1 00 PNA............. + * 0010:00 67 75 78 F5 63 00 0A 57 69 6E 32 2E 30 2E 30 .gux.c..Win2.0.0 + * 0020:2E 35 6C 00 00 52 00 1C 72 61 66 69 6C 65 73 2F .5l..R..rafiles/ + * 0030:77 65 62 73 69 74 65 2F 32 30 72 65 6C 65 61 73 website/20releas + * 0040:65 2E 72 61 79 53 00 00 06 36 42 e.rayS...6B + * + * Port number 0x1BC1 is found at offset 0x0d. + * + * This is just a horrible switch statement. Variable ra tells + * us where we're going. + */ + + bptr = m->m_data; + while (bptr < m->m_data + m->m_len) { + uint16_t p; + static int ra = 0; + char ra_tbl[4]; + + ra_tbl[0] = 0x50; + ra_tbl[1] = 0x4e; + ra_tbl[2] = 0x41; + ra_tbl[3] = 0; + + switch (ra) { + case 0: + case 2: + case 3: + if (*bptr++ != ra_tbl[ra]) { + ra = 0; + continue; + } + break; + + case 1: + /* + * We may get 0x50 several times, ignore them + */ + if (*bptr == 0x50) { + ra = 1; + bptr++; + continue; + } else if (*bptr++ != ra_tbl[ra]) { + ra = 0; + continue; + } + break; + + case 4: + /* + * skip version number + */ + bptr++; + break; + + case 5: + if (bptr == m->m_data + m->m_len - 1) + return 1; /* We need two bytes */ + + /* + * The difference between versions 1.0 and + * 2.0 is here. For future versions of + * the player this may need to be modified. + */ + if (*(bptr + 1) == 0x02) + bptr += 8; + else + bptr += 4; + break; + + case 6: + /* This is the field containing the port + * number that RA-player is listening to. + */ + + if (bptr == m->m_data + m->m_len - 1) + return 1; /* We need two bytes */ + + lport = (((uint8_t *)bptr)[0] << 8) + ((uint8_t *)bptr)[1]; + if (lport < 6970) + lport += 256; /* don't know why */ + if (lport < 6970 || lport > 7170) + return 1; /* failed */ + + /* try to get udp port between 6970 - 7170 */ + for (p = 6970; p < 7071; p++) { + if (udp_listen(slirp, INADDR_ANY, htons(p), + so->so_laddr.s_addr, htons(lport), + SS_FACCEPTONCE)) { + break; + } + } + if (p == 7071) + p = 0; + *(uint8_t *)bptr++ = (p >> 8) & 0xff; + *(uint8_t *)bptr = p & 0xff; + ra = 0; + return 1; /* port redirected, we're done */ + break; + + default: + ra = 0; + } + ra++; + } + return 1; + + default: + /* Ooops, not emulated, won't call tcp_emu again */ + so->so_emu = 0; + return 1; + } +} + +/* + * Do misc. config of SLiRP while its running. + * Return 0 if this connections is to be closed, 1 otherwise, + * return 2 if this is a command-line connection + */ +int tcp_ctl(struct socket *so) +{ + Slirp *slirp = so->slirp; + struct sbuf *sb = &so->so_snd; + struct gfwd_list *ex_ptr; + + DEBUG_CALL("tcp_ctl"); + DEBUG_ARG("so = %p", so); + + /* TODO: IPv6 */ + if (so->so_faddr.s_addr != slirp->vhost_addr.s_addr) { + /* Check if it's pty_exec */ + for (ex_ptr = slirp->guestfwd_list; ex_ptr; ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->ex_fport == so->so_fport && + so->so_faddr.s_addr == ex_ptr->ex_addr.s_addr) { + if (ex_ptr->write_cb) { + so->s = -1; + so->guestfwd = ex_ptr; + return 1; + } + DEBUG_MISC(" executing %s", ex_ptr->ex_exec); + if (ex_ptr->ex_unix) + return open_unix(so, ex_ptr->ex_unix); + else + return fork_exec(so, ex_ptr->ex_exec); + } + } + } + sb->sb_cc = slirp_fmt(sb->sb_wptr, sb->sb_datalen - (sb->sb_wptr - sb->sb_data), + "Error: No application configured.\r\n"); + sb->sb_wptr += sb->sb_cc; + return 0; +} diff --git a/app/src/main/cpp/libslirp/src/tcp_timer.c b/app/src/main/cpp/libslirp/src/tcp_timer.c new file mode 100644 index 00000000..aeb610fb --- /dev/null +++ b/app/src/main/cpp/libslirp/src/tcp_timer.c @@ -0,0 +1,283 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_timer.c 8.1 (Berkeley) 6/10/93 + * tcp_timer.c,v 1.2 1994/08/02 07:49:10 davidg Exp + */ + +#include "slirp.h" + +static struct tcpcb *tcp_timers(register struct tcpcb *tp, int timer); + +/* + * Fast timeout routine for processing delayed acks + */ +void tcp_fasttimo(Slirp *slirp) +{ + register struct socket *so; + register struct tcpcb *tp; + + DEBUG_CALL("tcp_fasttimo"); + + so = slirp->tcb.so_next; + if (so) + for (; so != &slirp->tcb; so = so->so_next) + if ((tp = (struct tcpcb *)so->so_tcpcb) && + (tp->t_flags & TF_DELACK)) { + tp->t_flags &= ~TF_DELACK; + tp->t_flags |= TF_ACKNOW; + tcp_output(tp); + } +} + +/* + * Tcp protocol timeout routine called every 500 ms. + * Updates the timers in all active tcb's and + * causes finite state machine actions if timers expire. + */ +void tcp_slowtimo(Slirp *slirp) +{ + register struct socket *ip, *ipnxt; + register struct tcpcb *tp; + register int i; + + DEBUG_CALL("tcp_slowtimo"); + + /* + * Search through tcb's and update active timers. + */ + ip = slirp->tcb.so_next; + if (ip == NULL) { + return; + } + for (; ip != &slirp->tcb; ip = ipnxt) { + ipnxt = ip->so_next; + tp = sototcpcb(ip); + if (tp == NULL) { + continue; + } + for (i = 0; i < TCPT_NTIMERS; i++) { + if (tp->t_timer[i] && --tp->t_timer[i] == 0) { + tcp_timers(tp, i); + if (ipnxt->so_prev != ip) + goto tpgone; + } + } + tp->t_idle++; + if (tp->t_rtt) + tp->t_rtt++; + tpgone:; + } + slirp->tcp_iss += TCP_ISSINCR / PR_SLOWHZ; /* increment iss */ + slirp->tcp_now++; /* for timestamps */ +} + +void tcp_canceltimers(struct tcpcb *tp) +{ + register int i; + + for (i = 0; i < TCPT_NTIMERS; i++) + tp->t_timer[i] = 0; +} + +const int tcp_backoff[TCP_MAXRXTSHIFT + 1] = { 1, 2, 4, 8, 16, 32, 64, + 64, 64, 64, 64, 64, 64 }; + +/* + * TCP timer processing. + */ +static struct tcpcb *tcp_timers(register struct tcpcb *tp, int timer) +{ + register int rexmt; + + DEBUG_CALL("tcp_timers"); + + switch (timer) { + /* + * 2 MSL timeout in shutdown went off. If we're closed but + * still waiting for peer to close and connection has been idle + * too long, or if 2MSL time is up from TIME_WAIT, delete connection + * control block. Otherwise, check again in a bit. + */ + case TCPT_2MSL: + if (tp->t_state != TCPS_TIME_WAIT && tp->t_idle <= TCP_MAXIDLE) + tp->t_timer[TCPT_2MSL] = TCPTV_KEEPINTVL; + else + tp = tcp_close(tp); + break; + + /* + * Retransmission timer went off. Message has not + * been acked within retransmit interval. Back off + * to a longer retransmit interval and retransmit one segment. + */ + case TCPT_REXMT: + + /* + * XXXXX If a packet has timed out, then remove all the queued + * packets for that session. + */ + + if (++tp->t_rxtshift > TCP_MAXRXTSHIFT) { + /* + * This is a hack to suit our terminal server here at the uni of + * canberra since they have trouble with zeroes... It usually lets + * them through unharmed, but under some conditions, it'll eat the + * zeros. If we keep retransmitting it, it'll keep eating the + * zeroes, so we keep retransmitting, and eventually the connection + * dies... (this only happens on incoming data) + * + * So, if we were gonna drop the connection from too many + * retransmits, don't... instead halve the t_maxseg, which might + * break up the NULLs and let them through + * + * *sigh* + */ + + tp->t_maxseg >>= 1; + if (tp->t_maxseg < 32) { + /* + * We tried our best, now the connection must die! + */ + tp->t_rxtshift = TCP_MAXRXTSHIFT; + tp = tcp_drop(tp, tp->t_softerror); + /* tp->t_softerror : ETIMEDOUT); */ /* XXX */ + return (tp); /* XXX */ + } + + /* + * Set rxtshift to 6, which is still at the maximum + * backoff time + */ + tp->t_rxtshift = 6; + } + rexmt = TCP_REXMTVAL(tp) * tcp_backoff[tp->t_rxtshift]; + TCPT_RANGESET(tp->t_rxtcur, rexmt, (short)tp->t_rttmin, + TCPTV_REXMTMAX); /* XXX */ + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + /* + * If losing, let the lower level know and try for + * a better route. Also, if we backed off this far, + * our srtt estimate is probably bogus. Clobber it + * so we'll take the next rtt measurement as our srtt; + * move the current srtt into rttvar to keep the current + * retransmit times until then. + */ + if (tp->t_rxtshift > TCP_MAXRXTSHIFT / 4) { + tp->t_rttvar += (tp->t_srtt >> TCP_RTT_SHIFT); + tp->t_srtt = 0; + } + tp->snd_nxt = tp->snd_una; + /* + * If timing a segment in this window, stop the timer. + */ + tp->t_rtt = 0; + /* + * Close the congestion window down to one segment + * (we'll open it by one segment for each ack we get). + * Since we probably have a window's worth of unacked + * data accumulated, this "slow start" keeps us from + * dumping all that data as back-to-back packets (which + * might overwhelm an intermediate gateway). + * + * There are two phases to the opening: Initially we + * open by one mss on each ack. This makes the window + * size increase exponentially with time. If the + * window is larger than the path can handle, this + * exponential growth results in dropped packet(s) + * almost immediately. To get more time between + * drops but still "push" the network to take advantage + * of improving conditions, we switch from exponential + * to linear window opening at some threshold size. + * For a threshold, we use half the current window + * size, truncated to a multiple of the mss. + * + * (the minimum cwnd that will give us exponential + * growth is 2 mss. We don't allow the threshold + * to go below this.) + */ + { + unsigned win = MIN(tp->snd_wnd, tp->snd_cwnd) / 2 / tp->t_maxseg; + if (win < 2) + win = 2; + tp->snd_cwnd = tp->t_maxseg; + tp->snd_ssthresh = win * tp->t_maxseg; + tp->t_dupacks = 0; + } + tcp_output(tp); + break; + + /* + * Persistence timer into zero window. + * Force a byte to be output, if possible. + */ + case TCPT_PERSIST: + tcp_setpersist(tp); + tp->t_force = 1; + tcp_output(tp); + tp->t_force = 0; + break; + + /* + * Keep-alive timer went off; send something + * or drop connection if idle for too long. + */ + case TCPT_KEEP: + if (tp->t_state < TCPS_ESTABLISHED) + goto dropit; + + if (slirp_do_keepalive && tp->t_state <= TCPS_CLOSE_WAIT) { + if (tp->t_idle >= TCPTV_KEEP_IDLE + TCP_MAXIDLE) + goto dropit; + /* + * Send a packet designed to force a response + * if the peer is up and reachable: + * either an ACK if the connection is still alive, + * or an RST if the peer has closed the connection + * due to timeout or reboot. + * Using sequence number tp->snd_una-1 + * causes the transmitted zero-length segment + * to lie outside the receive window; + * by the protocol spec, this requires the + * correspondent TCP to respond. + */ + tcp_respond(tp, &tp->t_template, (struct mbuf *)NULL, tp->rcv_nxt, + tp->snd_una - 1, 0, tp->t_socket->so_ffamily); + tp->t_timer[TCPT_KEEP] = TCPTV_KEEPINTVL; + } else + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_IDLE; + break; + + dropit: + tp = tcp_drop(tp, 0); + break; + } + + return (tp); +} diff --git a/app/src/main/cpp/libslirp/src/tcp_timer.h b/app/src/main/cpp/libslirp/src/tcp_timer.h new file mode 100644 index 00000000..3a2e9448 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/tcp_timer.h @@ -0,0 +1,133 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_timer.h 8.1 (Berkeley) 6/10/93 + * tcp_timer.h,v 1.4 1994/08/21 05:27:38 paul Exp + */ + +#ifndef TCP_TIMER_H +#define TCP_TIMER_H + +/* + * Definitions of the TCP timers. These timers are counted + * down PR_SLOWHZ times a second. + */ +#define TCPT_NTIMERS 4 + +#define TCPT_REXMT 0 /* retransmit */ +#define TCPT_PERSIST 1 /* retransmit persistence */ +#define TCPT_KEEP 2 /* keep alive */ +#define TCPT_2MSL 3 /* 2*msl quiet time timer */ + +/* + * The TCPT_REXMT timer is used to force retransmissions. + * The TCP has the TCPT_REXMT timer set whenever segments + * have been sent for which ACKs are expected but not yet + * received. If an ACK is received which advances tp->snd_una, + * then the retransmit timer is cleared (if there are no more + * outstanding segments) or reset to the base value (if there + * are more ACKs expected). Whenever the retransmit timer goes off, + * we retransmit one unacknowledged segment, and do a backoff + * on the retransmit timer. + * + * The TCPT_PERSIST timer is used to keep window size information + * flowing even if the window goes shut. If all previous transmissions + * have been acknowledged (so that there are no retransmissions in progress), + * and the window is too small to bother sending anything, then we start + * the TCPT_PERSIST timer. When it expires, if the window is nonzero, + * we go to transmit state. Otherwise, at intervals send a single byte + * into the peer's window to force him to update our window information. + * We do this at most as often as TCPT_PERSMIN time intervals, + * but no more frequently than the current estimate of round-trip + * packet time. The TCPT_PERSIST timer is cleared whenever we receive + * a window update from the peer. + * + * The TCPT_KEEP timer is used to keep connections alive. If an + * connection is idle (no segments received) for TCPTV_KEEP_INIT amount of time, + * but not yet established, then we drop the connection. Once the connection + * is established, if the connection is idle for TCPTV_KEEP_IDLE time + * (and keepalives have been enabled on the socket), we begin to probe + * the connection. We force the peer to send us a segment by sending: + * + * This segment is (deliberately) outside the window, and should elicit + * an ack segment in response from the peer. If, despite the TCPT_KEEP + * initiated segments we cannot elicit a response from a peer in TCPT_MAXIDLE + * amount of time probing, then we drop the connection. + */ + +/* + * Time constants. + */ +#define TCPTV_MSL (5 * PR_SLOWHZ) /* max seg lifetime (hah!) */ + +#define TCPTV_SRTTBASE \ + 0 /* base roundtrip time; \ + if 0, no idea yet */ +#define TCPTV_SRTTDFLT (3 * PR_SLOWHZ) /* assumed RTT if no info */ + +#define TCPTV_PERSMIN (5 * PR_SLOWHZ) /* retransmit persistence */ +#define TCPTV_PERSMAX (60 * PR_SLOWHZ) /* maximum persist interval */ + +#define TCPTV_KEEP_INIT (75 * PR_SLOWHZ) /* initial connect keep alive */ +#define TCPTV_KEEP_IDLE (120 * 60 * PR_SLOWHZ) /* dflt time before probing */ +#define TCPTV_KEEPINTVL (75 * PR_SLOWHZ) /* default probe interval */ +#define TCPTV_KEEPCNT 8 /* max probes before drop */ + +#define TCPTV_MIN (1 * PR_SLOWHZ) /* minimum allowable value */ +#define TCPTV_REXMTMAX (12 * PR_SLOWHZ) /* max allowable REXMT value */ + +#define TCP_LINGERTIME 120 /* linger at most 2 minutes */ + +#define TCP_MAXRXTSHIFT 12 /* maximum retransmits */ + + +/* + * Force a time value to be in a certain range. + */ +#define TCPT_RANGESET(tv, value, tvmin, tvmax) \ + { \ + (tv) = (value); \ + if ((tv) < (tvmin)) \ + (tv) = (tvmin); \ + else if ((tv) > (tvmax)) \ + (tv) = (tvmax); \ + } + +extern const int tcp_backoff[]; + +struct tcpcb; + +/* Process fast time-outs */ +void tcp_fasttimo(Slirp *); +/* Process slow time-outs */ +void tcp_slowtimo(Slirp *); +/* Cancel all timers for TCP tp */ +void tcp_canceltimers(struct tcpcb *); + +#endif diff --git a/app/src/main/cpp/libslirp/src/tcp_var.h b/app/src/main/cpp/libslirp/src/tcp_var.h new file mode 100644 index 00000000..c8da8cbd --- /dev/null +++ b/app/src/main/cpp/libslirp/src/tcp_var.h @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_var.h 8.3 (Berkeley) 4/10/94 + * tcp_var.h,v 1.3 1994/08/21 05:27:39 paul Exp + */ + +#ifndef TCP_VAR_H +#define TCP_VAR_H + +#include "tcpip.h" +#include "tcp_timer.h" + +/* + * Tcp control block, one per tcp; fields: + */ +struct tcpcb { + struct tcpiphdr *seg_next; /* sequencing queue */ + struct tcpiphdr *seg_prev; + short t_state; /* state of this connection */ + short t_timer[TCPT_NTIMERS]; /* tcp timers */ + short t_rxtshift; /* log(2) of rexmt exp. backoff */ + short t_rxtcur; /* current retransmit value */ + short t_dupacks; /* consecutive dup acks recd */ + uint16_t t_maxseg; /* maximum segment size */ + uint8_t t_force; /* 1 if forcing out a byte */ + uint16_t t_flags; +#define TF_ACKNOW 0x0001 /* ack peer immediately */ +#define TF_DELACK 0x0002 /* ack, but try to delay it */ +#define TF_NODELAY 0x0004 /* don't delay packets to coalesce */ +#define TF_NOOPT 0x0008 /* don't use tcp options */ +#define TF_SENTFIN 0x0010 /* have sent FIN */ +#define TF_REQ_SCALE 0x0020 /* have/will request window scaling */ +#define TF_RCVD_SCALE 0x0040 /* other side has requested scaling */ +#define TF_REQ_TSTMP 0x0080 /* have/will request timestamps */ +#define TF_RCVD_TSTMP 0x0100 /* a timestamp was received in SYN */ +#define TF_SACK_PERMIT 0x0200 /* other side said I could SACK */ + + struct tcpiphdr t_template; /* static skeletal packet for xmit */ + + struct socket *t_socket; /* back pointer to socket */ + /* + * The following fields are used as in the protocol specification. + * See RFC783, Dec. 1981, page 21. + */ + /* send sequence variables */ + tcp_seq snd_una; /* send unacknowledged */ + tcp_seq snd_nxt; /* send next */ + tcp_seq snd_up; /* send urgent pointer */ + tcp_seq snd_wl1; /* window update seg seq number */ + tcp_seq snd_wl2; /* window update seg ack number */ + tcp_seq iss; /* initial send sequence number */ + uint32_t snd_wnd; /* send window */ + /* receive sequence variables */ + uint32_t rcv_wnd; /* receive window */ + tcp_seq rcv_nxt; /* receive next */ + tcp_seq rcv_up; /* receive urgent pointer */ + tcp_seq irs; /* initial receive sequence number */ + /* + * Additional variables for this implementation. + */ + /* receive variables */ + tcp_seq rcv_adv; /* advertised window */ + /* retransmit variables */ + tcp_seq snd_max; /* highest sequence number sent; + * used to recognize retransmits + */ + /* congestion control (for slow start, source quench, retransmit after loss) + */ + uint32_t snd_cwnd; /* congestion-controlled window */ + uint32_t snd_ssthresh; /* snd_cwnd size threshold for + * for slow start exponential to + * linear switch + */ + /* + * transmit timing stuff. See below for scale of srtt and rttvar. + * "Variance" is actually smoothed difference. + */ + short t_idle; /* inactivity time */ + short t_rtt; /* round trip time */ + tcp_seq t_rtseq; /* sequence number being timed */ + short t_srtt; /* smoothed round-trip time */ + short t_rttvar; /* variance in round-trip time */ + uint16_t t_rttmin; /* minimum rtt allowed */ + uint32_t max_sndwnd; /* largest window peer has offered */ + + /* out-of-band data */ + uint8_t t_oobflags; /* have some */ + uint8_t t_iobc; /* input character */ +#define TCPOOB_HAVEDATA 0x01 +#define TCPOOB_HADDATA 0x02 + short t_softerror; /* possible error not yet reported */ + + /* RFC 1323 variables */ + uint8_t snd_scale; /* window scaling for send window */ + uint8_t rcv_scale; /* window scaling for recv window */ + uint8_t request_r_scale; /* pending window scaling */ + uint8_t requested_s_scale; + uint32_t ts_recent; /* timestamp echo data */ + uint32_t ts_recent_age; /* when last updated */ + tcp_seq last_ack_sent; +}; + +#define sototcpcb(so) ((so)->so_tcpcb) + +/* + * The smoothed round-trip time and estimated variance + * are stored as fixed point numbers scaled by the values below. + * For convenience, these scales are also used in smoothing the average + * (smoothed = (1/scale)sample + ((scale-1)/scale)smoothed). + * With these scales, srtt has 3 bits to the right of the binary point, + * and thus an "ALPHA" of 0.875. rttvar has 2 bits to the right of the + * binary point, and is smoothed with an ALPHA of 0.75. + */ +#define TCP_RTT_SCALE 8 /* multiplier for srtt; 3 bits frac. */ +#define TCP_RTT_SHIFT 3 /* shift for srtt; 3 bits frac. */ +#define TCP_RTTVAR_SCALE 4 /* multiplier for rttvar; 2 bits */ +#define TCP_RTTVAR_SHIFT 2 /* multiplier for rttvar; 2 bits */ + +/* + * The initial retransmission should happen at rtt + 4 * rttvar. + * Because of the way we do the smoothing, srtt and rttvar + * will each average +1/2 tick of bias. When we compute + * the retransmit timer, we want 1/2 tick of rounding and + * 1 extra tick because of +-1/2 tick uncertainty in the + * firing of the timer. The bias will give us exactly the + * 1.5 tick we need. But, because the bias is + * statistical, we have to test that we don't drop below + * the minimum feasible timer (which is 2 ticks). + * This macro assumes that the value of TCP_RTTVAR_SCALE + * is the same as the multiplier for rttvar. + */ +#define TCP_REXMTVAL(tp) (((tp)->t_srtt >> TCP_RTT_SHIFT) + (tp)->t_rttvar) + +#endif diff --git a/app/src/main/cpp/libslirp/src/tcpip.h b/app/src/main/cpp/libslirp/src/tcpip.h new file mode 100644 index 00000000..e9c794bd --- /dev/null +++ b/app/src/main/cpp/libslirp/src/tcpip.h @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcpip.h 8.1 (Berkeley) 6/10/93 + * tcpip.h,v 1.3 1994/08/21 05:27:40 paul Exp + */ + +#ifndef TCPIP_H +#define TCPIP_H + +/* + * Tcp+ip header, after ip options removed. + */ +struct tcpiphdr { + struct mbuf_ptr ih_mbuf; /* backpointer to mbuf */ + union { + struct { + struct in_addr ih_src; /* source internet address */ + struct in_addr ih_dst; /* destination internet address */ + uint8_t ih_x1; /* (unused) */ + uint8_t ih_pr; /* protocol */ + } ti_i4; + struct { + struct in6_addr ih_src; + struct in6_addr ih_dst; + uint8_t ih_x1; + uint8_t ih_nh; + } ti_i6; + } ti; + uint16_t ti_x0; + uint16_t ti_len; /* protocol length */ + struct tcphdr ti_t; /* tcp header */ +}; +#define ti_mbuf ih_mbuf.mptr +#define ti_pr ti.ti_i4.ih_pr +#define ti_src ti.ti_i4.ih_src +#define ti_dst ti.ti_i4.ih_dst +#define ti_src6 ti.ti_i6.ih_src +#define ti_dst6 ti.ti_i6.ih_dst +#define ti_nh6 ti.ti_i6.ih_nh +#define ti_sport ti_t.th_sport +#define ti_dport ti_t.th_dport +#define ti_seq ti_t.th_seq +#define ti_ack ti_t.th_ack +#define ti_x2 ti_t.th_x2 +#define ti_off ti_t.th_off +#define ti_flags ti_t.th_flags +#define ti_win ti_t.th_win +#define ti_sum ti_t.th_sum +#define ti_urp ti_t.th_urp + +#define tcpiphdr2qlink(T) \ + ((struct qlink *)(((char *)(T)) - sizeof(struct qlink))) +#define qlink2tcpiphdr(Q) \ + ((struct tcpiphdr *)(((char *)(Q)) + sizeof(struct qlink))) +#define tcpiphdr_next(T) qlink2tcpiphdr(tcpiphdr2qlink(T)->next) +#define tcpiphdr_prev(T) qlink2tcpiphdr(tcpiphdr2qlink(T)->prev) +#define tcpfrag_list_first(T) qlink2tcpiphdr((T)->seg_next) +#define tcpfrag_list_end(F, T) (tcpiphdr2qlink(F) == (struct qlink *)(T)) +#define tcpfrag_list_empty(T) ((T)->seg_next == (struct tcpiphdr *)(T)) + +/* This is the difference between the size of a tcpiphdr structure, and the + * size of actual ip+tcp headers, rounded up since we need to align data. */ +#define TCPIPHDR_DELTA \ + (MAX(0, ((int) sizeof(struct qlink) + \ + (int) sizeof(struct tcpiphdr) - (int) sizeof(struct ip) - \ + (int) sizeof(struct tcphdr) + 7) & \ + ~7)) + +/* + * Just a clean way to get to the first byte + * of the packet + */ +struct tcpiphdr_2 { + struct tcpiphdr dummy; + char first_char; +}; + +#endif diff --git a/app/src/main/cpp/libslirp/src/tftp.c b/app/src/main/cpp/libslirp/src/tftp.c new file mode 100644 index 00000000..1b396924 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/tftp.c @@ -0,0 +1,484 @@ +/* SPDX-License-Identifier: MIT */ +/* + * tftp.c - a simple, read-only tftp server for qemu + * + * Copyright (c) 2004 Magnus Damm + * + * 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 "slirp.h" + +#include +#include +#include + +static inline int tftp_session_in_use(struct tftp_session *spt) +{ + return (spt->slirp != NULL); +} + +static inline void tftp_session_update(struct tftp_session *spt) +{ + spt->timestamp = curtime; +} + +static void tftp_session_terminate(struct tftp_session *spt) +{ + if (spt->fd >= 0) { + close(spt->fd); + spt->fd = -1; + } + g_free(spt->filename); + spt->slirp = NULL; +} + +static int tftp_session_allocate(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftphdr *hdr) +{ + struct tftp_session *spt; + int k; + + for (k = 0; k < TFTP_SESSIONS_MAX; k++) { + spt = &slirp->tftp_sessions[k]; + + if (!tftp_session_in_use(spt)) + goto found; + + /* sessions time out after 5 inactive seconds */ + if ((int)(curtime - spt->timestamp) > 5000) { + tftp_session_terminate(spt); + goto found; + } + } + + return -1; + +found: + memset(spt, 0, sizeof(*spt)); + memcpy(&spt->client_addr, srcsas, sockaddr_size(srcsas)); + spt->fd = -1; + spt->block_size = 512; + spt->client_port = hdr->udp.uh_sport; + spt->slirp = slirp; + + tftp_session_update(spt); + + return k; +} + +static int tftp_session_find(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftphdr *hdr) +{ + struct tftp_session *spt; + int k; + + for (k = 0; k < TFTP_SESSIONS_MAX; k++) { + spt = &slirp->tftp_sessions[k]; + + if (tftp_session_in_use(spt)) { + if (sockaddr_equal(&spt->client_addr, srcsas)) { + if (spt->client_port == hdr->udp.uh_sport) { + return k; + } + } + } + } + + return -1; +} + +void tftp_cleanup(Slirp *slirp) +{ + struct tftp_session *spt; + int k; + + for (k = 0; k < TFTP_SESSIONS_MAX; k++) { + spt = &slirp->tftp_sessions[k]; + + if (tftp_session_in_use(spt)) { + tftp_session_terminate(spt); + } + } +} + +static int tftp_read_data(struct tftp_session *spt, uint32_t block_nr, + uint8_t *buf, int len) +{ + int bytes_read = 0; + + if (spt->fd < 0) { + spt->fd = open(spt->filename, O_RDONLY | O_BINARY); + } + + if (spt->fd < 0) { + return -1; + } + + if (len) { + if (lseek(spt->fd, block_nr * spt->block_size, SEEK_SET) == (off_t)-1) { + return -1; + } + + bytes_read = read(spt->fd, buf, len); + } + + return bytes_read; +} + +static struct tftp_t *tftp_prep_mbuf_data(struct tftp_session *spt, + struct mbuf *m) +{ + struct tftp_t *tp; + + memset(m->m_data, 0, m->m_size); + + m->m_data += IF_MAXLINKHDR; + if (spt->client_addr.ss_family == AF_INET6) { + m->m_data += sizeof(struct ip6); + } else { + m->m_data += sizeof(struct ip); + } + tp = (void *)m->m_data; + m->m_data += sizeof(struct udphdr); + + return tp; +} + +static void tftp_udp_output(struct tftp_session *spt, struct mbuf *m, + struct tftphdr *hdr) +{ + if (spt->client_addr.ss_family == AF_INET6) { + struct sockaddr_in6 sa6, da6; + + sa6.sin6_addr = spt->slirp->vhost_addr6; + sa6.sin6_port = hdr->udp.uh_dport; + da6.sin6_addr = ((struct sockaddr_in6 *)&spt->client_addr)->sin6_addr; + da6.sin6_port = spt->client_port; + + udp6_output(NULL, m, &sa6, &da6); + } else { + struct sockaddr_in sa4, da4; + + sa4.sin_addr = spt->slirp->vhost_addr; + sa4.sin_port = hdr->udp.uh_dport; + da4.sin_addr = ((struct sockaddr_in *)&spt->client_addr)->sin_addr; + da4.sin_port = spt->client_port; + + udp_output(NULL, m, &sa4, &da4, IPTOS_LOWDELAY); + } +} + +static int tftp_send_oack(struct tftp_session *spt, const char *keys[], + uint32_t values[], int nb, struct tftp_t *recv_tp) +{ + struct mbuf *m; + struct tftp_t *tp; + int i, n = 0; + + m = m_get(spt->slirp); + + if (!m) + return -1; + + tp = tftp_prep_mbuf_data(spt, m); + + tp->hdr.tp_op = htons(TFTP_OACK); + for (i = 0; i < nb; i++) { + n += slirp_fmt0(tp->x.tp_buf + n, sizeof(tp->x.tp_buf) - n, "%s", keys[i]); + n += slirp_fmt0(tp->x.tp_buf + n, sizeof(tp->x.tp_buf) - n, "%u", values[i]); + } + + m->m_len = G_SIZEOF_MEMBER(struct tftp_t, hdr.tp_op) + n; + tftp_udp_output(spt, m, &recv_tp->hdr); + + return 0; +} + +static void tftp_send_error(struct tftp_session *spt, uint16_t errorcode, + const char *msg, struct tftp_t *recv_tp) +{ + struct mbuf *m; + struct tftp_t *tp; + + DEBUG_TFTP("tftp error msg: %s", msg); + + m = m_get(spt->slirp); + + if (!m) { + goto out; + } + + tp = tftp_prep_mbuf_data(spt, m); + + tp->hdr.tp_op = htons(TFTP_ERROR); + tp->x.tp_error.tp_error_code = htons(errorcode); + slirp_pstrcpy((char *)tp->x.tp_error.tp_msg, sizeof(tp->x.tp_error.tp_msg), + msg); + + m->m_len = sizeof(struct tftp_t) - (TFTP_BLOCKSIZE_MAX + 2) + 3 + + strlen(msg) - sizeof(struct udphdr); + tftp_udp_output(spt, m, &recv_tp->hdr); + +out: + tftp_session_terminate(spt); +} + +static void tftp_send_next_block(struct tftp_session *spt, + struct tftphdr *hdr) +{ + struct mbuf *m; + struct tftp_t *tp; + int nobytes; + + m = m_get(spt->slirp); + + if (!m) { + return; + } + + tp = tftp_prep_mbuf_data(spt, m); + + tp->hdr.tp_op = htons(TFTP_DATA); + tp->x.tp_data.tp_block_nr = htons((spt->block_nr + 1) & 0xffff); + + nobytes = tftp_read_data(spt, spt->block_nr, tp->x.tp_data.tp_buf, + spt->block_size); + + if (nobytes < 0) { + /* send "file not found" error back */ + + tftp_send_error(spt, 1, "File not found", tp); + + m_free(m); + + return; + } + + m->m_len = sizeof(struct tftp_t) - (TFTP_BLOCKSIZE_MAX - nobytes) - + sizeof(struct udphdr); + tftp_udp_output(spt, m, hdr); + + if (nobytes == spt->block_size) { + tftp_session_update(spt); + } else { + tftp_session_terminate(spt); + } + + spt->block_nr++; +} + +static void tftp_handle_rrq(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftp_t *tp, int pktlen) +{ + struct tftp_session *spt; + int s, k; + size_t prefix_len; + char *req_fname; + const char *option_name[2]; + uint32_t option_value[2]; + int nb_options = 0; + + /* check if a session already exists and if so terminate it */ + s = tftp_session_find(slirp, srcsas, &tp->hdr); + if (s >= 0) { + tftp_session_terminate(&slirp->tftp_sessions[s]); + } + + s = tftp_session_allocate(slirp, srcsas, &tp->hdr); + + if (s < 0) { + return; + } + + spt = &slirp->tftp_sessions[s]; + + /* unspecified prefix means service disabled */ + if (!slirp->tftp_prefix) { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + + /* skip header fields */ + k = 0; + pktlen -= offsetof(struct tftp_t, x.tp_buf); + + /* prepend tftp_prefix */ + prefix_len = strlen(slirp->tftp_prefix); + spt->filename = g_malloc(prefix_len + TFTP_FILENAME_MAX + 2); + memcpy(spt->filename, slirp->tftp_prefix, prefix_len); + spt->filename[prefix_len] = '/'; + + /* get name */ + req_fname = spt->filename + prefix_len + 1; + + while (1) { + if (k >= TFTP_FILENAME_MAX || k >= pktlen) { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + req_fname[k] = tp->x.tp_buf[k]; + if (req_fname[k++] == '\0') { + break; + } + } + + DEBUG_TFTP("tftp rrq file: %s", req_fname); + + /* check mode */ + if ((pktlen - k) < 6) { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + + if (g_ascii_strcasecmp(&tp->x.tp_buf[k], "octet") != 0) { + tftp_send_error(spt, 4, "Unsupported transfer mode", tp); + return; + } + + k += 6; /* skipping octet */ + + /* do sanity checks on the filename */ + if ( +#ifdef G_OS_WIN32 + strstr(req_fname, "..\\") || + req_fname[strlen(req_fname) - 1] == '\\' || +#endif + strstr(req_fname, "../") || + req_fname[strlen(req_fname) - 1] == '/') { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + + /* check if the file exists */ + if (tftp_read_data(spt, 0, NULL, 0) < 0) { + tftp_send_error(spt, 1, "File not found", tp); + return; + } + + if (tp->x.tp_buf[pktlen - 1] != 0) { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + + while (k < pktlen && nb_options < G_N_ELEMENTS(option_name)) { + const char *key, *value; + + key = &tp->x.tp_buf[k]; + k += strlen(key) + 1; + + if (k >= pktlen) { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + + value = &tp->x.tp_buf[k]; + k += strlen(value) + 1; + + if (g_ascii_strcasecmp(key, "tsize") == 0) { + int tsize = atoi(value); + struct stat stat_p; + + if (tsize == 0) { + if (stat(spt->filename, &stat_p) == 0) + tsize = stat_p.st_size; + else { + tftp_send_error(spt, 1, "File not found", tp); + return; + } + } + + option_name[nb_options] = "tsize"; + option_value[nb_options] = tsize; + nb_options++; + } else if (g_ascii_strcasecmp(key, "blksize") == 0) { + int blksize = atoi(value); + + /* Accept blksize up to our maximum size */ + if (blksize > 0) { + spt->block_size = MIN(blksize, TFTP_BLOCKSIZE_MAX); + option_name[nb_options] = "blksize"; + option_value[nb_options] = spt->block_size; + nb_options++; + } + } + } + + if (nb_options > 0) { + assert(nb_options <= G_N_ELEMENTS(option_name)); + tftp_send_oack(spt, option_name, option_value, nb_options, tp); + return; + } + + spt->block_nr = 0; + tftp_send_next_block(spt, &tp->hdr); +} + +static void tftp_handle_ack(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftphdr *hdr) +{ + int s; + + s = tftp_session_find(slirp, srcsas, hdr); + + if (s < 0) { + return; + } + + tftp_send_next_block(&slirp->tftp_sessions[s], hdr); +} + +static void tftp_handle_error(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftphdr *hdr) +{ + int s; + + s = tftp_session_find(slirp, srcsas, hdr); + + if (s < 0) { + return; + } + + tftp_session_terminate(&slirp->tftp_sessions[s]); +} + +void tftp_input(struct sockaddr_storage *srcsas, struct mbuf *m) +{ + struct tftphdr *hdr = mtod_check(m, sizeof(struct tftphdr)); + + if (hdr == NULL) { + return; + } + + switch (ntohs(hdr->tp_op)) { + case TFTP_RRQ: + tftp_handle_rrq(m->slirp, srcsas, + mtod(m, struct tftp_t *), + m->m_len); + break; + + case TFTP_ACK: + tftp_handle_ack(m->slirp, srcsas, hdr); + break; + + case TFTP_ERROR: + tftp_handle_error(m->slirp, srcsas, hdr); + break; + } +} diff --git a/app/src/main/cpp/libslirp/src/tftp.h b/app/src/main/cpp/libslirp/src/tftp.h new file mode 100644 index 00000000..263c540a --- /dev/null +++ b/app/src/main/cpp/libslirp/src/tftp.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* tftp defines */ + +#ifndef SLIRP_TFTP_H +#define SLIRP_TFTP_H + +#include "util.h" + +#define TFTP_SESSIONS_MAX 20 + +#define TFTP_SERVER 69 + +#define TFTP_RRQ 1 +#define TFTP_WRQ 2 +#define TFTP_DATA 3 +#define TFTP_ACK 4 +#define TFTP_ERROR 5 +#define TFTP_OACK 6 + +#define TFTP_FILENAME_MAX 512 +#define TFTP_BLOCKSIZE_MAX 1428 + +SLIRP_PACKED_BEGIN +struct tftphdr { + struct udphdr udp; + uint16_t tp_op; +} SLIRP_PACKED_END; + +SLIRP_PACKED_BEGIN +struct tftp_t { + struct tftphdr hdr; + union { + struct { + uint16_t tp_block_nr; + uint8_t tp_buf[TFTP_BLOCKSIZE_MAX]; + } tp_data; + struct { + uint16_t tp_error_code; + uint8_t tp_msg[TFTP_BLOCKSIZE_MAX]; + } tp_error; + char tp_buf[TFTP_BLOCKSIZE_MAX + 2]; + } x; +} SLIRP_PACKED_END; + +struct tftp_session { + Slirp *slirp; + char *filename; + int fd; + uint16_t block_size; + + struct sockaddr_storage client_addr; + uint16_t client_port; + uint32_t block_nr; + + int timestamp; +}; + +/* Process TFTP packet coming from the guest */ +void tftp_input(struct sockaddr_storage *srcsas, struct mbuf *m); + +/* Clear remaining sessions */ +void tftp_cleanup(Slirp *slirp); + +#endif diff --git a/app/src/main/cpp/libslirp/src/udp.c b/app/src/main/cpp/libslirp/src/udp.c new file mode 100644 index 00000000..2965b184 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/udp.c @@ -0,0 +1,427 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)udp_usrreq.c 8.4 (Berkeley) 1/21/94 + * udp_usrreq.c,v 1.4 1994/10/02 17:48:45 phk Exp + */ + +/* + * Changes and additions relating to SLiRP + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#include "slirp.h" +#include "ip_icmp.h" + +static uint8_t udp_tos(struct socket *so); + +void udp_init(Slirp *slirp) +{ + slirp->udb.so_next = slirp->udb.so_prev = &slirp->udb; + slirp->udp_last_so = &slirp->udb; +} + +void udp_cleanup(Slirp *slirp) +{ + struct socket *so, *so_next; + + for (so = slirp->udb.so_next; so != &slirp->udb; so = so_next) { + so_next = so->so_next; + udp_detach(so); + } +} + +/* m->m_data points at ip packet header + * m->m_len length ip packet + * ip->ip_len length data (IPDU) + */ +void udp_input(register struct mbuf *m, int iphlen) +{ + Slirp *slirp = m->slirp; + M_DUP_DEBUG(slirp, m, 0, 0); + + register struct ip *ip; + register struct udphdr *uh; + int len; + struct ip save_ip; + struct socket *so; + struct sockaddr_storage lhost; + struct sockaddr_in *lhost4; + int ttl; + + DEBUG_CALL("udp_input"); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("iphlen = %d", iphlen); + + /* + * Strip IP options, if any; should skip this, + * make available to user, and use on returned packets, + * but we don't yet have a way to check the checksum + * with options still present. + */ + if (iphlen > sizeof(struct ip)) { + ip_stripoptions(m); + iphlen = sizeof(struct ip); + } + + /* + * Get IP and UDP header together in first mbuf. + */ + ip = mtod_check(m, iphlen + sizeof(struct udphdr)); + if (ip == NULL) { + goto bad; + } + uh = (struct udphdr *)((char *)ip + iphlen); + + /* + * Make mbuf data length reflect UDP length. + * If not enough data to reflect UDP length, drop. + */ + len = ntohs((uint16_t)uh->uh_ulen); + + if (ip->ip_len != len) { + if (len > ip->ip_len) { + goto bad; + } + m_adj(m, len - ip->ip_len); + ip->ip_len = len; + } + + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + save_ip = *ip; + save_ip.ip_len += iphlen; /* tcp_input subtracts this */ + + /* + * Checksum extended UDP header and data. + */ + if (uh->uh_sum) { + memset(&((struct ipovly *)ip)->ih_mbuf, 0, sizeof(struct mbuf_ptr)); + ((struct ipovly *)ip)->ih_x1 = 0; + ((struct ipovly *)ip)->ih_len = uh->uh_ulen; + if (cksum(m, len + sizeof(struct ip))) { + goto bad; + } + } + + lhost.ss_family = AF_INET; + lhost4 = (struct sockaddr_in *)&lhost; + lhost4->sin_addr = ip->ip_src; + lhost4->sin_port = uh->uh_sport; + + /* + * handle DHCP/BOOTP + */ + if (ntohs(uh->uh_dport) == BOOTP_SERVER && + (ip->ip_dst.s_addr == slirp->vhost_addr.s_addr || + ip->ip_dst.s_addr == 0xffffffff)) { + bootp_input(m); + goto bad; + } + + /* + * handle TFTP + */ + if (ntohs(uh->uh_dport) == TFTP_SERVER && + ip->ip_dst.s_addr == slirp->vhost_addr.s_addr) { + m->m_data += iphlen; + m->m_len -= iphlen; + tftp_input(&lhost, m); + m->m_data -= iphlen; + m->m_len += iphlen; + goto bad; + } + + if (slirp->restricted) { + goto bad; + } + + /* + * Locate pcb for datagram. + */ + so = solookup(&slirp->udp_last_so, &slirp->udb, &lhost, NULL); + + if (so == NULL) { + /* + * If there's no socket for this packet, + * create one + */ + so = socreate(slirp, IPPROTO_UDP); + if (udp_attach(so, AF_INET) == -1) { + DEBUG_MISC(" udp_attach errno = %d-%s", errno, strerror(errno)); + sofree(so); + goto bad; + } + + /* + * Setup fields + */ + so->so_lfamily = AF_INET; + so->so_laddr = ip->ip_src; + so->so_lport = uh->uh_sport; + + if ((so->so_iptos = udp_tos(so)) == 0) + so->so_iptos = ip->ip_tos; + + /* + * XXXXX Here, check if it's in udpexec_list, + * and if it is, do the fork_exec() etc. + */ + } + + so->so_ffamily = AF_INET; + so->so_faddr = ip->ip_dst; /* XXX */ + so->so_fport = uh->uh_dport; /* XXX */ + + iphlen += sizeof(struct udphdr); + m->m_len -= iphlen; + m->m_data += iphlen; + + /* + * Check for TTL + */ + ttl = save_ip.ip_ttl-1; + if (ttl <= 0) { + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + DEBUG_MISC("udp ttl exceeded"); + icmp_send_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, NULL); + goto bad; + } + setsockopt(so->s, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + + /* + * Now we sendto() the packet. + */ + if (sosendto(so, m) == -1) { + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + DEBUG_MISC("udp tx errno = %d-%s", errno, strerror(errno)); + icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, strerror(errno)); + goto bad; + } + + m_free(so->so_m); /* used for ICMP if error on sorecvfrom */ + + /* restore the orig mbuf packet */ + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + so->so_m = m; /* ICMP backup */ + + return; +bad: + m_free(m); +} + +int udp_output(struct socket *so, struct mbuf *m, struct sockaddr_in *saddr, + struct sockaddr_in *daddr, int iptos) +{ + Slirp *slirp = m->slirp; + char addr[INET_ADDRSTRLEN]; + + M_DUP_DEBUG(slirp, m, 0, sizeof(struct udpiphdr)); + + register struct udpiphdr *ui; + int error = 0; + + DEBUG_CALL("udp_output"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("saddr = %s", inet_ntop(AF_INET, &saddr->sin_addr, addr, sizeof(addr))); + DEBUG_ARG("daddr = %s", inet_ntop(AF_INET, &daddr->sin_addr, addr, sizeof(addr))); + + /* + * Adjust for header + */ + m->m_data -= sizeof(struct udpiphdr); + m->m_len += sizeof(struct udpiphdr); + + /* + * Fill in mbuf with extended UDP header + * and addresses and length put into network format. + */ + ui = mtod(m, struct udpiphdr *); + memset(&ui->ui_i.ih_mbuf, 0, sizeof(struct mbuf_ptr)); + ui->ui_x1 = 0; + ui->ui_pr = IPPROTO_UDP; + ui->ui_len = htons(m->m_len - sizeof(struct ip)); + /* XXXXX Check for from-one-location sockets, or from-any-location sockets + */ + ui->ui_src = saddr->sin_addr; + ui->ui_dst = daddr->sin_addr; + ui->ui_sport = saddr->sin_port; + ui->ui_dport = daddr->sin_port; + ui->ui_ulen = ui->ui_len; + + /* + * Stuff checksum and output datagram. + */ + ui->ui_sum = 0; + if ((ui->ui_sum = cksum(m, m->m_len)) == 0) + ui->ui_sum = 0xffff; + ((struct ip *)ui)->ip_len = m->m_len; + + ((struct ip *)ui)->ip_ttl = IPDEFTTL; + ((struct ip *)ui)->ip_tos = iptos; + + error = ip_output(so, m); + + return (error); +} + +int udp_attach(struct socket *so, unsigned short af) +{ + so->s = slirp_socket(af, SOCK_DGRAM, 0); + if (so->s != -1) { + if (slirp_bind_outbound(so, af) != 0) { + // bind failed - close socket + closesocket(so->s); + so->s = -1; + return -1; + } + +#ifdef __linux__ + { + int opt = 1; + switch (af) { + case AF_INET: + setsockopt(so->s, IPPROTO_IP, IP_RECVERR, &opt, sizeof(opt)); + break; + case AF_INET6: + setsockopt(so->s, IPPROTO_IPV6, IPV6_RECVERR, &opt, sizeof(opt)); + break; + default: + g_assert_not_reached(); + } + } +#endif + + so->so_expire = curtime + SO_EXPIRE; + slirp_insque(so, &so->slirp->udb); + } + so->slirp->cb->register_poll_fd(so->s, so->slirp->opaque); + return (so->s); +} + +void udp_detach(struct socket *so) +{ + so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); + closesocket(so->s); + sofree(so); +} + +static const struct tos_t udptos[] = { { 0, 53, IPTOS_LOWDELAY, 0 }, /* DNS */ + { 0, 0, 0, 0 } }; + +static uint8_t udp_tos(struct socket *so) +{ + int i = 0; + + while (udptos[i].tos) { + if ((udptos[i].fport && ntohs(so->so_fport) == udptos[i].fport) || + (udptos[i].lport && ntohs(so->so_lport) == udptos[i].lport)) { + if (so->slirp->enable_emu) + so->so_emu = udptos[i].emu; + return udptos[i].tos; + } + i++; + } + + return 0; +} + +struct socket *udpx_listen(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *laddr, socklen_t laddrlen, + int flags) +{ + struct socket *so; + socklen_t addrlen; + int save_errno; + + so = socreate(slirp, IPPROTO_UDP); + so->s = slirp_socket(haddr->sa_family, SOCK_DGRAM, 0); + if (so->s < 0) { + save_errno = errno; + sofree(so); + errno = save_errno; + return NULL; + } + if (haddr->sa_family == AF_INET6) + slirp_socket_set_v6only(so->s, (flags & SS_HOSTFWD_V6ONLY) != 0); + so->so_expire = curtime + SO_EXPIRE; + slirp_insque(so, &slirp->udb); + + if (bind(so->s, haddr, haddrlen) < 0) { + save_errno = errno; + udp_detach(so); + errno = save_errno; + return NULL; + } + slirp_socket_set_fast_reuse(so->s); + + addrlen = sizeof(so->fhost); + getsockname(so->s, &so->fhost.sa, &addrlen); + sotranslate_accept(so); + + sockaddr_copy(&so->lhost.sa, sizeof(so->lhost), laddr, laddrlen); + + if (flags != SS_FACCEPTONCE) + so->so_expire = 0; + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_ISFCONNECTED | flags; + + return so; +} + +struct socket *udp_listen(Slirp *slirp, uint32_t haddr, unsigned hport, + uint32_t laddr, unsigned lport, int flags) +{ + struct sockaddr_in hsa, lsa; + + memset(&hsa, 0, sizeof(hsa)); + hsa.sin_family = AF_INET; + hsa.sin_addr.s_addr = haddr; + hsa.sin_port = hport; + + memset(&lsa, 0, sizeof(lsa)); + lsa.sin_family = AF_INET; + lsa.sin_addr.s_addr = laddr; + lsa.sin_port = lport; + + return udpx_listen(slirp, (const struct sockaddr *) &hsa, sizeof(hsa), (struct sockaddr *) &lsa, sizeof(lsa), flags); +} diff --git a/app/src/main/cpp/libslirp/src/udp.h b/app/src/main/cpp/libslirp/src/udp.h new file mode 100644 index 00000000..b0514f18 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/udp.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)udp.h 8.1 (Berkeley) 6/10/93 + * udp.h,v 1.3 1994/08/21 05:27:41 paul Exp + */ + +#ifndef UDP_H +#define UDP_H + +#include "socket.h" + +#define UDP_TTL 0x60 + +/* + * Udp protocol header. + * Per RFC 768, September, 1981. + */ +struct udphdr { + uint16_t uh_sport; /* source port */ + uint16_t uh_dport; /* destination port */ + int16_t uh_ulen; /* udp length */ + uint16_t uh_sum; /* udp checksum */ +}; + +/* + * UDP kernel structures and variables. + */ +struct udpiphdr { + struct ipovly ui_i; /* overlaid ip structure */ + struct udphdr ui_u; /* udp header */ +}; +#define ui_mbuf ui_i.ih_mbuf.mptr +#define ui_x1 ui_i.ih_x1 +#define ui_pr ui_i.ih_pr +#define ui_len ui_i.ih_len +#define ui_src ui_i.ih_src +#define ui_dst ui_i.ih_dst +#define ui_sport ui_u.uh_sport +#define ui_dport ui_u.uh_dport +#define ui_ulen ui_u.uh_ulen +#define ui_sum ui_u.uh_sum + +/* + * Names for UDP sysctl objects + */ +#define UDPCTL_CHECKSUM 1 /* checksum UDP packets */ +#define UDPCTL_MAXID 2 + +struct mbuf; + +/* Called from slirp_init */ +void udp_init(Slirp *); +/* Called from slirp_cleanup */ +void udp_cleanup(Slirp *); +/* Process UDP datagram coming from the guest */ +void udp_input(register struct mbuf *, int); +/* Create a host UDP socket, bound to this socket */ +int udp_attach(struct socket *, unsigned short af); +/* Destroy socket */ +void udp_detach(struct socket *); + +/* Listen for incoming UDP datagrams on this haddr+hport */ +struct socket *udp_listen(Slirp *, uint32_t haddr, unsigned hport, uint32_t laddr, unsigned lport, int flags); +/* Listen for incoming UDP datagrams on this haddr */ +struct socket *udpx_listen(Slirp *, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *laddr, socklen_t laddrlen, + int flags); +/* Send UDP datagram to the guest */ +int udp_output(struct socket *so, struct mbuf *m, struct sockaddr_in *saddr, + struct sockaddr_in *daddr, int iptos); + +/* Process UDPv6 datagram coming from the guest */ +void udp6_input(register struct mbuf *); +/* Send UDPv6 datagram to the guest */ +int udp6_output(struct socket *so, struct mbuf *m, struct sockaddr_in6 *saddr, + struct sockaddr_in6 *daddr); + +#endif diff --git a/app/src/main/cpp/libslirp/src/udp6.c b/app/src/main/cpp/libslirp/src/udp6.c new file mode 100644 index 00000000..effdf77d --- /dev/null +++ b/app/src/main/cpp/libslirp/src/udp6.c @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron + */ + +#include "slirp.h" +#include "udp.h" +#include "dhcpv6.h" + +void udp6_input(struct mbuf *m) +{ + Slirp *slirp = m->slirp; + M_DUP_DEBUG(slirp, m, 0, 0); + + struct ip6 *ip, save_ip; + struct udphdr *uh; + int iphlen = sizeof(struct ip6); + int len; + struct socket *so; + struct sockaddr_in6 lhost; + int hop_limit; + + DEBUG_CALL("udp6_input"); + DEBUG_ARG("m = %p", m); + + if (slirp->restricted) { + goto bad; + } + + ip = mtod(m, struct ip6 *); + m->m_len -= iphlen; + m->m_data += iphlen; + uh = mtod_check(m, sizeof(struct udphdr)); + if (uh == NULL) { + goto bad; + } + m->m_len += iphlen; + m->m_data -= iphlen; + + if (ip6_cksum(m)) { + goto bad; + } + + len = ntohs((uint16_t)uh->uh_ulen); + + /* + * Make mbuf data length reflect UDP length. + * If not enough data to reflect UDP length, drop. + */ + if (ntohs(ip->ip_pl) != len) { + if (len > ntohs(ip->ip_pl)) { + goto bad; + } + m_adj(m, len - ntohs(ip->ip_pl)); + ip->ip_pl = htons(len); + } + + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + save_ip = *ip; + + /* Locate pcb for datagram. */ + lhost.sin6_family = AF_INET6; + lhost.sin6_addr = ip->ip_src; + lhost.sin6_port = uh->uh_sport; + + /* handle DHCPv6 */ + if (ntohs(uh->uh_dport) == DHCPV6_SERVER_PORT && + (in6_equal(&ip->ip_dst, &slirp->vhost_addr6) || + in6_dhcp_multicast(&ip->ip_dst))) { + m->m_data += iphlen; + m->m_len -= iphlen; + dhcpv6_input(&lhost, m); + m->m_data -= iphlen; + m->m_len += iphlen; + goto bad; + } + + /* handle TFTP */ + if (ntohs(uh->uh_dport) == TFTP_SERVER && + !memcmp(ip->ip_dst.s6_addr, slirp->vhost_addr6.s6_addr, 16)) { + m->m_data += iphlen; + m->m_len -= iphlen; + tftp_input((struct sockaddr_storage *)&lhost, m); + m->m_data -= iphlen; + m->m_len += iphlen; + goto bad; + } + + so = solookup(&slirp->udp_last_so, &slirp->udb, + (struct sockaddr_storage *)&lhost, NULL); + + if (so == NULL) { + /* If there's no socket for this packet, create one. */ + so = socreate(slirp, IPPROTO_UDP); + if (udp_attach(so, AF_INET6) == -1) { + DEBUG_MISC(" udp6_attach errno = %d-%s", errno, strerror(errno)); + sofree(so); + goto bad; + } + + /* Setup fields */ + so->so_lfamily = AF_INET6; + so->so_laddr6 = ip->ip_src; + so->so_lport6 = uh->uh_sport; + } + + so->so_ffamily = AF_INET6; + so->so_faddr6 = ip->ip_dst; /* XXX */ + so->so_fport6 = uh->uh_dport; /* XXX */ + + iphlen += sizeof(struct udphdr); + m->m_len -= iphlen; + m->m_data += iphlen; + + /* + * Check for TTL + */ + hop_limit = save_ip.ip_hl-1; + if (hop_limit <= 0) { + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + DEBUG_MISC("udp ttl exceeded"); + icmp6_send_error(m, ICMP6_TIMXCEED, ICMP6_TIMXCEED_INTRANS); + goto bad; + } + setsockopt(so->s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hop_limit, sizeof(hop_limit)); + + /* + * Now we sendto() the packet. + */ + if (sosendto(so, m) == -1) { + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + DEBUG_MISC("udp tx errno = %d-%s", errno, strerror(errno)); + icmp6_send_error(m, ICMP6_UNREACH, ICMP6_UNREACH_NO_ROUTE); + goto bad; + } + + m_free(so->so_m); /* used for ICMP if error on sorecvfrom */ + + /* restore the orig mbuf packet */ + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + so->so_m = m; + + return; +bad: + m_free(m); +} + +int udp6_output(struct socket *so, struct mbuf *m, struct sockaddr_in6 *saddr, + struct sockaddr_in6 *daddr) +{ + Slirp *slirp = m->slirp; + M_DUP_DEBUG(slirp, m, 0, sizeof(struct ip6) + sizeof(struct udphdr)); + + struct ip6 *ip; + struct udphdr *uh; + + DEBUG_CALL("udp6_output"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m = %p", m); + + /* adjust for header */ + m->m_data -= sizeof(struct udphdr); + m->m_len += sizeof(struct udphdr); + uh = mtod(m, struct udphdr *); + m->m_data -= sizeof(struct ip6); + m->m_len += sizeof(struct ip6); + ip = mtod(m, struct ip6 *); + + /* Build IP header */ + ip->ip_pl = htons(m->m_len - sizeof(struct ip6)); + ip->ip_nh = IPPROTO_UDP; + ip->ip_src = saddr->sin6_addr; + ip->ip_dst = daddr->sin6_addr; + + /* Build UDP header */ + uh->uh_sport = saddr->sin6_port; + uh->uh_dport = daddr->sin6_port; + uh->uh_ulen = ip->ip_pl; + uh->uh_sum = 0; + uh->uh_sum = ip6_cksum(m); + if (uh->uh_sum == 0) { + uh->uh_sum = 0xffff; + } + + return ip6_output(so, m, 0); +} diff --git a/app/src/main/cpp/libslirp/src/util.c b/app/src/main/cpp/libslirp/src/util.c new file mode 100644 index 00000000..7892edf4 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/util.c @@ -0,0 +1,441 @@ +/* SPDX-License-Identifier: MIT */ +/* + * util.c (mostly based on QEMU os-win32.c) + * + * Copyright (c) 2003-2008 Fabrice Bellard + * Copyright (c) 2010-2016 Red Hat, Inc. + * + * QEMU library functions for win32 which are shared between QEMU and + * the QEMU tools. + * + * 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 "util.h" + +//#include +#include +#include + +#if defined(_WIN32) +int slirp_inet_aton(const char *cp, struct in_addr *ia) +{ + uint32_t addr = inet_addr(cp); + if (addr == 0xffffffff) { + return 0; + } + ia->s_addr = addr; + return 1; +} +#endif + +void slirp_set_nonblock(int fd) +{ +#ifndef _WIN32 + int f; + f = fcntl(fd, F_GETFL); + assert(f != -1); + f = fcntl(fd, F_SETFL, f | O_NONBLOCK); + assert(f != -1); +#else + unsigned long opt = 1; + ioctlsocket(fd, FIONBIO, &opt); +#endif +} + +static void slirp_set_cloexec(int fd) +{ +#ifndef _WIN32 + int f; + f = fcntl(fd, F_GETFD); + assert(f != -1); + f = fcntl(fd, F_SETFD, f | FD_CLOEXEC); + assert(f != -1); +#endif +} + +/* + * Opens a socket with FD_CLOEXEC set + * On failure errno contains the reason. + */ +int slirp_socket(int domain, int type, int protocol) +{ + int ret; + +#ifdef SOCK_CLOEXEC + ret = socket(domain, type | SOCK_CLOEXEC, protocol); + if (ret != -1 || errno != EINVAL) { + return ret; + } +#endif + ret = socket(domain, type, protocol); + if (ret >= 0) { + slirp_set_cloexec(ret); + } + + return ret; +} + +#ifdef _WIN32 +static int socket_error(void) +{ + switch (WSAGetLastError()) { + case 0: + return 0; + case WSAEINTR: + return EINTR; + case WSAEINVAL: + return EINVAL; + case WSA_INVALID_HANDLE: + return EBADF; + case WSA_NOT_ENOUGH_MEMORY: + return ENOMEM; + case WSA_INVALID_PARAMETER: + return EINVAL; + case WSAENAMETOOLONG: + return ENAMETOOLONG; + case WSAENOTEMPTY: + return ENOTEMPTY; + case WSAEWOULDBLOCK: + /* not using EWOULDBLOCK as we don't want code to have + * to check both EWOULDBLOCK and EAGAIN */ + return EAGAIN; + case WSAEINPROGRESS: + return EINPROGRESS; + case WSAEALREADY: + return EALREADY; + case WSAENOTSOCK: + return ENOTSOCK; + case WSAEDESTADDRREQ: + return EDESTADDRREQ; + case WSAEMSGSIZE: + return EMSGSIZE; + case WSAEPROTOTYPE: + return EPROTOTYPE; + case WSAENOPROTOOPT: + return ENOPROTOOPT; + case WSAEPROTONOSUPPORT: + return EPROTONOSUPPORT; + case WSAEOPNOTSUPP: + return EOPNOTSUPP; + case WSAEAFNOSUPPORT: + return EAFNOSUPPORT; + case WSAEADDRINUSE: + return EADDRINUSE; + case WSAEADDRNOTAVAIL: + return EADDRNOTAVAIL; + case WSAENETDOWN: + return ENETDOWN; + case WSAENETUNREACH: + return ENETUNREACH; + case WSAENETRESET: + return ENETRESET; + case WSAECONNABORTED: + return ECONNABORTED; + case WSAECONNRESET: + return ECONNRESET; + case WSAENOBUFS: + return ENOBUFS; + case WSAEISCONN: + return EISCONN; + case WSAENOTCONN: + return ENOTCONN; + case WSAETIMEDOUT: + return ETIMEDOUT; + case WSAECONNREFUSED: + return ECONNREFUSED; + case WSAELOOP: + return ELOOP; + case WSAEHOSTUNREACH: + return EHOSTUNREACH; + default: + return EIO; + } +} + +#undef ioctlsocket +int slirp_ioctlsocket_wrap(int fd, int req, void *val) +{ + int ret; + ret = ioctlsocket(fd, req, val); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef closesocket +int slirp_closesocket_wrap(int fd) +{ + int ret; + ret = closesocket(fd); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef connect +int slirp_connect_wrap(int sockfd, const struct sockaddr *addr, int addrlen) +{ + int ret; + ret = connect(sockfd, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef listen +int slirp_listen_wrap(int sockfd, int backlog) +{ + int ret; + ret = listen(sockfd, backlog); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef bind +int slirp_bind_wrap(int sockfd, const struct sockaddr *addr, int addrlen) +{ + int ret; + ret = bind(sockfd, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef socket +int slirp_socket_wrap(int domain, int type, int protocol) +{ + int ret; + ret = socket(domain, type, protocol); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef accept +int slirp_accept_wrap(int sockfd, struct sockaddr *addr, int *addrlen) +{ + int ret; + ret = accept(sockfd, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef shutdown +int slirp_shutdown_wrap(int sockfd, int how) +{ + int ret; + ret = shutdown(sockfd, how); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef getsockopt +int slirp_getsockopt_wrap(int sockfd, int level, int optname, void *optval, + int *optlen) +{ + int ret; + ret = getsockopt(sockfd, level, optname, optval, optlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef setsockopt +int slirp_setsockopt_wrap(int sockfd, int level, int optname, + const void *optval, int optlen) +{ + int ret; + ret = setsockopt(sockfd, level, optname, optval, optlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef getpeername +int slirp_getpeername_wrap(int sockfd, struct sockaddr *addr, int *addrlen) +{ + int ret; + ret = getpeername(sockfd, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef getsockname +int slirp_getsockname_wrap(int sockfd, struct sockaddr *addr, int *addrlen) +{ + int ret; + ret = getsockname(sockfd, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef send +slirp_ssize_t slirp_send_wrap(int sockfd, const void *buf, size_t len, int flags) +{ + int ret; + ret = send(sockfd, buf, len, flags); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef sendto +slirp_ssize_t slirp_sendto_wrap(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *addr, int addrlen) +{ + int ret; + ret = sendto(sockfd, buf, len, flags, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef recv +slirp_ssize_t slirp_recv_wrap(int sockfd, void *buf, size_t len, int flags) +{ + int ret; + ret = recv(sockfd, buf, len, flags); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef recvfrom +slirp_ssize_t slirp_recvfrom_wrap(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *addr, int *addrlen) +{ + int ret; + ret = recvfrom(sockfd, buf, len, flags, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} +#endif /* WIN32 */ + +void slirp_pstrcpy(char *buf, int buf_size, const char *str) +{ + int c; + char *q = buf; + + if (buf_size <= 0) + return; + + for (;;) { + c = *str++; + if (c == 0 || q >= buf + buf_size - 1) + break; + *q++ = c; + } + *q = '\0'; +} + +G_GNUC_PRINTF(3, 0) +static int slirp_vsnprintf(char *str, size_t size, + const char *format, va_list args) +{ + int rv = g_vsnprintf(str, size, format, args); + + if (rv < 0) { + g_error("g_vsnprintf() failed: %s", g_strerror(errno)); + } + + return rv; +} + +/* + * A snprintf()-like function that: + * - returns the number of bytes written (excluding optional \0-ending) + * - dies on error + * - warn on truncation + */ +int slirp_fmt(char *str, size_t size, const char *format, ...) +{ + va_list args; + int rv; + + va_start(args, format); + rv = slirp_vsnprintf(str, size, format, args); + va_end(args); + + if (rv >= size) { + g_critical("slirp_fmt() truncation"); + } + + return MIN(rv, size); +} + +/* + * A snprintf()-like function that: + * - always \0-end (unless size == 0) + * - returns the number of bytes actually written, including \0 ending + * - dies on error + * - warn on truncation + */ +int slirp_fmt0(char *str, size_t size, const char *format, ...) +{ + va_list args; + int rv; + + va_start(args, format); + rv = slirp_vsnprintf(str, size, format, args); + va_end(args); + + if (rv >= size) { + g_critical("slirp_fmt0() truncation"); + if (size > 0) + str[size - 1] = '\0'; + rv = size; + } else { + rv += 1; /* include \0 */ + } + + return rv; +} + +const char *slirp_ether_ntoa(const uint8_t *addr, char *out_str, + size_t out_str_size) +{ + assert(out_str_size >= ETH_ADDRSTRLEN); + + slirp_fmt0(out_str, out_str_size, "%02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + + return out_str; +} diff --git a/app/src/main/cpp/libslirp/src/util.h b/app/src/main/cpp/libslirp/src/util.h new file mode 100644 index 00000000..c378cee1 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/util.h @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2003-2008 Fabrice Bellard + * Copyright (c) 2010-2019 Red Hat, Inc. + * + * 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. + */ +#ifndef UTIL_H_ +#define UTIL_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +#include "libslirp.h" + +#ifdef __GNUC__ +#define SLIRP_PACKED_BEGIN +#if defined(_WIN32) && (defined(__x86_64__) || defined(__i386__)) +#define SLIRP_PACKED_END __attribute__((gcc_struct, packed)) +#else +#define SLIRP_PACKED_END __attribute__((packed)) +#endif +#elif defined(_MSC_VER) +#define SLIRP_PACKED_BEGIN __pragma(pack(push, 1)) +#define SLIRP_PACKED_END __pragma(pack(pop)) +#endif + +#ifndef DIV_ROUND_UP +#define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d)) +#endif + +#ifndef container_of +#define container_of(ptr, type, member) \ + ((type *) (((char *)(ptr)) - offsetof(type, member))) +#endif + +#ifndef G_SIZEOF_MEMBER +#define G_SIZEOF_MEMBER(type, member) sizeof(((type *)0)->member) +#endif + +#if defined(_WIN32) /* CONFIG_IOVEC */ +#if !defined(IOV_MAX) /* XXX: to avoid duplicate with QEMU osdep.h */ +struct iovec { + void *iov_base; + size_t iov_len; +}; +#endif +#else +#include +#endif + +#define stringify(s) tostring(s) +#define tostring(s) #s + +#define SCALE_MS 1000000 + +#define ETH_ALEN 6 +#define ETH_ADDRSTRLEN 18 /* "xx:xx:xx:xx:xx:xx", with trailing NUL */ +#define ETH_HLEN 14 +#define ETH_MINLEN 60 +#define ETH_P_IP (0x0800) /* Internet Protocol packet */ +#define ETH_P_ARP (0x0806) /* Address Resolution packet */ +#define ETH_P_IPV6 (0x86dd) +#define ETH_P_VLAN (0x8100) +#define ETH_P_DVLAN (0x88a8) +#define ETH_P_NCSI (0x88f8) +#define ETH_P_UNKNOWN (0xffff) + +/* FIXME: remove me when made standalone */ +#ifdef _WIN32 +#undef accept +#undef bind +#undef closesocket +#undef connect +#undef getpeername +#undef getsockname +#undef getsockopt +#undef ioctlsocket +#undef listen +#undef recv +#undef recvfrom +#undef send +#undef sendto +#undef setsockopt +#undef shutdown +#undef socket +#endif + +#ifdef _WIN32 +#define connect slirp_connect_wrap +int slirp_connect_wrap(int fd, const struct sockaddr *addr, int addrlen); +#define listen slirp_listen_wrap +int slirp_listen_wrap(int fd, int backlog); +#define bind slirp_bind_wrap +int slirp_bind_wrap(int fd, const struct sockaddr *addr, int addrlen); +#define socket slirp_socket_wrap +int slirp_socket_wrap(int domain, int type, int protocol); +#define accept slirp_accept_wrap +int slirp_accept_wrap(int fd, struct sockaddr *addr, int *addrlen); +#define shutdown slirp_shutdown_wrap +int slirp_shutdown_wrap(int fd, int how); +#define getpeername slirp_getpeername_wrap +int slirp_getpeername_wrap(int fd, struct sockaddr *addr, int *addrlen); +#define getsockname slirp_getsockname_wrap +int slirp_getsockname_wrap(int fd, struct sockaddr *addr, int *addrlen); +#define send slirp_send_wrap +slirp_ssize_t slirp_send_wrap(int fd, const void *buf, size_t len, int flags); +#define sendto slirp_sendto_wrap +slirp_ssize_t slirp_sendto_wrap(int fd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, int addrlen); +#define recv slirp_recv_wrap +slirp_ssize_t slirp_recv_wrap(int fd, void *buf, size_t len, int flags); +#define recvfrom slirp_recvfrom_wrap +slirp_ssize_t slirp_recvfrom_wrap(int fd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, int *addrlen); +#define closesocket slirp_closesocket_wrap +int slirp_closesocket_wrap(int fd); +#define ioctlsocket slirp_ioctlsocket_wrap +int slirp_ioctlsocket_wrap(int fd, int req, void *val); +#define getsockopt slirp_getsockopt_wrap +int slirp_getsockopt_wrap(int sockfd, int level, int optname, void *optval, + int *optlen); +#define setsockopt slirp_setsockopt_wrap +int slirp_setsockopt_wrap(int sockfd, int level, int optname, + const void *optval, int optlen); +#define inet_aton slirp_inet_aton +int slirp_inet_aton(const char *cp, struct in_addr *ia); +#else +#define closesocket(s) close(s) +#define ioctlsocket(s, r, v) ioctl(s, r, v) +#endif + +int slirp_socket(int domain, int type, int protocol); +void slirp_set_nonblock(int fd); + +static inline int slirp_socket_set_v6only(int fd, int v) +{ + return setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &v, sizeof(v)); +} + +static inline int slirp_socket_set_nodelay(int fd) +{ + int v = 1; + return setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v)); +} + +static inline int slirp_socket_set_fast_reuse(int fd) +{ +#ifndef _WIN32 + int v = 1; + return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)); +#else + /* Enabling the reuse of an endpoint that was used by a socket still in + * TIME_WAIT state is usually performed by setting SO_REUSEADDR. On Windows + * fast reuse is the default and SO_REUSEADDR does strange things. So we + * don't have to do anything here. More info can be found at: + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms740621.aspx */ + return 0; +#endif +} + +void slirp_pstrcpy(char *buf, int buf_size, const char *str); + +int slirp_fmt(char *str, size_t size, const char *format, ...) G_GNUC_PRINTF(3, 4); +int slirp_fmt0(char *str, size_t size, const char *format, ...) G_GNUC_PRINTF(3, 4); + +/* + * Pretty print a MAC address into out_str. + * As a convenience returns out_str. + */ +const char *slirp_ether_ntoa(const uint8_t *addr, char *out_str, + size_t out_str_len); + +#endif diff --git a/app/src/main/cpp/libslirp/src/version.c b/app/src/main/cpp/libslirp/src/version.c new file mode 100644 index 00000000..93e0be9c --- /dev/null +++ b/app/src/main/cpp/libslirp/src/version.c @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#include "libslirp.h" + +const char * +slirp_version_string(void) +{ + return SLIRP_VERSION_STRING; +} diff --git a/app/src/main/cpp/libslirp/src/vmstate.c b/app/src/main/cpp/libslirp/src/vmstate.c new file mode 100644 index 00000000..0e1d4965 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/vmstate.c @@ -0,0 +1,445 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * VMState interpreter + * + * Copyright (c) 2009-2018 Red Hat Inc + * + * Authors: + * Juan Quintela + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +//#include + +#include "stream.h" +#include "vmstate.h" + +static int get_nullptr(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + if (slirp_istream_read_u8(f) == VMS_NULLPTR_MARKER) { + return 0; + } + g_warning("vmstate: get_nullptr expected VMS_NULLPTR_MARKER"); + return -EINVAL; +} + +static int put_nullptr(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) + +{ + if (pv == NULL) { + slirp_ostream_write_u8(f, VMS_NULLPTR_MARKER); + return 0; + } + g_warning("vmstate: put_nullptr must be called with pv == NULL"); + return -EINVAL; +} + +const VMStateInfo slirp_vmstate_info_nullptr = { + .name = "uint64", + .get = get_nullptr, + .put = put_nullptr, +}; + +/* 8 bit unsigned int */ + +static int get_uint8(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + uint8_t *v = pv; + *v = slirp_istream_read_u8(f); + return 0; +} + +static int put_uint8(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + uint8_t *v = pv; + slirp_ostream_write_u8(f, *v); + return 0; +} + +const VMStateInfo slirp_vmstate_info_uint8 = { + .name = "uint8", + .get = get_uint8, + .put = put_uint8, +}; + +/* 16 bit unsigned int */ + +static int get_uint16(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + uint16_t *v = pv; + *v = slirp_istream_read_u16(f); + return 0; +} + +static int put_uint16(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + uint16_t *v = pv; + slirp_ostream_write_u16(f, *v); + return 0; +} + +const VMStateInfo slirp_vmstate_info_uint16 = { + .name = "uint16", + .get = get_uint16, + .put = put_uint16, +}; + +/* 32 bit unsigned int */ + +static int get_uint32(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + uint32_t *v = pv; + *v = slirp_istream_read_u32(f); + return 0; +} + +static int put_uint32(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + uint32_t *v = pv; + slirp_ostream_write_u32(f, *v); + return 0; +} + +const VMStateInfo slirp_vmstate_info_uint32 = { + .name = "uint32", + .get = get_uint32, + .put = put_uint32, +}; + +/* 16 bit int */ + +static int get_int16(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + int16_t *v = pv; + *v = slirp_istream_read_i16(f); + return 0; +} + +static int put_int16(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + int16_t *v = pv; + slirp_ostream_write_i16(f, *v); + return 0; +} + +const VMStateInfo slirp_vmstate_info_int16 = { + .name = "int16", + .get = get_int16, + .put = put_int16, +}; + +/* 32 bit int */ + +static int get_int32(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + int32_t *v = pv; + *v = slirp_istream_read_i32(f); + return 0; +} + +static int put_int32(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + int32_t *v = pv; + slirp_ostream_write_i32(f, *v); + return 0; +} + +const VMStateInfo slirp_vmstate_info_int32 = { + .name = "int32", + .get = get_int32, + .put = put_int32, +}; + +/* vmstate_info_tmp, see VMSTATE_WITH_TMP, the idea is that we allocate + * a temporary buffer and the pre_load/pre_save methods in the child vmsd + * copy stuff from the parent into the child and do calculations to fill + * in fields that don't really exist in the parent but need to be in the + * stream. + */ +static int get_tmp(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + int ret; + const VMStateDescription *vmsd = field->vmsd; + int version_id = field->version_id; + void *tmp = g_malloc(size); + + /* Writes the parent field which is at the start of the tmp */ + *(void **)tmp = pv; + ret = slirp_vmstate_load_state(f, vmsd, tmp, version_id); + g_free(tmp); + return ret; +} + +static int put_tmp(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + const VMStateDescription *vmsd = field->vmsd; + void *tmp = g_malloc(size); + int ret; + + /* Writes the parent field which is at the start of the tmp */ + *(void **)tmp = pv; + ret = slirp_vmstate_save_state(f, vmsd, tmp); + g_free(tmp); + + return ret; +} + +const VMStateInfo slirp_vmstate_info_tmp = { + .name = "tmp", + .get = get_tmp, + .put = put_tmp, +}; + +/* uint8_t buffers */ + +static int get_buffer(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + slirp_istream_read(f, pv, size); + return 0; +} + +static int put_buffer(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + slirp_ostream_write(f, pv, size); + return 0; +} + +const VMStateInfo slirp_vmstate_info_buffer = { + .name = "buffer", + .get = get_buffer, + .put = put_buffer, +}; + +static int vmstate_n_elems(char *opaque, const VMStateField *field) +{ + int n_elems = 1; + + if (field->flags & VMS_ARRAY) { + n_elems = field->num; + } else if (field->flags & VMS_VARRAY_INT32) { + n_elems = *(int32_t *)(opaque + field->num_offset); + } else if (field->flags & VMS_VARRAY_UINT32) { + n_elems = *(uint32_t *)(opaque + field->num_offset); + } else if (field->flags & VMS_VARRAY_UINT16) { + n_elems = *(uint16_t *)(opaque + field->num_offset); + } else if (field->flags & VMS_VARRAY_UINT8) { + n_elems = *(uint8_t *)(opaque + field->num_offset); + } + + if (field->flags & VMS_MULTIPLY_ELEMENTS) { + n_elems *= field->num; + } + + return n_elems; +} + +static int vmstate_size(char *opaque, const VMStateField *field) +{ + int size = field->size; + + if (field->flags & VMS_VBUFFER) { + size = *(int32_t *)(opaque + field->size_offset); + if (field->flags & VMS_MULTIPLY) { + size *= field->size; + } + } + + return size; +} + +static int vmstate_save_state_v(SlirpOStream *f, const VMStateDescription *vmsd, + char *opaque, int version_id) +{ + int ret = 0; + const VMStateField *field = vmsd->fields; + + if (vmsd->pre_save) { + ret = vmsd->pre_save(opaque); + if (ret) { + g_warning("pre-save failed: %s", vmsd->name); + return ret; + } + } + + while (field->name) { + if ((field->field_exists && field->field_exists(opaque, version_id)) || + (!field->field_exists && field->version_id <= version_id)) { + char *first_elem = opaque + field->offset; + int i, n_elems = vmstate_n_elems(opaque, field); + int size = vmstate_size(opaque, field); + + if (field->flags & VMS_POINTER) { + first_elem = *(void **)first_elem; + assert(first_elem || !n_elems || !size); + } + for (i = 0; i < n_elems; i++) { + void *curr_elem = first_elem + size * i; + + if (field->flags & VMS_ARRAY_OF_POINTER) { + assert(curr_elem); + curr_elem = *(void **)curr_elem; + } + if (!curr_elem && size) { + /* if null pointer write placeholder and do not follow */ + assert(field->flags & VMS_ARRAY_OF_POINTER); + ret = slirp_vmstate_info_nullptr.put(f, curr_elem, size, + NULL); + } else if (field->flags & VMS_STRUCT) { + ret = slirp_vmstate_save_state(f, field->vmsd, curr_elem); + } else if (field->flags & VMS_VSTRUCT) { + ret = vmstate_save_state_v(f, field->vmsd, curr_elem, + field->struct_version_id); + } else { + ret = field->info->put(f, curr_elem, size, field); + } + if (ret) { + g_warning("Save of field %s/%s failed", vmsd->name, + field->name); + return ret; + } + } + } else { + if (field->flags & VMS_MUST_EXIST) { + g_warning("Output state validation failed: %s/%s", vmsd->name, + field->name); + assert(!(field->flags & VMS_MUST_EXIST)); + } + } + field++; + } + + return 0; +} + +int slirp_vmstate_save_state(SlirpOStream *f, const VMStateDescription *vmsd, + void *opaque) +{ + return vmstate_save_state_v(f, vmsd, opaque, vmsd->version_id); +} + +static void vmstate_handle_alloc(void *ptr, VMStateField *field, void *opaque) +{ + if (field->flags & VMS_POINTER && field->flags & VMS_ALLOC) { + size_t size = vmstate_size(opaque, field); + size *= vmstate_n_elems(opaque, field); + if (size) { + *(void **)ptr = g_malloc(size); + } + } +} + +int slirp_vmstate_load_state(SlirpIStream *f, const VMStateDescription *vmsd, + void *opaque_, int version_id) +{ + VMStateField *field = vmsd->fields; + int ret = 0; + char *opaque = opaque_; + + if (version_id > vmsd->version_id) { + g_warning("%s: incoming version_id %d is too new " + "for local version_id %d", + vmsd->name, version_id, vmsd->version_id); + return -EINVAL; + } + if (vmsd->pre_load) { + int ret = vmsd->pre_load(opaque); + if (ret) { + return ret; + } + } + while (field->name) { + if ((field->field_exists && field->field_exists(opaque, version_id)) || + (!field->field_exists && field->version_id <= version_id)) { + char *first_elem = opaque + field->offset; + int i, n_elems = vmstate_n_elems(opaque, field); + int size = vmstate_size(opaque, field); + + vmstate_handle_alloc(first_elem, field, opaque); + if (field->flags & VMS_POINTER) { + first_elem = *(void **)first_elem; + assert(first_elem || !n_elems || !size); + } + for (i = 0; i < n_elems; i++) { + void *curr_elem = first_elem + size * i; + + if (field->flags & VMS_ARRAY_OF_POINTER) { + curr_elem = *(void **)curr_elem; + } + if (!curr_elem && size) { + /* if null pointer check placeholder and do not follow */ + assert(field->flags & VMS_ARRAY_OF_POINTER); + ret = slirp_vmstate_info_nullptr.get(f, curr_elem, size, + NULL); + } else if (field->flags & VMS_STRUCT) { + ret = slirp_vmstate_load_state(f, field->vmsd, curr_elem, + field->vmsd->version_id); + } else if (field->flags & VMS_VSTRUCT) { + ret = slirp_vmstate_load_state(f, field->vmsd, curr_elem, + field->struct_version_id); + } else { + ret = field->info->get(f, curr_elem, size, field); + } + if (ret < 0) { + g_warning("Failed to load %s:%s", vmsd->name, field->name); + return ret; + } + } + } else if (field->flags & VMS_MUST_EXIST) { + g_warning("Input validation failed: %s/%s", vmsd->name, + field->name); + return -1; + } + field++; + } + if (vmsd->post_load) { + ret = vmsd->post_load(opaque, version_id); + } + return ret; +} diff --git a/app/src/main/cpp/libslirp/src/vmstate.h b/app/src/main/cpp/libslirp/src/vmstate.h new file mode 100644 index 00000000..cd77c850 --- /dev/null +++ b/app/src/main/cpp/libslirp/src/vmstate.h @@ -0,0 +1,401 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * QEMU migration/snapshot declarations + * + * Copyright (c) 2009-2011 Red Hat, Inc. + * + * Original author: Juan Quintela + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef VMSTATE_H_ +#define VMSTATE_H_ + +#include +#include +#include "slirp.h" +#include "stream.h" + +#define stringify(s) tostring(s) +#define tostring(s) #s + +typedef struct VMStateInfo VMStateInfo; +typedef struct VMStateDescription VMStateDescription; +typedef struct VMStateField VMStateField; + +int slirp_vmstate_save_state(SlirpOStream *f, const VMStateDescription *vmsd, + void *opaque); +int slirp_vmstate_load_state(SlirpIStream *f, const VMStateDescription *vmsd, + void *opaque, int version_id); + +/* VMStateInfo allows customized migration of objects that don't fit in + * any category in VMStateFlags. Additional information is always passed + * into get and put in terms of field and vmdesc parameters. However + * these two parameters should only be used in cases when customized + * handling is needed, such as QTAILQ. For primitive data types such as + * integer, field and vmdesc parameters should be ignored inside get/put. + */ +struct VMStateInfo { + const char *name; + int (*get)(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field); + int (*put)(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field); +}; + +enum VMStateFlags { + /* Ignored */ + VMS_SINGLE = 0x001, + + /* The struct member at opaque + VMStateField.offset is a pointer + * to the actual field (e.g. struct a { uint8_t *b; + * }). Dereference the pointer before using it as basis for + * further pointer arithmetic (see e.g. VMS_ARRAY). Does not + * affect the meaning of VMStateField.num_offset or + * VMStateField.size_offset; see VMS_VARRAY* and VMS_VBUFFER for + * those. */ + VMS_POINTER = 0x002, + + /* The field is an array of fixed size. VMStateField.num contains + * the number of entries in the array. The size of each entry is + * given by VMStateField.size and / or opaque + + * VMStateField.size_offset; see VMS_VBUFFER and + * VMS_MULTIPLY. Each array entry will be processed individually + * (VMStateField.info.get()/put() if VMS_STRUCT is not set, + * recursion into VMStateField.vmsd if VMS_STRUCT is set). May not + * be combined with VMS_VARRAY*. */ + VMS_ARRAY = 0x004, + + /* The field is itself a struct, containing one or more + * fields. Recurse into VMStateField.vmsd. Most useful in + * combination with VMS_ARRAY / VMS_VARRAY*, recursing into each + * array entry. */ + VMS_STRUCT = 0x008, + + /* The field is an array of variable size. The int32_t at opaque + + * VMStateField.num_offset contains the number of entries in the + * array. See the VMS_ARRAY description regarding array handling + * in general. May not be combined with VMS_ARRAY or any other + * VMS_VARRAY*. */ + VMS_VARRAY_INT32 = 0x010, + + /* Ignored */ + VMS_BUFFER = 0x020, + + /* The field is a (fixed-size or variable-size) array of pointers + * (e.g. struct a { uint8_t *b[]; }). Dereference each array entry + * before using it. Note: Does not imply any one of VMS_ARRAY / + * VMS_VARRAY*; these need to be set explicitly. */ + VMS_ARRAY_OF_POINTER = 0x040, + + /* The field is an array of variable size. The uint16_t at opaque + * + VMStateField.num_offset (subject to VMS_MULTIPLY_ELEMENTS) + * contains the number of entries in the array. See the VMS_ARRAY + * description regarding array handling in general. May not be + * combined with VMS_ARRAY or any other VMS_VARRAY*. */ + VMS_VARRAY_UINT16 = 0x080, + + /* The size of the individual entries (a single array entry if + * VMS_ARRAY or any of VMS_VARRAY* are set, or the field itself if + * neither is set) is variable (i.e. not known at compile-time), + * but the same for all entries. Use the int32_t at opaque + + * VMStateField.size_offset (subject to VMS_MULTIPLY) to determine + * the size of each (and every) entry. */ + VMS_VBUFFER = 0x100, + + /* Multiply the entry size given by the int32_t at opaque + + * VMStateField.size_offset (see VMS_VBUFFER description) with + * VMStateField.size to determine the number of bytes to be + * allocated. Only valid in combination with VMS_VBUFFER. */ + VMS_MULTIPLY = 0x200, + + /* The field is an array of variable size. The uint8_t at opaque + + * VMStateField.num_offset (subject to VMS_MULTIPLY_ELEMENTS) + * contains the number of entries in the array. See the VMS_ARRAY + * description regarding array handling in general. May not be + * combined with VMS_ARRAY or any other VMS_VARRAY*. */ + VMS_VARRAY_UINT8 = 0x400, + + /* The field is an array of variable size. The uint32_t at opaque + * + VMStateField.num_offset (subject to VMS_MULTIPLY_ELEMENTS) + * contains the number of entries in the array. See the VMS_ARRAY + * description regarding array handling in general. May not be + * combined with VMS_ARRAY or any other VMS_VARRAY*. */ + VMS_VARRAY_UINT32 = 0x800, + + /* Fail loading the serialised VM state if this field is missing + * from the input. */ + VMS_MUST_EXIST = 0x1000, + + /* When loading serialised VM state, allocate memory for the + * (entire) field. Only valid in combination with + * VMS_POINTER. Note: Not all combinations with other flags are + * currently supported, e.g. VMS_ALLOC|VMS_ARRAY_OF_POINTER won't + * cause the individual entries to be allocated. */ + VMS_ALLOC = 0x2000, + + /* Multiply the number of entries given by the integer at opaque + + * VMStateField.num_offset (see VMS_VARRAY*) with VMStateField.num + * to determine the number of entries in the array. Only valid in + * combination with one of VMS_VARRAY*. */ + VMS_MULTIPLY_ELEMENTS = 0x4000, + + /* A structure field that is like VMS_STRUCT, but uses + * VMStateField.struct_version_id to tell which version of the + * structure we are referencing to use. */ + VMS_VSTRUCT = 0x8000, + + /* Marker for end of list */ + VMS_END = 0x10000 +}; + +struct VMStateField { + const char *name; + size_t offset; + size_t size; + size_t start; + int num; + size_t num_offset; + size_t size_offset; + const VMStateInfo *info; + enum VMStateFlags flags; + const VMStateDescription *vmsd; + int version_id; + int struct_version_id; + bool (*field_exists)(void *opaque, int version_id); +}; + +struct VMStateDescription { + const char *name; + int version_id; + int (*pre_load)(void *opaque); + int (*post_load)(void *opaque, int version_id); + int (*pre_save)(void *opaque); + VMStateField *fields; +}; + + +extern const VMStateInfo slirp_vmstate_info_int16; +extern const VMStateInfo slirp_vmstate_info_int32; +extern const VMStateInfo slirp_vmstate_info_uint8; +extern const VMStateInfo slirp_vmstate_info_uint16; +extern const VMStateInfo slirp_vmstate_info_uint32; + +/** Put this in the stream when migrating a null pointer.*/ +#define VMS_NULLPTR_MARKER (0x30U) /* '0' */ +extern const VMStateInfo slirp_vmstate_info_nullptr; + +extern const VMStateInfo slirp_vmstate_info_buffer; +extern const VMStateInfo slirp_vmstate_info_tmp; + +#ifdef __GNUC__ +#define type_check_array(t1, t2, n) ((t1(*)[n])0 - (t2 *)0) +#define type_check_pointer(t1, t2) ((t1 **)0 - (t2 *)0) +#define typeof_field(type, field) typeof(((type *)0)->field) +#define type_check(t1, t2) ((t1 *)0 - (t2 *)0) +#else +#define type_check_array(t1, t2, n) 0 +#define type_check_pointer(t1, t2) 0 +#define typeof_field(type, field) (((type *)0)->field) +#define type_check(t1, t2) 0 +#endif + +#define vmstate_offset_value(_state, _field, _type) \ + (offsetof(_state, _field) + type_check(_type, typeof_field(_state, _field))) + +#define vmstate_offset_pointer(_state, _field, _type) \ + (offsetof(_state, _field) + \ + type_check_pointer(_type, typeof_field(_state, _field))) + +#define vmstate_offset_array(_state, _field, _type, _num) \ + (offsetof(_state, _field) + \ + type_check_array(_type, typeof_field(_state, _field), _num)) + +#define vmstate_offset_buffer(_state, _field) \ + vmstate_offset_array(_state, _field, uint8_t, \ + sizeof(typeof_field(_state, _field))) + +/* In the macros below, if there is a _version, that means the macro's + * field will be processed only if the version being received is >= + * the _version specified. In general, if you add a new field, you + * would increment the structure's version and put that version + * number into the new field so it would only be processed with the + * new version. + * + * In particular, for VMSTATE_STRUCT() and friends the _version does + * *NOT* pick the version of the sub-structure. It works just as + * specified above. The version of the top-level structure received + * is passed down to all sub-structures. This means that the + * sub-structures must have version that are compatible with all the + * structures that use them. + * + * If you want to specify the version of the sub-structure, use + * VMSTATE_VSTRUCT(), which allows the specific sub-structure version + * to be directly specified. + */ + +#define VMSTATE_SINGLE_TEST(_field, _state, _test, _version, _info, _type) \ + { \ + .name = (stringify(_field)), .version_id = (_version), \ + .field_exists = (_test), .size = sizeof(_type), .info = &(_info), \ + .flags = VMS_SINGLE, \ + .offset = vmstate_offset_value(_state, _field, _type), \ + } + +#define VMSTATE_ARRAY(_field, _state, _num, _version, _info, _type) \ + { \ + .name = (stringify(_field)), .version_id = (_version), .num = (_num), \ + .info = &(_info), .size = sizeof(_type), .flags = VMS_ARRAY, \ + .offset = vmstate_offset_array(_state, _field, _type, _num), \ + } + +#define VMSTATE_STRUCT_TEST(_field, _state, _test, _version, _vmsd, _type) \ + { \ + .name = (stringify(_field)), .version_id = (_version), \ + .field_exists = (_test), .vmsd = &(_vmsd), .size = sizeof(_type), \ + .flags = VMS_STRUCT, \ + .offset = vmstate_offset_value(_state, _field, _type), \ + } + +#define VMSTATE_STRUCT_POINTER_V(_field, _state, _version, _vmsd, _type) \ + { \ + .name = (stringify(_field)), .version_id = (_version), \ + .vmsd = &(_vmsd), .size = sizeof(_type *), \ + .flags = VMS_STRUCT | VMS_POINTER, \ + .offset = vmstate_offset_pointer(_state, _field, _type), \ + } + +#define VMSTATE_STRUCT_ARRAY_TEST(_field, _state, _num, _test, _version, \ + _vmsd, _type) \ + { \ + .name = (stringify(_field)), .num = (_num), .field_exists = (_test), \ + .version_id = (_version), .vmsd = &(_vmsd), .size = sizeof(_type), \ + .flags = VMS_STRUCT | VMS_ARRAY, \ + .offset = vmstate_offset_array(_state, _field, _type, _num), \ + } + +#define VMSTATE_STATIC_BUFFER(_field, _state, _version, _test, _start, _size) \ + { \ + .name = (stringify(_field)), .version_id = (_version), \ + .field_exists = (_test), .size = (_size - _start), \ + .info = &slirp_vmstate_info_buffer, .flags = VMS_BUFFER, \ + .offset = vmstate_offset_buffer(_state, _field) + _start, \ + } + +#define VMSTATE_VBUFFER_UINT32(_field, _state, _version, _test, _field_size) \ + { \ + .name = (stringify(_field)), .version_id = (_version), \ + .field_exists = (_test), \ + .size_offset = vmstate_offset_value(_state, _field_size, uint32_t), \ + .info = &slirp_vmstate_info_buffer, \ + .flags = VMS_VBUFFER | VMS_POINTER, \ + .offset = offsetof(_state, _field), \ + } + +#define QEMU_BUILD_BUG_ON_STRUCT(x) \ + struct { \ + int : (x) ? -1 : 1; \ + } + +#define QEMU_BUILD_BUG_ON_ZERO(x) \ + (sizeof(QEMU_BUILD_BUG_ON_STRUCT(x)) - sizeof(QEMU_BUILD_BUG_ON_STRUCT(x))) + +/* Allocate a temporary of type 'tmp_type', set tmp->parent to _state + * and execute the vmsd on the temporary. Note that we're working with + * the whole of _state here, not a field within it. + * We compile time check that: + * That _tmp_type contains a 'parent' member that's a pointer to the + * '_state' type + * That the pointer is right at the start of _tmp_type. + */ +#define VMSTATE_WITH_TMP(_state, _tmp_type, _vmsd) \ + { \ + .name = "tmp", \ + .size = sizeof(_tmp_type) + \ + QEMU_BUILD_BUG_ON_ZERO(offsetof(_tmp_type, parent) != 0) + \ + type_check_pointer(_state, typeof_field(_tmp_type, parent)), \ + .vmsd = &(_vmsd), .info = &slirp_vmstate_info_tmp, \ + } + +#define VMSTATE_SINGLE(_field, _state, _version, _info, _type) \ + VMSTATE_SINGLE_TEST(_field, _state, NULL, _version, _info, _type) + +#define VMSTATE_STRUCT(_field, _state, _version, _vmsd, _type) \ + VMSTATE_STRUCT_TEST(_field, _state, NULL, _version, _vmsd, _type) + +#define VMSTATE_STRUCT_POINTER(_field, _state, _vmsd, _type) \ + VMSTATE_STRUCT_POINTER_V(_field, _state, 0, _vmsd, _type) + +#define VMSTATE_STRUCT_ARRAY(_field, _state, _num, _version, _vmsd, _type) \ + VMSTATE_STRUCT_ARRAY_TEST(_field, _state, _num, NULL, _version, _vmsd, \ + _type) + +#define VMSTATE_INT16_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, slirp_vmstate_info_int16, int16_t) +#define VMSTATE_INT32_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, slirp_vmstate_info_int32, int32_t) + +#define VMSTATE_UINT8_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, slirp_vmstate_info_uint8, uint8_t) +#define VMSTATE_UINT16_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, slirp_vmstate_info_uint16, uint16_t) +#define VMSTATE_UINT32_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, slirp_vmstate_info_uint32, uint32_t) + +#define VMSTATE_INT16(_f, _s) VMSTATE_INT16_V(_f, _s, 0) +#define VMSTATE_INT32(_f, _s) VMSTATE_INT32_V(_f, _s, 0) + +#define VMSTATE_UINT8(_f, _s) VMSTATE_UINT8_V(_f, _s, 0) +#define VMSTATE_UINT16(_f, _s) VMSTATE_UINT16_V(_f, _s, 0) +#define VMSTATE_UINT32(_f, _s) VMSTATE_UINT32_V(_f, _s, 0) + +#define VMSTATE_UINT16_TEST(_f, _s, _t) \ + VMSTATE_SINGLE_TEST(_f, _s, _t, 0, slirp_vmstate_info_uint16, uint16_t) + +#define VMSTATE_UINT32_TEST(_f, _s, _t) \ + VMSTATE_SINGLE_TEST(_f, _s, _t, 0, slirp_vmstate_info_uint32, uint32_t) + +#define VMSTATE_INT16_ARRAY_V(_f, _s, _n, _v) \ + VMSTATE_ARRAY(_f, _s, _n, _v, slirp_vmstate_info_int16, int16_t) + +#define VMSTATE_INT16_ARRAY(_f, _s, _n) VMSTATE_INT16_ARRAY_V(_f, _s, _n, 0) + +#define VMSTATE_BUFFER_V(_f, _s, _v) \ + VMSTATE_STATIC_BUFFER(_f, _s, _v, NULL, 0, sizeof(typeof_field(_s, _f))) + +#define VMSTATE_BUFFER(_f, _s) VMSTATE_BUFFER_V(_f, _s, 0) + +#define VMSTATE_END_OF_LIST() \ + { \ + .flags = VMS_END, \ + } + +#endif /* VMSTATE_H_ */ diff --git a/app/src/main/cpp/libslirp/test/ncsitest.c b/app/src/main/cpp/libslirp/test/ncsitest.c new file mode 100644 index 00000000..f5ee0b5a --- /dev/null +++ b/app/src/main/cpp/libslirp/test/ncsitest.c @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com) + */ + +/* + * This test verifies slirp responses to NC-SI commands. + */ + +#include +#include +#include + +#include "slirp.h" +#include "ncsi-pkt.h" + +#define NCSI_RESPONSE_CAPACITY 1024 + +static void test_ncsi_get_version_id(Slirp *slirp) +{ + slirp->mfr_id = 0xabcdef01; + + uint8_t command[] = { + /* Destination MAC */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Source MAC */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Ethertype */ + 0x88, 0xf8, + /* NC-SI Control packet header */ + 0x00, /* MC ID */ + 0x01, /* Header revision */ + 0x00, /* Reserved */ + 0x01, /* Instance ID */ + 0x15, /* Control Packet Type */ + 0x00, /* Channel ID */ + 0x00, /* Reserved */ + 0x00, /* Payload length */ + 0x00, 0x00, 0x00, 0x00, /* Reserved */ + 0x00, 0x00, 0x00, 0x00, /* Reserved */ + }; + slirp_input(slirp, command, sizeof(command)); + + const struct ncsi_rsp_gvi_pkt *gvi = (const struct ncsi_rsp_gvi_pkt *) ((const char*) slirp->opaque + ETH_HLEN); + + assert(ntohs(gvi->rsp.code) == NCSI_PKT_RSP_C_COMPLETED); + assert(ntohs(gvi->rsp.code) == NCSI_PKT_RSP_R_NO_ERROR); + assert(ntohl(gvi->mf_id) == slirp->mfr_id); + + slirp->mfr_id = 0; +} + +static void test_ncsi_oem_mlx_unsupported_command(Slirp *slirp) +{ + uint8_t command[] = { + /* Destination MAC */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Source MAC */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Ethertype */ + 0x88, 0xf8, + /* NC-SI Control packet header */ + 0x00, /* MC ID */ + 0x01, /* Header revision */ + 0x00, /* Reserved */ + 0x01, /* Instance ID */ + 0x50, /* Control Packet Type */ + 0x00, /* Channel ID */ + 0x00, /* Reserved */ + 0x08, /* Payload length */ + 0x00, 0x00, 0x00, 0x00, /* Reserved */ + 0x00, 0x00, 0x00, 0x00, /* Reserved */ + /* NC-SI OEM packet header */ + 0x00, 0x00, 0x81, 0x19, /* Manufacturer ID: Mellanox */ + /* Vendor Data */ + 0xff, /* Command Revision */ + 0xff, /* Command ID */ + 0x00, /* Parameter */ + 0x00, /* Optional data */ + }; + const struct ncsi_rsp_oem_pkt *oem = (const struct ncsi_rsp_oem_pkt *) ((const char*) slirp->opaque + ETH_HLEN); + + slirp->mfr_id = 0x00000000; + slirp_input(slirp, command, sizeof(command)); + + assert(ntohs(oem->rsp.code) == NCSI_PKT_RSP_C_UNSUPPORTED); + assert(ntohs(oem->rsp.reason) == NCSI_PKT_RSP_R_UNKNOWN); + assert(ntohl(oem->mfr_id) == 0x8119); + + slirp->mfr_id = 0x8119; + slirp_input(slirp, command, sizeof(command)); + + assert(ntohs(oem->rsp.code) == NCSI_PKT_RSP_C_UNSUPPORTED); + assert(ntohs(oem->rsp.reason) == NCSI_PKT_RSP_R_UNKNOWN); + assert(ntohl(oem->mfr_id) == 0x8119); +} + +static void test_ncsi_oem_mlx_gma(Slirp *slirp) +{ + uint8_t oob_eth_addr[ETH_ALEN] = {0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe}; + uint8_t command[] = { + /* Destination MAC */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Source MAC */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Ethertype */ + 0x88, 0xf8, + /* NC-SI Control packet header */ + 0x00, /* MC ID */ + 0x01, /* Header revision */ + 0x00, /* Reserved */ + 0x01, /* Instance ID */ + 0x50, /* Control Packet Type */ + 0x00, /* Channel ID */ + 0x00, /* Reserved */ + 0x08, /* Payload length */ + 0x00, 0x00, 0x00, 0x00, /* Reserved */ + 0x00, 0x00, 0x00, 0x00, /* Reserved */ + /* NC-SI OEM packet header */ + 0x00, 0x00, 0x81, 0x19, /* Manufacturer ID: Mellanox */ + /* Vendor Data */ + 0x00, /* Command Revision */ + 0x00, /* Command ID */ + 0x1b, /* Parameter */ + 0x00, /* Optional data */ + }; + const struct ncsi_rsp_oem_pkt *oem = (const struct ncsi_rsp_oem_pkt *) ((const char*) slirp->opaque + ETH_HLEN); + + memset(slirp->oob_eth_addr, 0, ETH_ALEN); + slirp->mfr_id = 0x8119; + slirp_input(slirp, command, sizeof(command)); + + assert(ntohs(oem->rsp.code) == NCSI_PKT_RSP_C_COMPLETED); + assert(ntohs(oem->rsp.reason) == NCSI_PKT_RSP_R_NO_ERROR); + assert(ntohl(oem->mfr_id) == slirp->mfr_id); + assert(ntohs(oem->rsp.common.length) == MLX_GMA_PAYLOAD_LEN); + assert(memcmp(slirp->oob_eth_addr, &oem->data[MLX_MAC_ADDR_OFFSET], ETH_ALEN) == 0); + assert(oem->data[MLX_GMA_STATUS_OFFSET] == 0); + + memcpy(slirp->oob_eth_addr, oob_eth_addr, ETH_ALEN); + slirp_input(slirp, command, sizeof(command)); + + assert(ntohs(oem->rsp.code) == NCSI_PKT_RSP_C_COMPLETED); + assert(ntohs(oem->rsp.reason) == NCSI_PKT_RSP_R_NO_ERROR); + assert(ntohl(oem->mfr_id) == slirp->mfr_id); + assert(ntohs(oem->rsp.common.length) == MLX_GMA_PAYLOAD_LEN); + assert(memcmp(oob_eth_addr, &oem->data[MLX_MAC_ADDR_OFFSET], ETH_ALEN) == 0); + assert(oem->data[MLX_GMA_STATUS_OFFSET] == 1); +} + +static slirp_ssize_t send_packet(const void *buf, size_t len, void *opaque) +{ + assert(len <= NCSI_RESPONSE_CAPACITY); + memcpy(opaque, buf, len); + return len; +} + +int main(int argc, char *argv[]) +{ + SlirpConfig config = { + .version = SLIRP_CONFIG_VERSION_MAX, + }; + SlirpCb callbacks = { + .send_packet = send_packet, + }; + Slirp *slirp = NULL; + uint8_t ncsi_response[NCSI_RESPONSE_CAPACITY]; + + slirp = slirp_new(&config, &callbacks, ncsi_response); + + test_ncsi_get_version_id(slirp); + test_ncsi_oem_mlx_unsupported_command(slirp); + test_ncsi_oem_mlx_gma(slirp); + + slirp_cleanup(slirp); +} diff --git a/app/src/main/cpp/libslirp/test/pingtest.c b/app/src/main/cpp/libslirp/test/pingtest.c new file mode 100644 index 00000000..247a5b10 --- /dev/null +++ b/app/src/main/cpp/libslirp/test/pingtest.c @@ -0,0 +1,490 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2021-2022 Samuel Thibault + */ + +/* + * This simple test configures slirp and tries to ping it + * + * Note: to make this example actually be able to use the outside world, you + * need to either + * - run as root + * - set /proc/sys/net/ipv4/ping_group_range to allow sending ICMP echo requests + * - run a UDP echo server on the target + */ + +#include +#include +#include +#include + +#include "libslirp.h" + +//#define _WIN32 +#ifdef _WIN32 +//#include +#include +static int slirp_inet_aton(const char *cp, struct in_addr *ia) +{ + uint32_t addr = inet_addr(cp); + if (addr == 0xffffffff) { + return 0; + } + ia->s_addr = addr; + return 1; +} +#define inet_aton slirp_inet_aton +#else +#include +#include +#include +#endif + +/* Dumb simulation tick: 100ms */ +#define TICK 100 + +static Slirp *slirp; +static bool done; +static int64_t mytime; + +/* Print a frame for debugging */ +static void print_frame(const uint8_t *data, size_t len) { + int i; + + printf("\ngot packet size %zd:\n", len); + for (i = 0; i < len; i++) { + if (i && i % 16 == 0) + printf("\n"); + printf("%s%02x", i % 16 ? " " : "", data[i]); + } + if (len % 16 != 0) + printf("\n"); + printf("\n"); +} + +/* Classical 16bit checksum */ +static void checksum(uint8_t *data, size_t size, uint8_t *cksum) { + uint32_t sum = 0; + int i; + + cksum[0] = 0; + cksum[1] = 0; + + for (i = 0; i+1 < size; i += 2) + sum += (((uint16_t) data[i]) << 8) + data[i+1]; + if (i < size) /* Odd number of bytes */ + sum += ((uint16_t) data[i]) << 8; + + sum = (sum & 0xffff) + (sum >> 16); + sum = (sum & 0xffff) + (sum >> 16); + sum = ~sum; + + cksum[0] = sum >> 8; + cksum[1] = sum; +} + +/* This is called when receiving a packet from the virtual network, for the + * guest */ +static slirp_ssize_t send_packet(const void *buf, size_t len, void *opaque) { + const uint8_t *data = buf; + + assert(len >= 14); + + if (data[12] == 0x86 && + data[13] == 0xdd) { + /* Ignore IPv6 */ + return len; + } + + print_frame(data, len); + + if (data[12] == 0x08 && + data[13] == 0x06) { + /* ARP */ + /* We expect receiving an ARP request for our address */ + + /* Ethernet address type */ + assert(data[14] == 0x00); + assert(data[15] == 0x01); + + /* IPv4 address type */ + assert(data[16] == 0x08); + assert(data[17] == 0x00); + + /* Ethernet addresses are 6 bytes long */ + assert(data[18] == 0x06); + + /* IPv4 addresses are 4 bytes long */ + assert(data[19] == 0x04); + + /* Opcode: ARP request */ + assert(data[20] == 0x00); + assert(data[21] == 0x01); + + /* Ok, reply! */ + uint8_t myframe[] = { + /*** Ethernet ***/ + /* dst */ + 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02, + /* src */ + 0x52, 0x55, 0x0a, 0x00, 0x02, 0x0e, + /* Type: ARP */ + 0x08, 0x06, + + /* ether, IPv4, */ + 0x00, 0x01, 0x08, 0x00, + /* elen, IPlen */ + 0x06, 0x04, + /* ARP reply */ + 0x00, 0x02, + + /* Our ethernet address */ + 0x52, 0x55, 0x0a, 0x00, 0x02, 0x0e, + /* Our IP address */ + 0x0a, 0x00, 0x02, 0x0e, + + /* Host ethernet address */ + 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02, + /* Host IP address */ + 0x0a, 0x00, 0x02, 0x02, + }; + + slirp_input(slirp, myframe, sizeof(myframe)); + } + + if (data[12] == 0x08 && + data[13] == 0x00) { + /* IPv4 */ + assert(len >= 14 + 20); + + /* We expect receiving the ICMP echo reply for our echo request */ + + /* IPv + hlen */ + assert(data[14] == 0x45); + + /* proto: ICMP */ + assert(data[23] == 0x01); + + /* ICMP */ + assert(len >= 14 + 20 + 8 + 4); + + /* ICMP type: reply */ + assert(data[34] == 0x00); + + /* Check the data */ + assert(data[42] == 0xde); + assert(data[43] == 0xad); + assert(data[44] == 0xbe); + assert(data[45] == 0xef); + + /* Got the answer! */ + printf("got it!\n"); + done = 1; + } + + return len; +} + +static void guest_error(const char *msg, void *opaque) { + printf("guest error %s\n", msg); +} + + +/* + * Dumb timer implementation + */ +static int64_t clock_get_ns(void *opaque) { + return mytime; +} + +struct timer { + SlirpTimerId id; + void *cb_opaque; + int64_t expire; + struct timer *next; +}; + +static struct timer *timer_queue; + +static void *timer_new_opaque(SlirpTimerId id, void *cb_opaque, void *opaque) { + struct timer *new_timer = malloc(sizeof(*new_timer)); + new_timer->id = id; + new_timer->cb_opaque = cb_opaque; + new_timer->next = NULL; + return new_timer; +} + +static void timer_free(void *_timer, void *opaque) { + struct timer *timer = _timer; + struct timer **t; + + for (t = &timer_queue; *t != NULL; *t = (*t)->next) { + if (*t == timer) { + /* Not expired yet, drop it */ + *t = timer->next; + break; + } + } + + free(timer); +} + +static void timer_mod(void *_timer, int64_t expire_time, void *opaque) { + struct timer *timer = _timer; + struct timer **t; + + timer->expire = expire_time * 1000 * 1000; + + for (t = &timer_queue; *t != NULL; *t = (*t)->next) { + if (expire_time < (*t)->expire) + break; + } + + timer->next = *t; + *t = timer; +} + +static void timer_check(Slirp *slirp) { + while (timer_queue && timer_queue->expire <= mytime) + { + struct timer *t = timer_queue; + printf("handling %p at time %lu\n", + t, (unsigned long) timer_queue->expire); + timer_queue = t->next; + slirp_handle_timer(slirp, t->id, t->cb_opaque); + } +} + +static uint32_t timer_timeout(void) { + if (timer_queue) + { + uint32_t timeout = (timer_queue->expire - mytime) / (1000 * 1000); + if (timeout < TICK) + return timeout; + } + + return TICK; +} + + +/* + * Dumb polling implementation + */ +static int npoll; +static void register_poll_fd(int fd, void *opaque) { + /* We might want to prepare for polling on fd */ + npoll++; +} + +static void unregister_poll_fd(int fd, void *opaque) { + /* We might want to clear polling on fd */ + npoll--; +} + +static void notify(void *opaque) { + /* No need for this in single-thread case */ +} + +#ifdef _WIN32 +/* select() variant */ +static fd_set readfds, writefds, exceptfds; +static int maxfd; +static int add_poll_cb(int fd, int events, void *opaque) +{ + if (events & SLIRP_POLL_IN) + FD_SET(fd, &readfds); + if (events & SLIRP_POLL_OUT) + FD_SET(fd, &writefds); + if (events & SLIRP_POLL_PRI) + FD_SET(fd, &exceptfds); + if (maxfd < fd) + maxfd = fd; + return fd; +} + +static int get_revents_cb(int idx, void *opaque) +{ + int event = 0; + if (FD_ISSET(idx, &readfds)) + event |= SLIRP_POLL_IN; + if (FD_ISSET(idx, &writefds)) + event |= SLIRP_POLL_OUT; + if (FD_ISSET(idx, &exceptfds)) + event |= SLIRP_POLL_PRI; + return event; +} + +static void dopoll(uint32_t timeout) { + int err; + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + maxfd = 0; + + slirp_pollfds_fill(slirp, &timeout, add_poll_cb, NULL); + printf("we will use timeout %u\n", (unsigned) timeout); + + struct timeval tv = { + .tv_sec = timeout / 1000, + .tv_usec = (timeout % 1000) * 1000, + }; + err = select(maxfd+1, &readfds, &writefds, &exceptfds, &tv); + + slirp_pollfds_poll(slirp, err < 0, get_revents_cb, NULL); +} +#else +/* poll() variant */ +static struct pollfd *fds; +static int cur_poll; +static int add_poll_cb(int fd, int events, void *opaque) +{ + short poll_events = 0; + + assert(cur_poll < npoll); + fds[cur_poll].fd = fd; + + if (events & SLIRP_POLL_IN) + poll_events |= POLLIN; + if (events & SLIRP_POLL_OUT) + poll_events |= POLLOUT; + if (events & SLIRP_POLL_PRI) + poll_events |= POLLPRI; + fds[cur_poll].events = poll_events; + + return cur_poll++; +} + +static int get_revents_cb(int idx, void *opaque) +{ + return fds[idx].revents; +} + +static void dopoll(uint32_t timeout) { + int err; + fds = malloc(sizeof(*fds) * npoll); + cur_poll = 0; + + slirp_pollfds_fill(slirp, &timeout, add_poll_cb, NULL); + printf("we will use timeout %u\n", (unsigned) timeout); + + err = poll(fds, cur_poll, timeout); + + slirp_pollfds_poll(slirp, err < 0, get_revents_cb, NULL); + + free(fds); +} +#endif + + +static struct SlirpCb callbacks = { + .send_packet = send_packet, + .guest_error = guest_error, + .clock_get_ns = clock_get_ns, + .timer_new_opaque = timer_new_opaque, + .timer_free = timer_free, + .timer_mod = timer_mod, + .register_poll_fd = register_poll_fd, + .unregister_poll_fd = unregister_poll_fd, + .notify = notify, +}; + + +int main(int argc, char *argv[]) { + SlirpConfig config = { + .version = 4, + .restricted = false, + .in_enabled = true, + .vnetwork.s_addr = htonl(0x0a000200), + .vnetmask.s_addr = htonl(0xffffff00), + .vhost.s_addr = htonl(0x0a000202), + .vdhcp_start.s_addr = htonl(0x0a00020f), + .vnameserver.s_addr = htonl(0x0a000203), + .disable_host_loopback = false, + .enable_emu = false, + .disable_dns = false, + }; + uint32_t timeout = 0; + + printf("Slirp version %s\n", slirp_version_string()); + +#if !defined(_WIN32) + inet_pton(AF_INET6, "fec0::", &config.vprefix_addr6); + config.vprefix_len = 64; + config.vhost6 = config.vprefix_addr6; + config.vhost6.s6_addr[15] = 2; + config.vnameserver6 = config.vprefix_addr6; + config.vnameserver6.s6_addr[15] = 2; + config.in6_enabled = true, +#endif + + slirp = slirp_new(&config, &callbacks, NULL); + + /* Send echo request */ + uint8_t myframe[] = { + /*** Ethernet ***/ + /* dst */ + 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02, + /* src */ + 0x52, 0x55, 0x0a, 0x00, 0x02, 0x0e, + /* Type: IPv4 */ + 0x08, 0x00, + + /*** IPv4 ***/ + /* vhl,tos, len */ + 0x45, 0x00, 0x00, 0x20, + /* id, off (DF) */ + 0x68, 0xd7, 0x40, 0x00, + /* ttl,pro, cksum */ + 0x40, 0x01, 0x00, 0x00, + /* src */ + 0x0a, 0x00, 0x02, 0x0e, + /* dst */ + 0x00, 0x00, 0x00, 0x00, + + /*** ICMPv4 ***/ + /* type, code, cksum */ + 0x08, 0x00, 0x00, 0x00, + /* id, seq */ + 0x01, 0xec, 0x00, 0x01, + /* data */ + 0xde, 0xad, 0xbe, 0xef, + }; + + struct in_addr in_addr = { .s_addr = htonl(0x0a000202) }; + if (argc > 1) { + if (inet_aton(argv[1], &in_addr) == 0) { + printf("usage: %s [destination IPv4 address]\n", argv[0]); + exit(EXIT_FAILURE); + } + } + uint32_t addr = ntohl(in_addr.s_addr); + myframe[30] = addr >> 24; + myframe[31] = addr >> 16; + myframe[32] = addr >> 8; + myframe[33] = addr >> 0; + + /* IPv4 header checksum */ + checksum(&myframe[14], 20, &myframe[24]); + /* ICMP header checksum */ + checksum(&myframe[34], 12, &myframe[36]); + + slirp_input(slirp, myframe, sizeof(myframe)); + + /* Wait for echo reply */ + while (!done) { + printf("time %lu\n", (unsigned long) mytime); + + timer_check(slirp); + /* Here we make the virtual time wait like the real time, but we could + * make it wait differently */ + timeout = timer_timeout(); + printf("we wish timeout %u\n", (unsigned) timeout); + + dopoll(timeout); + + /* Fake that the tick elapsed */ + mytime += TICK * 1000 * 1000; + } + + slirp_cleanup(slirp); +} diff --git a/app/src/main/cpp/sync.cpp b/app/src/main/cpp/sync.cpp new file mode 100644 index 00000000..e0d3bb53 --- /dev/null +++ b/app/src/main/cpp/sync.cpp @@ -0,0 +1,78 @@ +#include +#include + +#include "libslirp/src/libvdeslirp.h" + +extern "C" { + +JNIEXPORT void JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_initNative + (JNIEnv* env, jobject thisObject, jint mtu) { + + SlirpConfig slirpcfg; + struct vdeslirp *myslirp; + vdeslirp_init(&slirpcfg, VDE_INIT_DEFAULT); + slirpcfg.if_mtu = mtu; + + myslirp = vdeslirp_open(&slirpcfg); + + if (!myslirp) { + abort(); + } + + auto fid = env->GetFieldID(env->GetObjectClass(thisObject), "mySlirp", "J"); + env->SetLongField(thisObject, fid, reinterpret_cast(myslirp)); +} + +#define GET_MYSLIRP(env, thisObject) \ + reinterpret_cast( \ + env->GetLongField( \ + thisObject, \ + env->GetFieldID( \ + env->GetObjectClass(thisObject), \ + "mySlirp", \ + "J"))) + +JNIEXPORT void JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_finalizeNative + (JNIEnv* env, jobject thisObject) { + + auto mySlirp = GET_MYSLIRP(env, thisObject); + + if (mySlirp == nullptr) { + return; + } + + vdeslirp_close(mySlirp); + auto fid = env->GetFieldID(env->GetObjectClass(thisObject), "mySlirp", "J"); + env->SetLongField(thisObject, fid, 0L); +} + +JNIEXPORT long JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeRecv + (JNIEnv* env, jobject thisObject, jobject dbb, jlong offset, jlong count) { + + void *buf = reinterpret_cast(env->GetDirectBufferAddress(dbb)) + offset; + return vdeslirp_recv(GET_MYSLIRP(env, thisObject), buf, count); +} + +JNIEXPORT long JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeSend + (JNIEnv* env, jobject thisObject, jobject dbb, jlong offset, jlong count) { + + void *buf = reinterpret_cast(env->GetDirectBufferAddress(dbb)) + offset; + return vdeslirp_send(GET_MYSLIRP(env, thisObject), buf, count); +} + +JNIEXPORT jobject JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_getVdeFd + (JNIEnv* env, jobject thisObject) { + + auto fd = vdeslirp_fd(GET_MYSLIRP(env, thisObject)); + + const auto class_fdesc = env->FindClass("java/io/FileDescriptor"); + const auto const_fdesc = env->GetMethodID(class_fdesc, "", "()V"); + auto ret = env->NewObject(class_fdesc, const_fdesc); + + const auto field_fd = env->GetFieldID(class_fdesc, "descriptor", "I"); + env->SetIntField(ret, field_fd, fd); + + return ret; +} + +} diff --git a/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java b/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java index e05a6256..abbdb668 100644 --- a/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java +++ b/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java @@ -50,6 +50,8 @@ public class AsteroidBleManager extends BleManager { public final HashMap recvCallbacks; public HashMap sendingCharacteristics; + private int currentMtu = 244; + public AsteroidBleManager(@NonNull final Context context, SynchronizationService syncService) { super(context); mSynchronizationService = syncService; @@ -85,6 +87,10 @@ public final void setBatteryLevel(Data data) { mSynchronizationService.handleUpdateBatteryPercentage(batteryLevelEvent); } + public int getCurrentMtu() { + return currentMtu; + } + public static class BatteryLevelEvent { public int battery = 0; } @@ -93,10 +99,21 @@ public final void readCharacteristics() { readCharacteristic(batteryCharacteristic).with(((device, data) -> setBatteryLevel(data))).enqueue(); } + @Override + protected boolean shouldClearCacheWhenDisconnected() { + return true; + } + private abstract class AsteroidBleManagerGattCallback extends BleManagerGattCallback { + @Override + protected void onMtuChanged(@NonNull BluetoothGatt gatt, int mtu) { + Log.d(TAG, "MTU negotiated to " + mtu); + currentMtu = mtu; + } + /* It is a constraint of the Bluetooth library that it is required to initialize - the characteristics in the isRequiredServiceSupported() function. */ + the characteristics in the isRequiredServiceSupported() function. */ @Override public final boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) { final BluetoothGattService batteryService = gatt.getService(AsteroidUUIDS.BATTERY_SERVICE_UUID); @@ -116,6 +133,9 @@ public final boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gat for (IConnectivityService service : mSynchronizationService.getServices().values()) { BluetoothGattService bluetoothGattService = gatt.getService(service.getServiceUUID()); + if (bluetoothGattService == null) + continue; + List sendUuids = new ArrayList<>(); service.getCharacteristicUUIDs().forEach((uuid, direction) -> { if (direction == IConnectivityService.Direction.TO_WATCH) @@ -142,7 +162,7 @@ public final boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gat @Override protected final void initialize() { beginAtomicRequestQueue() - .add(requestMtu(256) // Remember, GATT needs 3 bytes extra. This will allow packet size of 244 bytes. + .add(requestMtu(517) .with((device, mtu) -> log(Log.INFO, "MTU set to " + mtu)) .fail((device, status) -> log(Log.WARN, "Requested MTU not supported: " + status))) .done(device -> log(Log.INFO, "Target initialized")) @@ -168,4 +188,4 @@ protected final void initialize() { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/asteroidos/sync/asteroid/IAsteroidDevice.java b/app/src/main/java/org/asteroidos/sync/asteroid/IAsteroidDevice.java index 3fe92ccf..0c33f66c 100644 --- a/app/src/main/java/org/asteroidos/sync/asteroid/IAsteroidDevice.java +++ b/app/src/main/java/org/asteroidos/sync/asteroid/IAsteroidDevice.java @@ -43,6 +43,7 @@ enum ConnectionState { void unregisterBleService(UUID serviceUUID); void registerCallback(UUID characteristicUUID, IServiceCallback callback); void unregisterCallback(UUID characteristicUUID); + int getMtu(); IConnectivityService getServiceByUUID(UUID uuid); HashMap getServices(); diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java new file mode 100644 index 00000000..47305a15 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java @@ -0,0 +1,141 @@ +/* + * AsteroidOSSync + * Copyright (c) 2023 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.connectivity; + +import android.content.Context; +import android.system.Os; +import android.system.OsConstants; +import android.system.StructPollfd; +import android.util.Log; + +import org.asteroidos.sync.asteroid.IAsteroidDevice; +import org.asteroidos.sync.utils.AsteroidUUIDS; + +import java.io.FileDescriptor; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.UUID; + +public class SlirpService implements IConnectivityService { + + private final IAsteroidDevice mDevice; + + private final Context mCtx; + + private final Thread slirpThread; + + private volatile int mtu; + + public SlirpService(Context ctx, IAsteroidDevice device) { + mDevice = device; + mCtx = ctx; + + slirpThread = new Thread(() -> { + FileDescriptor fd = getVdeFd(); + StructPollfd pollfd = new StructPollfd(); + pollfd.fd = fd; + pollfd.events = (short) OsConstants.POLLIN; + StructPollfd[] pollfds = new StructPollfd[] { pollfd }; + while (true) { + try { + if (Os.poll(pollfds, 1500) == 0) { + continue; + } + + ByteBuffer rx = ByteBuffer.allocateDirect(mtu); + long read = vdeRecv(rx, 0, mtu); + byte[] data = new byte[(int) read]; + rx.get(data); + mDevice.send(AsteroidUUIDS.SLIRP_OUTGOING_CHAR, data, SlirpService.this); + +// resetMtu(); + } catch (Exception e) { + Log.e("SlirpService", e.toString()); + } + } + }); + + mtu = mDevice.getMtu(); + + initNative(mtu - 14); + + mDevice.registerCallback(AsteroidUUIDS.SLIRP_INCOMING_CHAR, data -> { + resetMtu(); + + ByteBuffer tx = ByteBuffer.allocateDirect(data.length); + tx.put(data); + vdeSend(tx, 0, data.length); + }); + + slirpThread.start(); + } + + private void resetMtu() { + int newMtu = mDevice.getMtu(); + if (mtu != newMtu) { + mtu = newMtu; + + finalizeNative(); + initNative(mtu - 14); + } + } + + @Override + public void sync() { + } + + @Override + public void unsync() { + } + + @Override + public HashMap getCharacteristicUUIDs() { + HashMap chars = new HashMap<>(); + chars.put(AsteroidUUIDS.SLIRP_OUTGOING_CHAR, Direction.TO_WATCH); + chars.put(AsteroidUUIDS.SLIRP_INCOMING_CHAR, Direction.FROM_WATCH); + return chars; + } + + @Override + protected void finalize() throws Throwable { + finalizeNative(); + super.finalize(); + } + + static { + System.loadLibrary("sync"); + } + + @Override + public UUID getServiceUUID() { + return AsteroidUUIDS.SLIRP_SERVICE_UUID; + } + + private long mySlirp = 0; + + private native void initNative(int mtu); + + private native void finalizeNative(); + + private native long vdeRecv(ByteBuffer buffer, long offset, long count); + + private native long vdeSend(ByteBuffer buffer, long offset, long count); + + private native FileDescriptor getVdeFd(); +} diff --git a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java index f1894900..9a3f43c7 100644 --- a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java +++ b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java @@ -51,6 +51,7 @@ import org.asteroidos.sync.connectivity.NotificationService; import org.asteroidos.sync.connectivity.ScreenshotService; import org.asteroidos.sync.connectivity.SilentModeService; +import org.asteroidos.sync.connectivity.SlirpService; import org.asteroidos.sync.connectivity.TimeService; import org.asteroidos.sync.connectivity.WeatherService; @@ -198,6 +199,11 @@ public final void unregisterCallback(UUID characteristicUUID) { mBleMngr.recvCallbacks.remove(characteristicUUID); } + @Override + public int getMtu() { + return mBleMngr.getCurrentMtu(); + } + @Override public final IConnectivityService getServiceByUUID(UUID uuid) { return bleServices.get(uuid); @@ -285,6 +291,7 @@ public void onCreate() { registerBleService(new WeatherService(getApplicationContext(), this)); registerBleService(new ScreenshotService(getApplicationContext(), this)); registerBleService(new TimeService(getApplicationContext(), this)); + registerBleService(new SlirpService(getApplicationContext(), this)); } handleConnect(); diff --git a/app/src/main/java/org/asteroidos/sync/utils/AsteroidUUIDS.java b/app/src/main/java/org/asteroidos/sync/utils/AsteroidUUIDS.java index 7bbdf43d..0d2e2eb1 100644 --- a/app/src/main/java/org/asteroidos/sync/utils/AsteroidUUIDS.java +++ b/app/src/main/java/org/asteroidos/sync/utils/AsteroidUUIDS.java @@ -58,4 +58,9 @@ public class AsteroidUUIDS { public static final UUID NOTIFICATION_SERVICE_UUID = UUID.fromString("00009071-0000-0000-0000-00A57E401D05"); public static final UUID NOTIFICATION_UPDATE_CHAR = UUID.fromString("00009001-0000-0000-0000-00A57E401D05"); public static final UUID NOTIFICATION_FEEDBACK_CHAR = UUID.fromString("00009002-0000-0000-0000-00A57E401D05"); + + // SlirpService + public static final UUID SLIRP_SERVICE_UUID = UUID.fromString("0000A071-0000-0000-0000-00A57E401D05"); + public static final UUID SLIRP_OUTGOING_CHAR = UUID.fromString("0000A001-0000-0000-0000-00A57E401D05"); + public static final UUID SLIRP_INCOMING_CHAR = UUID.fromString("0000A002-0000-0000-0000-00A57E401D05"); } From 97694fefe87db358140643272396c23c44224a5d Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Thu, 2 May 2024 14:07:12 +0200 Subject: [PATCH 02/10] Use D-Bus over TCP for bi-directional notification handling --- app/build.gradle.kts | 6 +- app/src/main/cpp/CMakeLists.txt | 11 + app/src/main/cpp/build_depends.sh | 133 ++ app/src/main/cpp/sync.cpp | 32 +- app/src/main/java/cx/ath/matthew/cgi/CGI.java | 565 ++++++++ .../cx/ath/matthew/cgi/CGIErrorHandler.java | 41 + .../matthew/cgi/CGIHeaderSentException.java | 39 + .../cgi/CGIInvalidContentFormatException.java | 39 + .../java/cx/ath/matthew/cgi/CGITools.java | 48 + .../java/cx/ath/matthew/cgi/CheckBox.java | 46 + .../ath/matthew/cgi/DefaultErrorHandler.java | 67 + .../java/cx/ath/matthew/cgi/DisplayField.java | 46 + .../java/cx/ath/matthew/cgi/DropDown.java | 131 ++ .../main/java/cx/ath/matthew/cgi/Field.java | 38 + .../java/cx/ath/matthew/cgi/HTMLForm.java | 141 ++ .../java/cx/ath/matthew/cgi/HiddenField.java | 46 + .../cx/ath/matthew/cgi/MultipleDropDown.java | 115 ++ .../java/cx/ath/matthew/cgi/NewTable.java | 42 + .../java/cx/ath/matthew/cgi/Password.java | 46 + .../main/java/cx/ath/matthew/cgi/Radio.java | 46 + .../java/cx/ath/matthew/cgi/SubmitButton.java | 54 + .../java/cx/ath/matthew/cgi/TextArea.java | 57 + .../java/cx/ath/matthew/cgi/TextField.java | 69 + .../main/java/cx/ath/matthew/cgi/testcgi.java | 78 ++ .../main/java/cx/ath/matthew/debug/Debug.java | 617 +++++++++ .../main/java/cx/ath/matthew/debug/Debug.jpp | 597 +++++++++ .../java/cx/ath/matthew/io/DOMPrinter.java | 114 ++ .../cx/ath/matthew/io/ExecInputStream.java | 142 +++ .../cx/ath/matthew/io/ExecOutputStream.java | 137 ++ .../java/cx/ath/matthew/io/InOutCopier.java | 115 ++ .../cx/ath/matthew/io/TeeInputStream.java | 155 +++ .../cx/ath/matthew/io/TeeOutputStream.java | 141 ++ app/src/main/java/cx/ath/matthew/io/test.java | 48 + .../main/java/cx/ath/matthew/io/test2.java | 39 + .../main/java/cx/ath/matthew/io/test3.java | 45 + .../matthew/unix/NotConnectedException.java | 37 + .../cx/ath/matthew/unix/USInputStream.java | 84 ++ .../cx/ath/matthew/unix/USOutputStream.java | 69 + .../cx/ath/matthew/unix/UnixIOException.java | 44 + .../cx/ath/matthew/unix/UnixServerSocket.java | 129 ++ .../java/cx/ath/matthew/unix/UnixSocket.java | 320 +++++ .../ath/matthew/unix/UnixSocketAddress.java | 85 ++ .../main/java/cx/ath/matthew/unix/java-unix.h | 112 ++ .../java/cx/ath/matthew/unix/testclient.java | 51 + .../java/cx/ath/matthew/unix/testserver.java | 55 + .../java/cx/ath/matthew/utils/Hexdump.java | 149 +++ .../org/asteroidos/sync/MainActivity.java | 8 +- .../sync/asteroid/AsteroidBleManager.java | 4 +- .../sync/connectivity/MediaService.java | 2 + .../connectivity/NotificationService.java | 104 +- .../sync/connectivity/SlirpService.java | 112 +- .../sync/dbus/DBusNotificationService.java | 169 +++ .../sync/dbus/IDBusConnectionCallback.java | 27 + .../sync/dbus/IDBusConnectionProvider.java | 25 + .../sync/services/INotificationHandler.java | 28 + .../asteroidos/sync/services/NLService.java | 21 +- .../sync/services/SynchronizationService.java | 13 +- .../asteroidos/sync/utils/AppInfoHelper.java | 12 +- app/src/main/java/org/freedesktop/DBus.java | 490 +++++++ .../java/org/freedesktop/Notifications.java | 52 + .../freedesktop/dbus/AbstractConnection.java | 1030 +++++++++++++++ .../java/org/freedesktop/dbus/ArrayFrob.java | 173 +++ .../java/org/freedesktop/dbus/BusAddress.java | 41 + .../org/freedesktop/dbus/CallbackHandler.java | 22 + .../java/org/freedesktop/dbus/Container.java | 88 ++ .../org/freedesktop/dbus/DBusAsyncReply.java | 111 ++ .../org/freedesktop/dbus/DBusCallInfo.java | 51 + .../org/freedesktop/dbus/DBusConnection.java | 780 ++++++++++++ .../org/freedesktop/dbus/DBusInterface.java | 31 + .../freedesktop/dbus/DBusInterfaceName.java | 27 + .../java/org/freedesktop/dbus/DBusMap.java | 152 +++ .../org/freedesktop/dbus/DBusMatchRule.java | 143 +++ .../org/freedesktop/dbus/DBusMemberName.java | 27 + .../freedesktop/dbus/DBusSerializable.java | 38 + .../org/freedesktop/dbus/DBusSigHandler.java | 25 + .../java/org/freedesktop/dbus/DBusSignal.java | 259 ++++ .../freedesktop/dbus/DirectConnection.java | 251 ++++ .../org/freedesktop/dbus/EfficientMap.java | 116 ++ .../org/freedesktop/dbus/EfficientQueue.java | 107 ++ .../main/java/org/freedesktop/dbus/Error.java | 142 +++ .../org/freedesktop/dbus/ExportedObject.java | 166 +++ .../java/org/freedesktop/dbus/Gettext.java | 33 + .../org/freedesktop/dbus/InternalSignal.java | 20 + .../org/freedesktop/dbus/Marshalling.java | 626 +++++++++ .../java/org/freedesktop/dbus/Message.java | 1132 +++++++++++++++++ .../org/freedesktop/dbus/MessageReader.java | 175 +++ .../org/freedesktop/dbus/MessageWriter.java | 68 + .../java/org/freedesktop/dbus/MethodCall.java | 126 ++ .../org/freedesktop/dbus/MethodReturn.java | 69 + .../org/freedesktop/dbus/MethodTuple.java | 38 + .../java/org/freedesktop/dbus/ObjectPath.java | 23 + .../java/org/freedesktop/dbus/ObjectTree.java | 163 +++ .../main/java/org/freedesktop/dbus/Path.java | 40 + .../java/org/freedesktop/dbus/Position.java | 28 + .../dbus/RemoteInvocationHandler.java | 191 +++ .../org/freedesktop/dbus/RemoteObject.java | 56 + .../org/freedesktop/dbus/SignalTuple.java | 51 + .../org/freedesktop/dbus/StrongReference.java | 43 + .../java/org/freedesktop/dbus/Struct.java | 23 + .../java/org/freedesktop/dbus/Transport.java | 827 ++++++++++++ .../main/java/org/freedesktop/dbus/Tuple.java | 24 + .../org/freedesktop/dbus/TypeSignature.java | 37 + .../java/org/freedesktop/dbus/UInt16.java | 79 ++ .../java/org/freedesktop/dbus/UInt32.java | 83 ++ .../java/org/freedesktop/dbus/UInt64.java | 151 +++ .../java/org/freedesktop/dbus/Variant.java | 112 ++ .../java/org/freedesktop/dbus/bin/Caller.java | 83 ++ .../freedesktop/dbus/bin/CreateInterface.java | 711 +++++++++++ .../org/freedesktop/dbus/bin/DBusDaemon.java | 878 +++++++++++++ .../dbus/bin/IdentifierMangler.java | 43 + .../dbus/bin/IterableNodeList.java | 29 + .../org/freedesktop/dbus/bin/ListDBus.java | 74 ++ .../dbus/bin/NodeListIterator.java | 38 + .../freedesktop/dbus/bin/StructStruct.java | 54 + .../dbus/exceptions/DBusException.java | 26 + .../exceptions/DBusExecutionException.java | 40 + .../dbus/exceptions/FatalDBusException.java | 20 + .../dbus/exceptions/FatalException.java | 15 + .../exceptions/InternalMessageException.java | 20 + .../dbus/exceptions/MarshallingException.java | 20 + .../exceptions/MessageFormatException.java | 23 + .../MessageProtocolVersionException.java | 22 + .../dbus/exceptions/MessageTypeException.java | 22 + .../dbus/exceptions/NonFatalException.java | 15 + .../dbus/exceptions/NotConnected.java | 23 + .../exceptions/UnknownTypeCodeException.java | 21 + .../freedesktop/dbus/test/ProfileStruct.java | 32 + .../org/freedesktop/dbus/test/Profiler.java | 40 + .../dbus/test/ProfilerInstance.java | 28 + .../freedesktop/dbus/test/TestException.java | 24 + .../dbus/test/TestNewInterface.java | 26 + .../dbus/test/TestRemoteInterface.java | 57 + .../dbus/test/TestRemoteInterface2.java | 54 + .../dbus/test/TestSerializable.java | 48 + .../dbus/test/TestSignalInterface.java | 97 ++ .../dbus/test/TestSignalInterface2.java | 45 + .../org/freedesktop/dbus/test/TestStruct.java | 32 + .../freedesktop/dbus/test/TestStruct2.java | 31 + .../freedesktop/dbus/test/TestStruct3.java | 30 + .../org/freedesktop/dbus/test/TestTuple.java | 30 + .../dbus/test/TwoPartInterface.java | 29 + .../freedesktop/dbus/test/TwoPartObject.java | 18 + .../dbus/test/cross_test_client.java | 513 ++++++++ .../dbus/test/cross_test_server.java | 344 +++++ .../org/freedesktop/dbus/test/profile.java | 381 ++++++ .../java/org/freedesktop/dbus/test/test.java | 965 ++++++++++++++ .../freedesktop/dbus/test/test_low_level.java | 56 + .../dbus/test/test_p2p_client.java | 42 + .../dbus/test/test_p2p_server.java | 94 ++ .../dbus/test/two_part_test_client.java | 42 + .../dbus/test/two_part_test_server.java | 55 + .../freedesktop/dbus/types/DBusListType.java | 44 + .../freedesktop/dbus/types/DBusMapType.java | 47 + .../dbus/types/DBusStructType.java | 44 + 154 files changed, 19620 insertions(+), 91 deletions(-) create mode 100755 app/src/main/cpp/build_depends.sh create mode 100644 app/src/main/java/cx/ath/matthew/cgi/CGI.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/CGIErrorHandler.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/CGIHeaderSentException.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/CGIInvalidContentFormatException.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/CGITools.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/CheckBox.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/DefaultErrorHandler.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/DisplayField.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/DropDown.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/Field.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/HTMLForm.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/HiddenField.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/MultipleDropDown.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/NewTable.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/Password.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/Radio.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/SubmitButton.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/TextArea.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/TextField.java create mode 100644 app/src/main/java/cx/ath/matthew/cgi/testcgi.java create mode 100644 app/src/main/java/cx/ath/matthew/debug/Debug.java create mode 100644 app/src/main/java/cx/ath/matthew/debug/Debug.jpp create mode 100644 app/src/main/java/cx/ath/matthew/io/DOMPrinter.java create mode 100644 app/src/main/java/cx/ath/matthew/io/ExecInputStream.java create mode 100644 app/src/main/java/cx/ath/matthew/io/ExecOutputStream.java create mode 100644 app/src/main/java/cx/ath/matthew/io/InOutCopier.java create mode 100644 app/src/main/java/cx/ath/matthew/io/TeeInputStream.java create mode 100644 app/src/main/java/cx/ath/matthew/io/TeeOutputStream.java create mode 100644 app/src/main/java/cx/ath/matthew/io/test.java create mode 100644 app/src/main/java/cx/ath/matthew/io/test2.java create mode 100644 app/src/main/java/cx/ath/matthew/io/test3.java create mode 100644 app/src/main/java/cx/ath/matthew/unix/NotConnectedException.java create mode 100644 app/src/main/java/cx/ath/matthew/unix/USInputStream.java create mode 100644 app/src/main/java/cx/ath/matthew/unix/USOutputStream.java create mode 100644 app/src/main/java/cx/ath/matthew/unix/UnixIOException.java create mode 100644 app/src/main/java/cx/ath/matthew/unix/UnixServerSocket.java create mode 100644 app/src/main/java/cx/ath/matthew/unix/UnixSocket.java create mode 100644 app/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java create mode 100644 app/src/main/java/cx/ath/matthew/unix/java-unix.h create mode 100644 app/src/main/java/cx/ath/matthew/unix/testclient.java create mode 100644 app/src/main/java/cx/ath/matthew/unix/testserver.java create mode 100644 app/src/main/java/cx/ath/matthew/utils/Hexdump.java create mode 100644 app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationService.java create mode 100644 app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.java create mode 100644 app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.java create mode 100644 app/src/main/java/org/asteroidos/sync/services/INotificationHandler.java create mode 100644 app/src/main/java/org/freedesktop/DBus.java create mode 100644 app/src/main/java/org/freedesktop/Notifications.java create mode 100644 app/src/main/java/org/freedesktop/dbus/AbstractConnection.java create mode 100644 app/src/main/java/org/freedesktop/dbus/ArrayFrob.java create mode 100644 app/src/main/java/org/freedesktop/dbus/BusAddress.java create mode 100644 app/src/main/java/org/freedesktop/dbus/CallbackHandler.java create mode 100644 app/src/main/java/org/freedesktop/dbus/Container.java create mode 100644 app/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java create mode 100644 app/src/main/java/org/freedesktop/dbus/DBusCallInfo.java create mode 100644 app/src/main/java/org/freedesktop/dbus/DBusConnection.java create mode 100644 app/src/main/java/org/freedesktop/dbus/DBusInterface.java create mode 100644 app/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java create mode 100644 app/src/main/java/org/freedesktop/dbus/DBusMap.java create mode 100644 app/src/main/java/org/freedesktop/dbus/DBusMatchRule.java create mode 100644 app/src/main/java/org/freedesktop/dbus/DBusMemberName.java create mode 100644 app/src/main/java/org/freedesktop/dbus/DBusSerializable.java create mode 100644 app/src/main/java/org/freedesktop/dbus/DBusSigHandler.java create mode 100644 app/src/main/java/org/freedesktop/dbus/DBusSignal.java create mode 100644 app/src/main/java/org/freedesktop/dbus/DirectConnection.java create mode 100644 app/src/main/java/org/freedesktop/dbus/EfficientMap.java create mode 100644 app/src/main/java/org/freedesktop/dbus/EfficientQueue.java create mode 100644 app/src/main/java/org/freedesktop/dbus/Error.java create mode 100644 app/src/main/java/org/freedesktop/dbus/ExportedObject.java create mode 100644 app/src/main/java/org/freedesktop/dbus/Gettext.java create mode 100644 app/src/main/java/org/freedesktop/dbus/InternalSignal.java create mode 100644 app/src/main/java/org/freedesktop/dbus/Marshalling.java create mode 100644 app/src/main/java/org/freedesktop/dbus/Message.java create mode 100644 app/src/main/java/org/freedesktop/dbus/MessageReader.java create mode 100644 app/src/main/java/org/freedesktop/dbus/MessageWriter.java create mode 100644 app/src/main/java/org/freedesktop/dbus/MethodCall.java create mode 100644 app/src/main/java/org/freedesktop/dbus/MethodReturn.java create mode 100644 app/src/main/java/org/freedesktop/dbus/MethodTuple.java create mode 100644 app/src/main/java/org/freedesktop/dbus/ObjectPath.java create mode 100644 app/src/main/java/org/freedesktop/dbus/ObjectTree.java create mode 100644 app/src/main/java/org/freedesktop/dbus/Path.java create mode 100644 app/src/main/java/org/freedesktop/dbus/Position.java create mode 100644 app/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java create mode 100644 app/src/main/java/org/freedesktop/dbus/RemoteObject.java create mode 100644 app/src/main/java/org/freedesktop/dbus/SignalTuple.java create mode 100644 app/src/main/java/org/freedesktop/dbus/StrongReference.java create mode 100644 app/src/main/java/org/freedesktop/dbus/Struct.java create mode 100644 app/src/main/java/org/freedesktop/dbus/Transport.java create mode 100644 app/src/main/java/org/freedesktop/dbus/Tuple.java create mode 100644 app/src/main/java/org/freedesktop/dbus/TypeSignature.java create mode 100644 app/src/main/java/org/freedesktop/dbus/UInt16.java create mode 100644 app/src/main/java/org/freedesktop/dbus/UInt32.java create mode 100644 app/src/main/java/org/freedesktop/dbus/UInt64.java create mode 100644 app/src/main/java/org/freedesktop/dbus/Variant.java create mode 100644 app/src/main/java/org/freedesktop/dbus/bin/Caller.java create mode 100644 app/src/main/java/org/freedesktop/dbus/bin/CreateInterface.java create mode 100644 app/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java create mode 100644 app/src/main/java/org/freedesktop/dbus/bin/IdentifierMangler.java create mode 100644 app/src/main/java/org/freedesktop/dbus/bin/IterableNodeList.java create mode 100644 app/src/main/java/org/freedesktop/dbus/bin/ListDBus.java create mode 100644 app/src/main/java/org/freedesktop/dbus/bin/NodeListIterator.java create mode 100644 app/src/main/java/org/freedesktop/dbus/bin/StructStruct.java create mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java create mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java create mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java create mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java create mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java create mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java create mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java create mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java create mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java create mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java create mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java create mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/ProfileStruct.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/Profiler.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/ProfilerInstance.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestException.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestNewInterface.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface2.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestSerializable.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface2.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestStruct.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestStruct2.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestStruct3.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestTuple.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TwoPartInterface.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/TwoPartObject.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/cross_test_client.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/cross_test_server.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/profile.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/test.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/test_low_level.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/test_p2p_client.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/test_p2p_server.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/two_part_test_client.java create mode 100644 app/src/main/java/org/freedesktop/dbus/test/two_part_test_server.java create mode 100644 app/src/main/java/org/freedesktop/dbus/types/DBusListType.java create mode 100644 app/src/main/java/org/freedesktop/dbus/types/DBusMapType.java create mode 100644 app/src/main/java/org/freedesktop/dbus/types/DBusStructType.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 43176688..3c8749cb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,9 +14,6 @@ android { versionName = "0.29" ndk.abiFilters.clear() ndk.abiFilters.add("arm64-v8a") - ndk.abiFilters.add("armeabi-v7a") - ndk.abiFilters.add("x86") - ndk.abiFilters.add("x86_64") externalNativeBuild { cmake { cppFlags += "" @@ -42,7 +39,7 @@ android { srcDir("src/main/lib/powerampapi/poweramp_api_lib/res/") } jniLibs { - srcDir("src/main/cpp/lib") + srcDir("/work/android-root/lib") } } } @@ -82,4 +79,5 @@ dependencies { implementation("org.osmdroid:osmdroid-android:6.1.16") implementation("no.nordicsemi.android.support.v18:scanner:1.6.0") implementation("no.nordicsemi.android:ble:2.7.2") + implementation("com.google.guava:guava:33.1.0-android") } diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 01bde151..705b0bc6 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -14,6 +14,17 @@ project("sync") add_subdirectory(libslirp) +find_program(BASH bash) + +set(ENV{ANDROID_NDK_HOME} ${CMAKE_ANDROID_NDK}) +set(ENV{ABI} ${CMAKE_ANDROID_ARCH_ABI}) +execute_process(COMMAND ${BASH} ${CMAKE_CURRENT_SOURCE_DIR}/build_depends.sh) + +link_directories(/tmp/android-root/lib/${CMAKE_ANDROID_ARCH_ABI}) +include_directories(/tmp/android-root/include) + +set(ENV{PKG_CONFIG_PATH} /tmp/android-root/lib/${CMAKE_ANDROID_ARCH_ABI}/pkgconfig) + # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. diff --git a/app/src/main/cpp/build_depends.sh b/app/src/main/cpp/build_depends.sh new file mode 100755 index 00000000..2ddd3e14 --- /dev/null +++ b/app/src/main/cpp/build_depends.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +# +# AsteroidOSSync +# Copyright (c) 2024 AsteroidOS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +set -Eeo pipefail +ANDROID_NDK_HOME=${ANDROID_NDK_HOME:?please supply a valid \$ANDROID_SDK_HOME} +ABI=${ABI:?please supply a valid android \$ABI} +case "${ABI}" in + arm64-v8a) + LINUX_ABI=aarch64 + ;; + armeabi-v7a) + LINUX_ABI=arm + ;; + x86_64) + LINUX_ABI=x86_64 + ;; + x86) + LINUX_ABI=i686-pc + ;; + *) + exit 1 + ;; +esac +SYSROOT=${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot + +>&2 echo "Android NDK in ${ANDROID_NDK_HOME}" +export PATH=$PATH:${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin + +PREFIX=${PREFIX:-/tmp/android-root/} + +GLIB_VERSION=${GLIB_VERSION:-2.80.0} +GLIB_URL=https://download.gnome.org/sources/glib/${GLIB_VERSION%.*}/glib-${GLIB_VERSION}.tar.xz +GLIB_CACHE=${XDG_CACHE_DIR:-/tmp}/glib-${GLIB_VERSION}.tar.xz + +LIBICONV_VERSION=${LIBICONV_VERSION:-1.17} +LIBICONV_URL=https://ftp.gnu.org/pub/gnu/libiconv/libiconv-${LIBICONV_VERSION}.tar.gz +LIBICONV_CACHE=${XDG_CACHE_DIR:-/tmp}/libiconv-${LIBICONV_VERSION}.tar.gz +export CFLAGS=--sysroot="${SYSROOT}" +export CPPFLAGS=--sysroot="${SYSROOT}" +export CC=${LINUX_ABI}-linux-android21-clang +export CXX=${LINUX_ABI}-linux-android21-clang++ +export AR=llvm-ar +export RANLIB=llvm-ranlib + +pushd "$(mktemp -d)" + + [[ ! -f "${LIBICONV_CACHE}" ]] \ + && wget -O "${LIBICONV_CACHE}" "${LIBICONV_URL}" + bsdtar --strip-components=1 -xf "${LIBICONV_CACHE}" + + mkdir -p build + pushd build + + ../configure --host=${LINUX_ABI}-linux-android --with-sysroot="${SYSROOT}" --prefix="${PREFIX}" --libdir="${PREFIX}/lib/${ABI}" + make -j14 + make install + + popd # build + +popd + +pushd "$(mktemp -d)" + + [[ ! -f "${GLIB_CACHE}" ]] \ + && wget -O "${GLIB_CACHE}" "${GLIB_URL}" + bsdtar --strip-components=1 -xf "${GLIB_CACHE}" + + >&2 echo "Will build GLib" + + _CROSS_FILE=$(mktemp) + >&2 echo "Will setup cross" + cat <"${_CROSS_FILE}" +[built-in options] +c_args = ['-I${PREFIX}/include'] +c_link_args = ['-L${PREFIX}/lib/${ABI}'] + +[constants] +arch = '${LINUX_ABI}-linux-android' + +[binaries] +ar = 'llvm-ar' +c = '${LINUX_ABI}-linux-android21-clang' +as = [c] +cpp = '${LINUX_ABI}-linux-android21-clang++' +ranlib = 'llvm-ranlib' +strip = 'llvm-strip' +pkgconfig = '/usr/bin/pkg-config' +cmake = '/usr/bin/cmake' + +[properties] +sys_root = '${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot' +pkg_config_libdir = '${PREFIX}/lib/${ABI}/pkgconfig' + +[host_machine] +system = 'android' +cpu_family = '${LINUX_ABI}' +cpu = '${LINUX_ABI}' +endian = 'little' +EOF. + + patch <&2 echo "Will configure in ${PWD}/_builddir/" + >&2 meson setup ./_builddir/ ./ --cross-file="${_CROSS_FILE}" --prefix="${PREFIX}" --libdir="lib/${ABI}" + >&2 echo "Will build" + >&2 ninja -C ./_builddir/ + >&2 echo "Will install" + >&2 ninja -C ./_builddir/ install + >&2 echo "All depends ready" + +popd \ No newline at end of file diff --git a/app/src/main/cpp/sync.cpp b/app/src/main/cpp/sync.cpp index e0d3bb53..840cf4b6 100644 --- a/app/src/main/cpp/sync.cpp +++ b/app/src/main/cpp/sync.cpp @@ -1,5 +1,6 @@ #include #include +//#include #include "libslirp/src/libvdeslirp.h" @@ -46,14 +47,14 @@ JNIEXPORT void JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_finali env->SetLongField(thisObject, fid, 0L); } -JNIEXPORT long JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeRecv +JNIEXPORT jlong JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeRecv (JNIEnv* env, jobject thisObject, jobject dbb, jlong offset, jlong count) { void *buf = reinterpret_cast(env->GetDirectBufferAddress(dbb)) + offset; return vdeslirp_recv(GET_MYSLIRP(env, thisObject), buf, count); } -JNIEXPORT long JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeSend +JNIEXPORT jlong JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeSend (JNIEnv* env, jobject thisObject, jobject dbb, jlong offset, jlong count) { void *buf = reinterpret_cast(env->GetDirectBufferAddress(dbb)) + offset; @@ -75,4 +76,31 @@ JNIEXPORT jobject JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_get return ret; } +JNIEXPORT jint JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeAddUnixFwd + (JNIEnv* env, jobject thisObject, jstring path, jstring ip, jint port) { + const char *c_path = env->GetStringUTFChars(path, nullptr); + const char *c_ip = env->GetStringUTFChars(ip, nullptr); + + struct in_addr addr{ inet_addr(c_ip) }; + int rv = vdeslirp_add_unixfwd(GET_MYSLIRP(env, thisObject), const_cast(c_path), &addr, port); + + env->ReleaseStringUTFChars(path, c_path); + env->ReleaseStringUTFChars(ip, c_ip); + + return rv; +} + +JNIEXPORT jint JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeAddFwd + (JNIEnv* env, jobject thisObject, jboolean udp, jstring hostip, jint hostport, jstring ip, jint port) { + const char *c_hostip = env->GetStringUTFChars(hostip, nullptr); + const char *c_ip = env->GetStringUTFChars(ip, nullptr); + + int rv = vdeslirp_add_fwd(GET_MYSLIRP(env, thisObject), udp, (struct in_addr){inet_addr(c_hostip) }, hostport, (struct in_addr){inet_addr(c_ip) }, port); + + env->ReleaseStringUTFChars(hostip, c_hostip); + env->ReleaseStringUTFChars(ip, c_ip); + + return rv; +} + } diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGI.java b/app/src/main/java/cx/ath/matthew/cgi/CGI.java new file mode 100644 index 00000000..b63fffa8 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/CGI.java @@ -0,0 +1,565 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.Vector; +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +/** + * This is the main class you have to extend with your CGI program. + * You should implement the cgi() method. + * + * @author Matthew Johnson <src@matthew.ath.cx> + */ +abstract public class CGI +{ + private CGIErrorHandler errorhandler = new DefaultErrorHandler(); + private boolean headers_sent = false; + private HashMap headers = new HashMap(); + private Vector cookies = new Vector(); + private LinkedList pagedata = new LinkedList(); + private LinkedList rawdata = new LinkedList(); + + private native String getenv(String var); + /** MUST pass String.class and ALWAYS returns a String[] */ + private native Object[] getfullenv(Class c); + private native void setenv(String var, String value); + { + System.loadLibrary("cgi-java"); + } + + /** + * Called by CGIs to send a header to the output + * + * @param variable The header variable to set. + * @param value The value of the variable. + * + * @throws CGIHeaderSentException if the headers have already been sent. + * + * @see #flush + */ + public final void header(String variable, String value) throws CGIHeaderSentException + { + // only send headers once + if (headers_sent) throw new CGIHeaderSentException(); + + // buffer the variable (Map so that each header is only set once) + headers.put(variable.toLowerCase(), value); + } + + /** + * Sets a Cookie in the web browser, with extended attributes. + * Calls header() so must be called before sending any output. + * + * A parameter will not be sent if it is null. + * + * @param variable The cookie variable to set. + * @param value The value of the variable. + * @param path The path that the cookie will be returned for. + * @param domain The domain that the cookie will be returned for. + * @param expires The expiry date of the cookie. + * @param secure Will only send the cookie over HTTPS if this is true. + * + * @throws CGIHeaderSentException if the headers have already been sent. + * + * @see #flush + * @see #header + */ + public final void setcookie(String variable, String value, String path, String domain, Date expires, boolean secure) throws CGIHeaderSentException + { + if (headers_sent) throw new CGIHeaderSentException(); + + //Set-Cookie: NAME=VALUE; expires=DATE; + //path=PATH; domain=DOMAIN_NAME; secure + //Wdy, DD-Mon-YYYY HH:MM:SS GMT + DateFormat df = new SimpleDateFormat("E, dd-MMM-yyyy HH:mm:ss zzz"); + String cookie = variable+"="+value; + if (null != path) cookie += "; path="+path; + if (null != domain) cookie += "; domain="+domain; + if (null != expires) cookie += "; expires="+df.format(expires); + if (secure) cookie += "; secure"; + cookies.add("Set-Cookie: "+ cookie); + } + + /** + * Sets a Cookie in the web browser. + * Calls header() so must be called before sending any output. + * + * @param variable The cookie variable to set. + * @param value The value of the variable. + * + * @throws CGIHeaderSentException if the headers have already been sent. + * + * @see #flush + * @see #header + */ + public final void setcookie(String variable, String value) throws CGIHeaderSentException + { + if (headers_sent) throw new CGIHeaderSentException(); + + //Set-Cookie: NAME=VALUE; expires=DATE; + //path=PATH; domain=DOMAIN_NAME; secure + cookies.add("Set-Cookie: "+ variable+"="+value); + } + + /** + * Called by CGIs to send byte data to the output. + * The data is buffered until the CGI exits, or a call of flush. + * + * @param data The page data. + * @throws CGIInvalidContentFormatException if text data has already been sent. + * + * @see #flush + */ + public final void out(byte[] data) throws CGIInvalidContentFormatException + { + if (pagedata.size() > 0) throw new CGIInvalidContentFormatException(); + rawdata.add(data); + } + + /** + * Called by CGIs to send a string to the output. + * The data is buffered until the CGI exits, or a call of flush. + * + * @param data The page data. + * @throws CGIInvalidContentFormatException if raw data has already been sent. + * + * @see #flush + */ + public final void out(String data) throws CGIInvalidContentFormatException + { + if (rawdata.size() > 0) throw new CGIInvalidContentFormatException(); + pagedata.add(data); + } + + /** + * This will return an OutputStream that you can write data + * directly to. Calling this method will cause the output to be + * flushed and the Headers sent. At the moment this is not buffered + * and will be sent directly to the client. Subsequent calls + * to out() will appear after data written to the output stream. + * + * @see #out + * @return an OutputStream + */ + public final OutputStream getOutputStream() throws IOException + { + flush(); + return System.out; + } + + /** + * Flushes the output. + * Note that you cannot send a header after a flush. + * If you want to send both text and binary data in a page + * you may do so either side of a flush. + * + * @see #header + */ + public final void flush() throws IOException + { + if (!headers_sent) { + // don't send headers again + headers_sent = true; + // send headers + Iterator i = headers.keySet().iterator(); + while (i.hasNext()) { + String key = (String) i.next(); + String value = (String) headers.get(key); + System.out.println(key + ": " + value); + } + // send cookies + i = cookies.iterator(); + while (i.hasNext()) { + System.out.println((String) i.next()); + } + System.out.println(); + } + + // send data + if (pagedata.size() >0) { + Iterator j = pagedata.iterator(); + while (j.hasNext()) { + System.out.println((String) j.next()); + } + pagedata.clear(); + } else if (rawdata.size() > 0) { + Iterator j = rawdata.iterator(); + while (j.hasNext()) { + System.out.write((byte[]) j.next()); + } + pagedata.clear(); + } + System.out.flush(); + } + + /** + * Sets a custom exception handler. + * Gets called when an exception is thrown. + * The default error handler prints the error nicely in HTML + * and then exits gracefully. + * + * @param handler The new exception handler + */ + protected final void setErrorHandler(CGIErrorHandler handler) + { + errorhandler = handler; + } + + /** + * Override this method in your CGI program. + * + * @param POST A Map of variable =$gt; value for the POST variables. + * @param GET A Map of variable =$gt; value for the GET variables. + * @param ENV A Map of variable =$gt; value for the Webserver environment variables. + * @param COOKIES A Map of variable =$gt; value for the browser-sent cookies. + * @param params An array of parameters passed to the CGI (GET with no variable assignments) + * + * @throws Exception You can throw anything, it will be caught by the error handler. + */ + abstract protected void cgi(Map POST, Map GET, Map ENV, Map COOKIES, String[] params) throws Exception; + + /** + * Reads variables from a String like a=b&c=d to a Map {a => b, c => d}. + * + * @param s String to read from. + * @param seperator seperator character between variables (eg &) + * @param values whether or not this string has values for variables + * + * @return a Map with values, a Vector without + */ + private Object readVariables(String s, char seperator, boolean values) + { + HashMap vars = new HashMap(); + Vector varv = new Vector(); + String temp = ""; + String variable = null; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == seperator) { // new variable + if (null != temp) temp = temp.trim(); + if (values) { + if (variable == null) {variable = temp; temp = "";} + else variable.trim(); + if (!variable.equals("")) { + Object o = vars.get(variable); + if (o == null) + vars.put(variable.trim(), temp); + else if (o instanceof String) { + LinkedList l = new LinkedList(); + l.add(o); + l.add(temp); + vars.put(variable.trim(), l); + } else if (o instanceof LinkedList) + ((LinkedList) o).add(temp); + } + temp = ""; + } + else { + varv.add(temp); + temp = ""; + } + variable = null; + continue; + } + if (values && c == '=') { + variable = temp; + temp = ""; + continue; + } + switch (c) { + case '%': // escaped character + try { + char a = s.charAt(++i); + char b = s.charAt(++i); + int ch = 0; + switch (a) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + ch += 0x10 * (a - '0'); + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + ch += 0x10 * (a - 'a' + 0xa); + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + ch += 0x10 * (a - 'A' + 0xA); + break; + } + switch (b) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + ch += (b - '0'); + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + ch += (b - 'a' + 0xa); + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + ch += (b - 'A' + 0xA); + break; + } + temp += (char) ch; + } catch (StringIndexOutOfBoundsException SIOOBe) { + // this means someone has included an invalid escape sequence. + // Invalid URIs can just be thrown on the floor. + } + break; + // + is a space + case '+': + temp += ' '; + break; + default: + temp += c; + } + } + if (values) { + if (variable == null) {variable = temp; temp = "";} + else variable.trim(); + //out("DEBUG variable read: "+variable+"/"+temp); + if (!variable.equals("")) { + Object o = vars.get(variable); + if (o == null) + vars.put(variable.trim(), temp); + else if (o instanceof String) { + LinkedList l = new LinkedList(); + l.add(o); + l.add(temp); + vars.put(variable.trim(), l); + } else if (o instanceof LinkedList) + ((LinkedList) o).add(temp); + } + + return vars; + } + else { + varv.add(temp); + return varv; + } + } + + /** + * Sets up the POST variables + */ + private Map getPOST() + { + try { + String s = ""; + while(System.in.available() > 0) + s += (char) System.in.read(); + //out("DEBUG: POST STRING: "+s); + return (Map) readVariables(s, '&', true); + } catch (IOException IOe) { + try { + out("ERROR: IOException: "+IOe); + } catch (CGIInvalidContentFormatException CGIICFe) { + System.err.println("ERROR: IOException: "+IOe); + } + return new HashMap(); + } + } + + /** + * Sets up the COOKIEs + */ + private Map getCOOKIE() + { + String s = getenv("HTTP_COOKIE"); + if (null == s) + return new HashMap(); + else + return (Map) readVariables(s, ';', true); + } + + /** + * Sets up the GET variables + */ + private Map getGET() + { + String s = getenv("QUERY_STRING"); + if (null == s) + return new HashMap(); + else + return (Map) readVariables(s, '&', true); + } + + /** + * Sets up the ENV variables + */ + private Map getENV() + { + Map m = new HashMap(); + String[] env = (String[]) getfullenv(String.class); + for (int i = 0; i < env.length; i++){ + if (null == env[i]) continue; + String[] e = env[i].split("="); + if (1 == e.length) + m.put(e[0], ""); + else + m.put(e[0], e[1]); + } + +/* + m.put("SERVER_SOFTWARE", getenv("SERVER_SOFTWARE")); + m.put("SERVER_NAME", getenv("SERVER_NAME")); + m.put("GATEWAY_INTERFACE", getenv("GATEWAY_INTERFACE")); + m.put("SERVER_PROTOCOL", getenv("SERVER_PROTOCOL")); + m.put("SERVER_PORT", getenv("SERVER_PORT")); + m.put("REQUEST_METHOD", getenv("REQUEST_METHOD")); + m.put("PATH_INFO", getenv("PATH_INFO")); + m.put("PATH_TRANSLATED", getenv("PATH_TRANSLATED")); + m.put("SCRIPT_NAME", getenv("SCRIPT_NAME")); + m.put("QUERY_STRING", getenv("QUERY_STRING")); + m.put("REMOTE_HOST", getenv("REMOTE_HOST")); + m.put("REMOTE_ADDR", getenv("REMOTE_ADDR")); + m.put("AUTH_TYPE", getenv("AUTH_TYPE")); + m.put("REMOTE_USER", getenv("REMOTE_USER")); + m.put("REMOTE_IDENT", getenv("REMOTE_IDENT")); + m.put("CONTENT_TYPE", getenv("CONTENT_TYPE")); + m.put("CONTENT_LENGTH", getenv("CONTENT_LENGTH")); + m.put("HTTP_ACCEPT", getenv("HTTP_ACCEPT")); + m.put("HTTP_USER_AGENT", getenv("HTTP_USER_AGENT")); + m.put("HTTP_COOKIE", getenv("HTTP_COOKIE")); + m.put("HTTP_ACCEPT_CHARSET", getenv("HTTP_ACCEPT_CHARSET")); + m.put("HTTP_ACCEPT_ENCODING", getenv("HTTP_ACCEPT_ENCODING")); + m.put("HTTP_CACHE_CONTROL", getenv("HTTP_CACHE_CONTROL")); + m.put("HTTP_REFERER", getenv("HTTP_REFERER")); + m.put("HTTP_X_FORWARDED_FOR", getenv("HTTP_X_FORWARDED_FOR")); + m.put("HTTP_HOST", getenv("HTTP_HOST")); + m.put("REQUEST_URI", getenv("REQUEST_URI")); + m.put("DOCUMENT_ROOT", getenv("DOCUMENT_ROOT")); + m.put("PATH", getenv("PATH")); + m.put("SERVER_ADDR", getenv("SERVER_ADDR")); + m.put("SCRIPT_FILENAME", getenv("SCRIPT_FILENAME")); + m.put("HTTP_COOKIE2", getenv("HTTP_COOKIE2")); + m.put("HTTP_CONNECTION", getenv("HTTP_CONNECTION")); + m.put("LANG", getenv("LANG")); + m.put("REDIRECT_LANG", getenv("REDIRECT_LANG")); + */ + return m; + } + + /** + * Sets up the param variables + */ + private String[] getParams(String args) + { + Vector v = (Vector) readVariables(args, ',', false); + String[] params = new String[v.size()]; + Iterator i = v.iterator(); + for (int j = 0; j < params.length; j++) + params[j] = (String) i.next(); + return params; + } + + /** + * This method sets up all the CGI variables and calls the cgi() method, then writes out the page data. + */ + public final void doCGI(String[] args) + { + CGI cgiclass = null; + // wrap everything in a try, we need to handle all our own errors. + try { + // setup the CGI variables + Map POST = getPOST(); + Map GET = getGET(); + Map ENV = getENV(); + Map COOKIE = getCOOKIE(); + String[] params = new String[] {}; + if (args.length >= 1) + params = getParams(args[0]); + + // instantiate CGI class + /* Class c = Class.forName(args[0]); + cgiclass = (CGI) c.newInstance(); */ + + // set default headers + /*cgiclass.*/header("Content-type", "text/html"); + + // execute the CGI + /*cgiclass.*/cgi(POST, GET, ENV, COOKIE, params); + + // send the output / remaining output + /*cgiclass.*/flush(); + } + + // yes, we really want to do this. CGI programs can't send errors. Print nicely to the screen. + catch (Exception e) { + errorhandler.print(/*null == cgiclass ? false : cgiclass.*/headers_sent, e); + } + catch (Throwable t) { + t.printStackTrace(); // this is bad enough to produce stderr errors + errorhandler.print(/*null == cgiclass ? false : cgiclass.*/headers_sent, new Exception(t.toString())); + } + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGIErrorHandler.java b/app/src/main/java/cx/ath/matthew/cgi/CGIErrorHandler.java new file mode 100644 index 00000000..e0798cb7 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/CGIErrorHandler.java @@ -0,0 +1,41 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +/** + * Interface to handle exceptions in the CGI. + */ +public interface CGIErrorHandler +{ + /** + * This is called if an exception is not caught in the CGI. + * It should handle printing the error message nicely to the user, + * and then exit gracefully. + */ + public void print(boolean headers_sent, Exception e); +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGIHeaderSentException.java b/app/src/main/java/cx/ath/matthew/cgi/CGIHeaderSentException.java new file mode 100644 index 00000000..4df8cc70 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/CGIHeaderSentException.java @@ -0,0 +1,39 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +/** + * Thrown if the headers have already been sent and CGI.header is called. + */ +public class CGIHeaderSentException extends Exception +{ + public CGIHeaderSentException() + { + super("Headers already sent by CGI"); + } +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGIInvalidContentFormatException.java b/app/src/main/java/cx/ath/matthew/cgi/CGIInvalidContentFormatException.java new file mode 100644 index 00000000..281533d3 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/CGIInvalidContentFormatException.java @@ -0,0 +1,39 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +/** + * Thrown if both raw and text data are set in the same page. + */ +public class CGIInvalidContentFormatException extends Exception +{ + public CGIInvalidContentFormatException() + { + super("Cannot send both raw and text data"); + } +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGITools.java b/app/src/main/java/cx/ath/matthew/cgi/CGITools.java new file mode 100644 index 00000000..9a379459 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/CGITools.java @@ -0,0 +1,48 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +abstract class CGITools +{ + /** + * Escape a character in a string. + * @param in String to escape in. + * @param c Character to escape. + * @return in with c replaced with \c + */ + public static String escapeChar(String in, char c) + { + String out = ""; + for (int i = 0; i < in.length(); i++) { + if (in.charAt(i) == c) out += '\\'; + out += in.charAt(i); + } + return out; + } +} + diff --git a/app/src/main/java/cx/ath/matthew/cgi/CheckBox.java b/app/src/main/java/cx/ath/matthew/cgi/CheckBox.java new file mode 100644 index 00000000..350390d4 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/CheckBox.java @@ -0,0 +1,46 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public class CheckBox extends Field +{ + boolean checked; + public CheckBox(String name, String label, boolean checked) + { + this.name = name; + this.label = label; + this.checked = checked; + } + protected String print() + { + return ""; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/DefaultErrorHandler.java b/app/src/main/java/cx/ath/matthew/cgi/DefaultErrorHandler.java new file mode 100644 index 00000000..f5b812fd --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/DefaultErrorHandler.java @@ -0,0 +1,67 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +/** + * Interface to handle exceptions in the CGI. + */ +public class DefaultErrorHandler implements CGIErrorHandler +{ + /** + * This is called if an exception is not caught in the CGI. + * It should handle printing the error message nicely to the user, + * and then exit gracefully. + */ + public void print(boolean headers_sent, Exception e) + { + if (!headers_sent) { + System.out.println("Content-type: text/html"); + System.out.println(""); + System.out.println(""); + System.out.println(""); + System.out.println("Exception in CGI"); + System.out.println(""); + } + System.out.println("
"); + System.out.println("

"+e.getClass().toString()+"

"); + System.out.println("

"); + System.out.println("Exception Message: "+e.getMessage()); + System.out.println("

"); + System.out.println("

"); + System.out.println("Stack Trace:"); + System.out.println("

"); + System.out.println("
");
+      e.printStackTrace(System.out);
+      System.out.println("
"); + System.out.println("
"); + if (!headers_sent) { + System.out.println(""); + } + System.exit(1); + } +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/DisplayField.java b/app/src/main/java/cx/ath/matthew/cgi/DisplayField.java new file mode 100644 index 00000000..ec62a7ff --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/DisplayField.java @@ -0,0 +1,46 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public class DisplayField extends Field +{ + String value; + public DisplayField(String label, String value) + { + this.name = ""; + this.label = label; + this.value = value; + } + protected String print() + { + return value; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/DropDown.java b/app/src/main/java/cx/ath/matthew/cgi/DropDown.java new file mode 100644 index 00000000..bdc45346 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/DropDown.java @@ -0,0 +1,131 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +import java.util.List; + +public class DropDown extends Field +{ + Object[] values; + Object defval; + boolean indexed = false; + /** + * Create a new DropDown list. + * + * @param name The HTML field name. + * @param label The label to display + * @param values The values for the drop down list + * @param defval If this parameter is set then this element will be selected by default. + * @param indexed If this is set to true, then indexes will be returned, rather than values. + */ + public DropDown(String name, String label, Object[] values, Object defval, boolean indexed) + { + this.name = name; + this.label = label; + this.values = values; + this.indexed = indexed; + this.defval = defval; + } + /** + * Create a new DropDown list. + * + * @param name The HTML field name. + * @param label The label to display + * @param values The values for the drop down list + * @param defval If this parameter is set then this element will be selected by default. + * @param indexed If this is set to true, then indexes will be returned, rather than values. + */ + public DropDown(String name, String label, Object[] values, int defval, boolean indexed) + { + this.name = name; + this.label = label; + this.values = values; + if (defval < 0) + this.defval = null; + else + this.defval = values[defval]; + this.indexed = indexed; + } + /** + * Create a new DropDown list. + * + * @param name The HTML field name. + * @param label The label to display + * @param values The values for the drop down list + * @param defval If this parameter is set then this element will be selected by default. + * @param indexed If this is set to true, then indexes will be returned, rather than values. + */ + public DropDown(String name, String label, List values, Object defval, boolean indexed) + { + this.name = name; + this.label = label; + this.values = (Object[]) values.toArray(new Object[] {}); + this.defval = defval; + this.indexed = indexed; + } + /** + * Create a new DropDown list. + * + * @param name The HTML field name. + * @param label The label to display + * @param values The values for the drop down list + * @param defval If this parameter is set then this element will be selected by default. + * @param indexed If this is set to true, then indexes will be returned, rather than values. + */ + public DropDown(String name, String label, List values, int defval, boolean indexed) + { + this.name = name; + this.label = label; + this.values = (Object[]) values.toArray(new Object[] {}); + if (defval < 0) + this.defval = null; + else + this.defval = values.get(defval); + this.indexed = indexed; + } + protected String print() + { + String s = ""; + s += "\n"; + return s; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/Field.java b/app/src/main/java/cx/ath/matthew/cgi/Field.java new file mode 100644 index 00000000..d859952b --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/Field.java @@ -0,0 +1,38 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public abstract class Field +{ + protected String name; + protected String label; + protected abstract String print(); +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/HTMLForm.java b/app/src/main/java/cx/ath/matthew/cgi/HTMLForm.java new file mode 100644 index 00000000..0a397399 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/HTMLForm.java @@ -0,0 +1,141 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +import java.util.Iterator; +import java.util.Vector; + + +/** + * Class to manage drawing HTML forms + */ +public class HTMLForm +{ + private String target; + private String submitlabel; + private String tableclass; + private Vector fields; + private boolean post = true; + + /** + * @param target The module to submit to + */ + public HTMLForm(String target) + { + this(target, "Submit", null); + } + + /** + * @param target The module to submit to + * @param submitlabel The string to display on the submit button + */ + public HTMLForm(String target, String submitlabel) + { + this(target, submitlabel, null); + } + + /** + * @param target The module to submit to + * @param submitlabel The string to display on the submit button + * @param tableclass The class= parameter for the generated table + */ + public HTMLForm(String target, String submitlabel, String tableclass) + { + this.target = target; + this.submitlabel = submitlabel; + this.tableclass = tableclass; + fields = new Vector(); + } + + /** + * Add a field to be displayed in the form. + * + * @param field A Field subclass. + */ + public void addField(Field field) + { + fields.add(field); + } + + /** + * Set GET method rather than POST + * @param enable Enable/Disable GET + */ + public void setGET(boolean enable) + { + post = !enable; + } + + /** + * Shows the form. + * @param cgi The CGI instance that is handling output + */ + public void display(CGI cgi) + { + try { + cgi.out("
"); + if (null == tableclass) + cgi.out(""); + else + cgi.out("
"); + + Iterator i = fields.iterator(); + while (i.hasNext()) { + Field f = (Field) i.next(); + if (f instanceof NewTable) { + cgi.out(f.print()); + } + if (!(f instanceof HiddenField) && !(f instanceof SubmitButton) && !(f instanceof NewTable)) { + cgi.out(" "); + cgi.out(" "); + cgi.out(" "); + cgi.out(" "); + } + } + cgi.out(" "); + cgi.out(" "); + cgi.out(" "); + cgi.out("
"+f.label+""+f.print()+"
"); + i = fields.iterator(); + while (i.hasNext()) { + Field f = (Field) i.next(); + if (f instanceof HiddenField || f instanceof SubmitButton) { + cgi.out(" "+f.print()); + } + } + cgi.out(" "); + cgi.out("
"); + cgi.out("
"); + } catch (CGIInvalidContentFormatException CGIICFe) {} + } +} + + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/HiddenField.java b/app/src/main/java/cx/ath/matthew/cgi/HiddenField.java new file mode 100644 index 00000000..523f85bc --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/HiddenField.java @@ -0,0 +1,46 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public class HiddenField extends Field +{ + String value; + public HiddenField(String name, String value) + { + this.name = name; + this.label = ""; + this.value = value; + } + protected String print() + { + return ""; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/MultipleDropDown.java b/app/src/main/java/cx/ath/matthew/cgi/MultipleDropDown.java new file mode 100644 index 00000000..39904bdf --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/MultipleDropDown.java @@ -0,0 +1,115 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +/* + * + * TODO To change the template for this generated file go to + * Window - Preferences - Java - Code Style - Code Templates + */ +package cx.ath.matthew.cgi; + +import java.util.List; + +/** + * @author Agent + * + * TODO To change the template for this generated type comment go to + * Window - Preferences - Java - Code Style - Code Templates + */ +public class MultipleDropDown extends DropDown { + + /** + * @param name + * @param label + * @param values + * @param defval + * @param indexed + */ + public MultipleDropDown(String name, String label, String[] values, + String defval, boolean indexed) { + super(name, label, values, defval, indexed); + // TODO Auto-generated constructor stub + } + + /** + * @param name + * @param label + * @param values + * @param defval + * @param indexed + */ + public MultipleDropDown(String name, String label, String[] values, + int defval, boolean indexed) { + super(name, label, values, defval, indexed); + // TODO Auto-generated constructor stub + } + + /** + * @param name + * @param label + * @param values + * @param defval + * @param indexed + */ + public MultipleDropDown(String name, String label, List values, + String defval, boolean indexed) { + super(name, label, values, defval, indexed); + // TODO Auto-generated constructor stub + } + + /** + * @param name + * @param label + * @param values + * @param defval + * @param indexed + */ + public MultipleDropDown(String name, String label, List values, int defval, + boolean indexed) { + super(name, label, values, defval, indexed); + // TODO Auto-generated constructor stub + } + + protected String print() + { + String s = ""; + s += "\n"; + return s; + } + +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/NewTable.java b/app/src/main/java/cx/ath/matthew/cgi/NewTable.java new file mode 100644 index 00000000..1638b74f --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/NewTable.java @@ -0,0 +1,42 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; +public class NewTable extends Field { + + private String name; + private String cssClass; + + public NewTable (String name, String css) { + this.name = name; + this.cssClass = css; + } + + protected String print() { + return "\n"; + } +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/Password.java b/app/src/main/java/cx/ath/matthew/cgi/Password.java new file mode 100644 index 00000000..046fc0c0 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/Password.java @@ -0,0 +1,46 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public class Password extends Field +{ + String defval; + public Password(String name, String label, String defval) + { + this.name = name; + this.label = label; + this.defval = defval; + } + protected String print() + { + return ""; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/Radio.java b/app/src/main/java/cx/ath/matthew/cgi/Radio.java new file mode 100644 index 00000000..3a0d80e5 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/Radio.java @@ -0,0 +1,46 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public class Radio extends Field +{ + boolean checked; + public Radio(String name, String label, boolean checked) + { + this.name = name; + this.label = label; + this.checked = checked; + } + protected String print() + { + return ""; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/SubmitButton.java b/app/src/main/java/cx/ath/matthew/cgi/SubmitButton.java new file mode 100644 index 00000000..ff97e1ce --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/SubmitButton.java @@ -0,0 +1,54 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +/* + * + * TODO To change the template for this generated file go to + * Window - Preferences - Java - Code Style - Code Templates + */ +package cx.ath.matthew.cgi; + +/** + * @author Agent + * + * TODO To change the template for this generated type comment go to + * Window - Preferences - Java - Code Style - Code Templates + */ +public class SubmitButton extends Field { + + public SubmitButton(String name, String label) { + this.name = name; + this.label = label; + } + /* (non-Javadoc) + * @see cx.ath.matthew.cgi.Field#print() + */ + protected String print() { + return ""; + } + +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/TextArea.java b/app/src/main/java/cx/ath/matthew/cgi/TextArea.java new file mode 100644 index 00000000..6a924c48 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/TextArea.java @@ -0,0 +1,57 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public class TextArea extends Field +{ + String defval; + int cols; + int rows; + public TextArea(String name, String label, String defval) + { + this(name, label, defval, 30, 4); + } + public TextArea(String name, String label, String defval, int cols, int rows) + { + this.name = name; + this.label = label; + if (null == defval) + this.defval = ""; + else + this.defval = defval; + this.cols = cols; + this.rows = rows; + } + protected String print() + { + return ""; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/TextField.java b/app/src/main/java/cx/ath/matthew/cgi/TextField.java new file mode 100644 index 00000000..124ce3ef --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/TextField.java @@ -0,0 +1,69 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + + +public class TextField extends Field +{ + String defval; + int length; + public TextField(String name, String label) + { + this.name = name; + this.label = label; + this.defval = ""; + this.length = 0; + } + public TextField(String name, String label, String defval) + { + this.name = name; + this.label = label; + if (null == defval) + this.defval = ""; + else + this.defval = defval; + this.length = 0; + } + public TextField(String name, String label, String defval, int length) + { + this.name = name; + this.label = label; + if (null == defval) + this.defval = ""; + else + this.defval = defval; + this.length = length; + } + protected String print() + { + return ""; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/testcgi.java b/app/src/main/java/cx/ath/matthew/cgi/testcgi.java new file mode 100644 index 00000000..563447d9 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/testcgi.java @@ -0,0 +1,78 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +import java.util.Iterator; +import java.util.Map; + +class testcgi extends CGI +{ + protected void cgi(Map POST, Map GET, Map ENV, Map COOKIE, String[] params) throws Exception + { + header("Content-type", "text/plain"); + setcookie("testcgi", "You have visited us already"); + out("This is a test CGI program"); + out("These are the params:"); + for (int i=0; i < params.length; i++) + out("-- "+params[i]); + + out("These are the POST vars:"); + Iterator i = POST.keySet().iterator(); + while (i.hasNext()) { + String s = (String) i.next(); + out("-- "+s+" => "+POST.get(s)); + } + + out("These are the GET vars:"); + i = GET.keySet().iterator(); + while (i.hasNext()) { + String s = (String) i.next(); + out("-- "+s+" => "+GET.get(s)); + } + + out("These are the ENV vars:"); + i = ENV.keySet().iterator(); + while (i.hasNext()) { + String s = (String) i.next(); + out("-- "+s+" => "+ENV.get(s)); + } + + out("These are the COOKIEs:"); + i = COOKIE.keySet().iterator(); + while (i.hasNext()) { + String s = (String) i.next(); + out("-- "+s+" => "+COOKIE.get(s)); + } + } + + public static void main(String[] args) + { + CGI cgi = new testcgi(); + cgi.doCGI(args); + } +} diff --git a/app/src/main/java/cx/ath/matthew/debug/Debug.java b/app/src/main/java/cx/ath/matthew/debug/Debug.java new file mode 100644 index 00000000..22e8c8cc --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/debug/Debug.java @@ -0,0 +1,617 @@ +/* Copyright (C) 1991-2014 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ +/* This header is separate from features.h so that the compiler can + include it implicitly at the start of every compilation. It must + not itself include or any other header that includes + because the implicit include comes before any feature + test macros that may be defined in a source file before it first + explicitly includes a system header. GCC knows the name of this + header in order to preinclude it. */ +/* glibc's intent is to support the IEC 559 math functionality, real + and complex. If the GCC (4.9 and later) predefined macros + specifying compiler intent are available, use them to determine + whether the overall intent is to support these features; otherwise, + presume an older compiler has intent to support these features and + define these macros by default. */ +/* wchar_t uses ISO/IEC 10646 (2nd ed., published 2011-03-15) / + Unicode 6.0. */ +/* We do not support C11 . */ +/* + * Java Debug Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.debug; +import cx.ath.matthew.utils.Hexdump; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +/** + Add debugging to your program, has support for large projects with multiple + classes and debug levels per class. Supports optional enabling of debug + per-level per-class and debug targets of files, Streams or stderr. + Also supports timing between debug outputs, printing of stack traces for Throwables + and files/line numbers on each message. +

+ Debug now automatically figures out which class it was called from, so all + methods passing in the calling class are deprecated. +

+

+ The defaults are to print all messages to stderr with class and method name. +

+

+ Should be called like this: +

+
+   if (Debug.debug) Debug.print(Debug.INFO, "Debug Message");
+  
+ */ +public class Debug +{ + /** + This interface can be used to provide custom printing filters + for certain classes. + */ + public static interface FilterCommand + { + /** + Called to print debug messages with a custom filter. + @param output The PrintStream to output to. + @param level The debug level of this message. + @param location The textual location of the message. + @param extra Extra information such as timing details. + @param message The debug message. + @param lines Other lines of a multiple-line debug message. + */ + public void filter(PrintStream output, int level, String location, String extra, String message, String[] lines); + } + /** Highest priority messages */ + public static final int CRIT = 1; + /** Error messages */ + public static final int ERR = 2; + /** Warnings */ + public static final int WARN = 3; + /** Information */ + public static final int INFO = 4; + /** Debug messages */ + public static final int DEBUG = 5; + /** Verbose debug messages */ + public static final int VERBOSE = 6; + /** Set this to false to disable compilation of Debug statements */ + public static final boolean debug = false; + /** The current output stream (defaults to System.err) */ + public static PrintStream debugout = System.err; + private static Properties prop = null; + private static boolean timing = false; + private static boolean ttrace = false; + private static boolean lines = false; + private static boolean hexdump = false; + private static long last = 0; + private static int balen = 36; + private static int bawidth = 80; + private static Class saveclass = null; + //TODO: 1.5 private static Map, FilterCommand> filterMap = new HashMap, FilterCommand>(); + private static Map filterMap = new HashMap(); + /** + Set properties to configure debugging. + Format of properties is class => level, e.g. +
+      cx.ath.matthew.io.TeeOutputStream = INFO
+      cx.ath.matthew.io.DOMPrinter = DEBUG
+     
+ The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which + correspond to all messages up to that level. The special words YES, ALL and TRUE + cause all messages to be printed regardless of level. All other terms disable + messages for that class. CRIT and ERR messages are always printed if debugging is enabled + unless explicitly disabled. + The special class name ALL can be used to set the default level for all classes. + @param prop Properties object to use. + */ + public static void setProperties(Properties prop) + { + Debug.prop = prop; + } + /** + Read which class to debug on at which level from the given File. + Syntax the same as Java Properties files: +
+     <class> = <debuglevel>
+     
+ E.G. +
+      cx.ath.matthew.io.TeeOutputStream = INFO
+      cx.ath.matthew.io.DOMPrinter = DEBUG
+     
+ The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which + correspond to all messages up to that level. The special words YES, ALL and TRUE + cause all messages to be printed regardless of level. All other terms disable + messages for that class. CRIT and ERR messages are always printed if debugging is enabled + unless explicitly disabled. + The special class name ALL can be used to set the default level for all classes. + @param f File to read from. + */ + public static void loadConfig(File f) throws IOException + { + prop = new Properties(); + prop.load(new FileInputStream(f)); + } + /** @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. */ + //TODO: 1.5 @Deprecated() + public static boolean debugging(Class c, int loglevel) + { + if (debug) { + if (null == c) return true; + return debugging(c.getName(), loglevel); + } + return false; + } + public static boolean debugging(String s, int loglevel) + { + if (debug) { + try { + if (null == s) return true; + if (null == prop) return loglevel <= VERBOSE; + String d = prop.getProperty(s); + if (null == d || "".equals(d)) d = prop.getProperty("ALL"); + if (null == d) return loglevel <= ERR; + if ("".equals(d)) return loglevel <= ERR; + d = d.toLowerCase(); + if ("true".equals(d)) return true; + if ("yes".equals(d)) return true; + if ("all".equals(d)) return true; + if ("verbose".equals(d)) return loglevel <= VERBOSE; + if ("debug".equals(d)) return loglevel <= DEBUG; + if ("info".equals(d)) return loglevel <= INFO; + if ("warn".equals(d)) return loglevel <= WARN; + if ("err".equals(d)) return loglevel <= ERR; + if ("crit".equals(d)) return loglevel <= CRIT; + int i = Integer.parseInt(d); return i >= loglevel; + } catch (Exception e) { return false; } + } + return false; + } + /** + Output to the given Stream */ + public static void setOutput(PrintStream p) throws IOException + { + debugout = p; + } + /** + Output to the given file */ + public static void setOutput(String filename) throws IOException + { + debugout = new PrintStream(new FileOutputStream(filename, true)); + } + /** + Output to the default debug.log */ + public static void setOutput() throws IOException { + setOutput("./debug.log"); + } + /** + Log at DEBUG + @param d The object to log */ + public static void print(Object d) + { + if (debug) { + if (d instanceof String) + print(DEBUG, (String) d); + else if (d instanceof Throwable) + print(DEBUG, (Throwable) d); + else if (d instanceof byte[]) + print(DEBUG, (byte[]) d); + else if (d instanceof Map) + printMap(DEBUG, (Map) d); + else print(DEBUG, d); + } + } + /** + Log at DEBUG + @param o The object doing the logging + @param d The object to log + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, Object d) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(d); + } + } + /** + Log an Object + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param d The object to log with d.toString() + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, Object d) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, d); + } + } + /** + Log a String + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param s The log message + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, String s) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, s); + } + } + /** + Log a Throwable + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param t The throwable to log with .toString and .printStackTrace + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, Throwable t) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, t); + } + } + /** + Log a Throwable + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param t The throwable to log with .toString and .printStackTrace + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, Throwable t) + { + if (debug) { + saveclass = c; + print(loglevel, t); + } + } + /** + Log a Throwable + @param loglevel The level to log at (DEBUG, WARN, etc) + @param t The throwable to log with .toString and .printStackTrace + @see #setThrowableTraces to turn on stack traces. + */ + public static void print(int loglevel, Throwable t) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + String[] lines = null; + if (ttrace) { + StackTraceElement[] ste = t.getStackTrace(); + lines = new String[ste.length]; + for (int i = 0; i < ste.length; i++) + lines[i] = "\tat "+ste[i].toString(); + } + _print(t.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, t.toString(), lines); + } + } + } + /** + Log a byte array + @param loglevel The level to log at (DEBUG, WARN, etc) + @param b The byte array to print. + @see #setHexDump to enable hex dumping. + @see #setByteArrayCount to change how many bytes are printed. + @see #setByteArrayWidth to change the formatting width of hex. */ + public static void print(int loglevel, byte[] b) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + String[] lines = null; + if (hexdump) { + if (balen >= b.length) + lines = Hexdump.format(b, bawidth).split("\n"); + else { + byte[] buf = new byte[balen]; + System.arraycopy(b, 0, buf, 0, balen); + lines = Hexdump.format(buf, bawidth).split("\n"); + } + } + _print(b.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, b.length+" bytes", lines); + } + } + } + /** + Log a String + @param loglevel The level to log at (DEBUG, WARN, etc) + @param s The string to log with d.toString() + */ + public static void print(int loglevel, String s) + { + if (debug) + print(loglevel, (Object) s); + } + /** + Log an Object + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param d The object to log with d.toString() + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, Object d) + { + if (debug) { + saveclass = c; + print(loglevel, d); + } + } + /** + Log a String + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param s The log message + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, String s) + { + if (debug) { + saveclass = c; + print(loglevel, s); + } + } + private static String[] getTraceElements() + { + String[] data = new String[] { "", "", "" }; + try { + Method m = Thread.class.getMethod("getStackTrace", new Class[0]); + StackTraceElement[] stes = (StackTraceElement[]) m.invoke(Thread.currentThread(), new Object[0]); + for (StackTraceElement ste: stes) { + if (Debug.class.getName().equals(ste.getClassName())) continue; + if (Thread.class.getName().equals(ste.getClassName())) continue; + if (Method.class.getName().equals(ste.getClassName())) continue; + if (ste.getClassName().startsWith("sun.reflect")) continue; + data[0] = ste.getClassName(); + data[1] = ste.getMethodName(); + if (lines) + data[2] = " "+ste.getFileName()+":"+ste.getLineNumber(); + break; + } + } catch (NoSuchMethodException NSMe) { + if (null != saveclass) + data[0] = saveclass.getName(); + } catch (IllegalAccessException IAe) { + } catch (InvocationTargetException ITe) { + } + return data; + } + /** + Log an Object + @param loglevel The level to log at (DEBUG, WARN, etc) + @param o The object to log + */ + public static void print(int loglevel, Object o) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + _print(o.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, o.toString(), null); + } + } + } + /** + Log a Map + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param m The Map to print out + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void printMap(Object o, int loglevel, Map m) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + printMap(loglevel, m); + } + } + /** + Log a Map + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param m The Map to print out + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void printMap(Class c, int loglevel, Map m) + { + if (debug) { + saveclass = c; + printMap(loglevel, m); + } + } + /** + Log a Map at DEBUG log level + @param m The Map to print out + */ + public static void printMap(Map m) + { + printMap(DEBUG, m); + } + /** + Log a Map + @param loglevel The level to log at (DEBUG, WARN, etc) + @param m The Map to print out + */ + public static void printMap(int loglevel, Map m) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + Iterator i = m.keySet().iterator(); + String[] lines = new String[m.size()]; + int j = 0; + while (i.hasNext()) { + Object key = i.next(); + lines[j++] = "\t\t- "+key+" => "+m.get(key); + } + _print(m.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, "Map:", lines); + } + } + } + /** + Enable or disable stack traces in Debuging throwables. + */ + public static void setThrowableTraces(boolean ttrace) + { + Debug.ttrace = ttrace; + } + /** + Enable or disable timing in Debug messages. + */ + public static void setTiming(boolean timing) + { + Debug.timing = timing; + } + /** + Enable or disable line numbers. + */ + public static void setLineNos(boolean lines) + { + Debug.lines = lines; + } + /** + Enable or disable hexdumps. + */ + public static void setHexDump(boolean hexdump) + { + Debug.hexdump = hexdump; + } + /** + Set the size of hexdumps. + (Default: 36) + */ + public static void setByteArrayCount(int count) + { + Debug.balen = count; + } + /** + Set the formatted width of hexdumps. + (Default: 80 chars) + */ + public static void setByteArrayWidth(int width) + { + Debug.bawidth = width; + } + /** + Add a filter command for a specific type. + This command will be called with the output stream + and the text to be sent. It should perform any + changes necessary to the text and then print the + result to the output stream. + */ + public static void addFilterCommand(Class c, FilterCommand f) + //TODO 1.5: public static void addFilterCommand(Class c, FilterCommand f) + { + filterMap.put(c, f); + } + private static void _print(Class c, int level, String loc, String extra, String message, String[] lines) + { + //TODO 1.5: FilterCommand f = filterMap.get(c); + FilterCommand f = (FilterCommand) filterMap.get(c); + if (null == f) { + debugout.println("["+loc+"] " +extra + message); + if (null != lines) + for (String s: lines) + debugout.println(s); + } else + f.filter(debugout, level, loc, extra, message, lines); + } +} diff --git a/app/src/main/java/cx/ath/matthew/debug/Debug.jpp b/app/src/main/java/cx/ath/matthew/debug/Debug.jpp new file mode 100644 index 00000000..56fd4ac7 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/debug/Debug.jpp @@ -0,0 +1,597 @@ +/* + * Java Debug Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.debug; + +import cx.ath.matthew.utils.Hexdump; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +/** + Add debugging to your program, has support for large projects with multiple + classes and debug levels per class. Supports optional enabling of debug + per-level per-class and debug targets of files, Streams or stderr. + Also supports timing between debug outputs, printing of stack traces for Throwables + and files/line numbers on each message. +

+ Debug now automatically figures out which class it was called from, so all + methods passing in the calling class are deprecated. +

+

+ The defaults are to print all messages to stderr with class and method name. +

+

+ Should be called like this: +

+
+   if (Debug.debug) Debug.print(Debug.INFO, "Debug Message");
+  
+ */ +public class Debug +{ + /** + This interface can be used to provide custom printing filters + for certain classes. + */ + public static interface FilterCommand + { + /** + Called to print debug messages with a custom filter. + @param output The PrintStream to output to. + @param level The debug level of this message. + @param location The textual location of the message. + @param extra Extra information such as timing details. + @param message The debug message. + @param lines Other lines of a multiple-line debug message. + */ + public void filter(PrintStream output, int level, String location, String extra, String message, String[] lines); + } + /** Highest priority messages */ + public static final int CRIT = 1; + /** Error messages */ + public static final int ERR = 2; + /** Warnings */ + public static final int WARN = 3; + /** Information */ + public static final int INFO = 4; + /** Debug messages */ + public static final int DEBUG = 5; + /** Verbose debug messages */ + public static final int VERBOSE = 6; + /** Set this to false to disable compilation of Debug statements */ + public static final boolean debug = DEBUGSETTING; + /** The current output stream (defaults to System.err) */ + public static PrintStream debugout = System.err; + private static Properties prop = null; + private static boolean timing = false; + private static boolean ttrace = false; + private static boolean lines = false; + private static boolean hexdump = false; + private static long last = 0; + private static int balen = 36; + private static int bawidth = 80; + private static Class saveclass = null; + //TODO: 1.5 private static Map, FilterCommand> filterMap = new HashMap, FilterCommand>(); + private static Map filterMap = new HashMap(); + /** + Set properties to configure debugging. + Format of properties is class => level, e.g. +
+      cx.ath.matthew.io.TeeOutputStream = INFO
+      cx.ath.matthew.io.DOMPrinter = DEBUG
+     
+ The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which + correspond to all messages up to that level. The special words YES, ALL and TRUE + cause all messages to be printed regardless of level. All other terms disable + messages for that class. CRIT and ERR messages are always printed if debugging is enabled + unless explicitly disabled. + The special class name ALL can be used to set the default level for all classes. + @param prop Properties object to use. + */ + public static void setProperties(Properties prop) + { + Debug.prop = prop; + } + /** + Read which class to debug on at which level from the given File. + Syntax the same as Java Properties files: +
+     <class> = <debuglevel>
+     
+ E.G. +
+      cx.ath.matthew.io.TeeOutputStream = INFO
+      cx.ath.matthew.io.DOMPrinter = DEBUG
+     
+ The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which + correspond to all messages up to that level. The special words YES, ALL and TRUE + cause all messages to be printed regardless of level. All other terms disable + messages for that class. CRIT and ERR messages are always printed if debugging is enabled + unless explicitly disabled. + The special class name ALL can be used to set the default level for all classes. + @param f File to read from. + */ + public static void loadConfig(File f) throws IOException + { + prop = new Properties(); + prop.load(new FileInputStream(f)); + } + /** @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. */ + //TODO: 1.5 @Deprecated() + public static boolean debugging(Class c, int loglevel) + { + if (debug) { + if (null == c) return true; + return debugging(c.getName(), loglevel); + } + return false; + } + public static boolean debugging(String s, int loglevel) + { + if (debug) { + try { + if (null == s) return true; + if (null == prop) return loglevel <= DEBUG; + String d = prop.getProperty(s); + if (null == d || "".equals(d)) d = prop.getProperty("ALL"); + if (null == d) return loglevel <= ERR; + if ("".equals(d)) return loglevel <= ERR; + d = d.toLowerCase(); + if ("true".equals(d)) return true; + if ("yes".equals(d)) return true; + if ("all".equals(d)) return true; + if ("verbose".equals(d)) return loglevel <= VERBOSE; + if ("debug".equals(d)) return loglevel <= DEBUG; + if ("info".equals(d)) return loglevel <= INFO; + if ("warn".equals(d)) return loglevel <= WARN; + if ("err".equals(d)) return loglevel <= ERR; + if ("crit".equals(d)) return loglevel <= CRIT; + int i = Integer.parseInt(d); return i >= loglevel; + } catch (Exception e) { return false; } + } + return false; + } + + /** + Output to the given Stream */ + public static void setOutput(PrintStream p) throws IOException + { + debugout = p; + } + /** + Output to the given file */ + public static void setOutput(String filename) throws IOException + { + debugout = new PrintStream(new FileOutputStream(filename, true)); + } + + /** + Output to the default debug.log */ + public static void setOutput() throws IOException { + setOutput("./debug.log"); + } + /** + Log at DEBUG + @param d The object to log */ + public static void print(Object d) + { + if (debug) { + if (d instanceof String) + print(DEBUG, (String) d); + else if (d instanceof Throwable) + print(DEBUG, (Throwable) d); + else if (d instanceof byte[]) + print(DEBUG, (byte[]) d); + else if (d instanceof Map) + printMap(DEBUG, (Map) d); + else print(DEBUG, d); + } + } + /** + Log at DEBUG + @param o The object doing the logging + @param d The object to log + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, Object d) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(d); + } + } + + /** + Log an Object + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param d The object to log with d.toString() + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, Object d) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, d); + } + } + /** + Log a String + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param s The log message + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, String s) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, s); + } + } + /** + Log a Throwable + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param t The throwable to log with .toString and .printStackTrace + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, Throwable t) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, t); + } + } + + /** + Log a Throwable + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param t The throwable to log with .toString and .printStackTrace + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, Throwable t) + { + if (debug) { + saveclass = c; + print(loglevel, t); + } + } + /** + Log a Throwable + @param loglevel The level to log at (DEBUG, WARN, etc) + @param t The throwable to log with .toString and .printStackTrace + @see #setThrowableTraces to turn on stack traces. + */ + public static void print(int loglevel, Throwable t) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + String[] lines = null; + if (ttrace) { + StackTraceElement[] ste = t.getStackTrace(); + lines = new String[ste.length]; + for (int i = 0; i < ste.length; i++) + lines[i] = "\tat "+ste[i].toString(); + } + _print(t.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, t.toString(), lines); + } + } + } + + /** + Log a byte array + @param loglevel The level to log at (DEBUG, WARN, etc) + @param b The byte array to print. + @see #setHexDump to enable hex dumping. + @see #setByteArrayCount to change how many bytes are printed. + @see #setByteArrayWidth to change the formatting width of hex. */ + public static void print(int loglevel, byte[] b) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + String[] lines = null; + if (hexdump) { + if (balen >= b.length) + lines = Hexdump.format(b, bawidth).split("\n"); + else { + byte[] buf = new byte[balen]; + System.arraycopy(b, 0, buf, 0, balen); + lines = Hexdump.format(buf, bawidth).split("\n"); + } + } + _print(b.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, b.length+" bytes", lines); + } + } + } + /** + Log a String + @param loglevel The level to log at (DEBUG, WARN, etc) + @param s The string to log with d.toString() + */ + public static void print(int loglevel, String s) + { + if (debug) + print(loglevel, (Object) s); + } + /** + Log an Object + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param d The object to log with d.toString() + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, Object d) + { + if (debug) { + saveclass = c; + print(loglevel, d); + } + } + /** + Log a String + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param s The log message + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, String s) + { + if (debug) { + saveclass = c; + print(loglevel, s); + } + } + private static String[] getTraceElements() + { + String[] data = new String[] { "", "", "" }; + try { + Method m = Thread.class.getMethod("getStackTrace", new Class[0]); + StackTraceElement[] stes = (StackTraceElement[]) m.invoke(Thread.currentThread(), new Object[0]); + for (StackTraceElement ste: stes) { + if (Debug.class.getName().equals(ste.getClassName())) continue; + if (Thread.class.getName().equals(ste.getClassName())) continue; + if (Method.class.getName().equals(ste.getClassName())) continue; + if (ste.getClassName().startsWith("sun.reflect")) continue; + data[0] = ste.getClassName(); + data[1] = ste.getMethodName(); + if (lines) + data[2] = " "+ste.getFileName()+":"+ste.getLineNumber(); + break; + } + } catch (NoSuchMethodException NSMe) { + if (null != saveclass) + data[0] = saveclass.getName(); + } catch (IllegalAccessException IAe) { + } catch (InvocationTargetException ITe) { + } + return data; + } + /** + Log an Object + @param loglevel The level to log at (DEBUG, WARN, etc) + @param o The object to log + */ + public static void print(int loglevel, Object o) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + _print(o.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, o.toString(), null); + } + } + } + + /** + Log a Map + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param m The Map to print out + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void printMap(Object o, int loglevel, Map m) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + printMap(loglevel, m); + } + } + /** + Log a Map + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param m The Map to print out + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void printMap(Class c, int loglevel, Map m) + { + if (debug) { + saveclass = c; + printMap(loglevel, m); + } + } + /** + Log a Map at DEBUG log level + @param m The Map to print out + */ + public static void printMap(Map m) + { + printMap(DEBUG, m); + } + /** + Log a Map + @param loglevel The level to log at (DEBUG, WARN, etc) + @param m The Map to print out + */ + public static void printMap(int loglevel, Map m) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + Iterator i = m.keySet().iterator(); + String[] lines = new String[m.size()]; + int j = 0; + while (i.hasNext()) { + Object key = i.next(); + lines[j++] = "\t\t- "+key+" => "+m.get(key); + } + _print(m.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, "Map:", lines); + } + } + } + /** + Enable or disable stack traces in Debuging throwables. + */ + public static void setThrowableTraces(boolean ttrace) + { + Debug.ttrace = ttrace; + } + /** + Enable or disable timing in Debug messages. + */ + public static void setTiming(boolean timing) + { + Debug.timing = timing; + } + /** + Enable or disable line numbers. + */ + public static void setLineNos(boolean lines) + { + Debug.lines = lines; + } + /** + Enable or disable hexdumps. + */ + public static void setHexDump(boolean hexdump) + { + Debug.hexdump = hexdump; + } + /** + Set the size of hexdumps. + (Default: 36) + */ + public static void setByteArrayCount(int count) + { + Debug.balen = count; + } + /** + Set the formatted width of hexdumps. + (Default: 80 chars) + */ + public static void setByteArrayWidth(int width) + { + Debug.bawidth = width; + } + /** + Add a filter command for a specific type. + This command will be called with the output stream + and the text to be sent. It should perform any + changes necessary to the text and then print the + result to the output stream. + */ + public static void addFilterCommand(Class c, FilterCommand f) + //TODO 1.5: public static void addFilterCommand(Class c, FilterCommand f) + { + filterMap.put(c, f); + } + private static void _print(Class c, int level, String loc, String extra, String message, String[] lines) + { + //TODO 1.5: FilterCommand f = filterMap.get(c); + FilterCommand f = (FilterCommand) filterMap.get(c); + if (null == f) { + debugout.println("["+loc+"] " +extra + message); + if (null != lines) + for (String s: lines) + debugout.println(s); + } else + f.filter(debugout, level, loc, extra, message, lines); + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/io/DOMPrinter.java b/app/src/main/java/cx/ath/matthew/io/DOMPrinter.java new file mode 100644 index 00000000..e61fa779 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/DOMPrinter.java @@ -0,0 +1,114 @@ +/* + * Java DOM Printing Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Print a DOM tree to the given OutputStream + */ +public class DOMPrinter +{ + /** + * Print the given node and all its children. + * @param n The Node to print. + * @param os The Stream to print to. + */ + public static void printNode(Node n, OutputStream os) + { + PrintStream p = new PrintStream(os); + printNode(n, p); + } + /** + * Print the given node and all its children. + * @param n The Node to print. + * @param p The Stream to print to. + */ + public static void printNode(Node n, PrintStream p) + { + if (null != n.getNodeValue()) p.print(n.getNodeValue()); + else { + p.print("<"+n.getNodeName()); + if (n.hasAttributes()) { + NamedNodeMap nnm = n.getAttributes(); + for (int i = 0; i < nnm.getLength(); i++) { + Node attr = nnm.item(i); + p.print(" "+attr.getNodeName()+"='"+attr.getNodeValue()+"'"); + } + } + if (n.hasChildNodes()) { + p.print(">"); + NodeList nl = n.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) + printNode(nl.item(i), p); + p.print(""); + } else { + p.print("/>"); + } + } + } + /** + * Print the given document and all its children. + * @param d The Document to print. + * @param p The Stream to print to. + */ + public static void printDOM(Document d, PrintStream p) + { + DocumentType dt = d.getDoctype(); + if (null != dt) { + p.print(""); + } + Element e = d.getDocumentElement(); + printNode(e, p); + } + /** + * Print the given document and all its children. + * @param d The Document to print. + * @param os The Stream to print to. + */ + public static void printDOM(Document d, OutputStream os) + { + PrintStream p = new PrintStream(os); + printDOM(d, p); + } +} + diff --git a/app/src/main/java/cx/ath/matthew/io/ExecInputStream.java b/app/src/main/java/cx/ath/matthew/io/ExecInputStream.java new file mode 100644 index 00000000..517cded8 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/ExecInputStream.java @@ -0,0 +1,142 @@ +/* + * Java Exec Pipe Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; + +import java.io.FilterInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Class to pipe an InputStream through a command using stdin/stdout. + * E.g. + *
+ *    Reader r = new InputStreamReader(new ExecInputStream(new FileInputStream("file"), "command"));
+ * 
+ */ +public class ExecInputStream extends FilterInputStream +{ + private Process proc; + private InputStream stdout; + private OutputStream stdin; + private InOutCopier copy; + + /** + * Create a new ExecInputStream on the given InputStream + * using the process to filter the stream. + * @param is Reads from this InputStream + * @param p Filters data through stdin/out on this Process + */ + public ExecInputStream(InputStream is, Process p) throws IOException + { + super(is); + proc = p; + stdin = p.getOutputStream(); + stdout = p.getInputStream(); + copy = new InOutCopier(in, stdin); + copy.start(); + } + /** + * Create a new ExecInputStream on the given InputStream + * using the process to filter the stream. + * @param is Reads from this InputStream + * @param cmd Creates a Process from this string to filter data through stdin/out + */ + public ExecInputStream(InputStream is, String cmd) throws IOException + { this(is, Runtime.getRuntime().exec(cmd)); } + /** + * Create a new ExecInputStream on the given InputStream + * using the process to filter the stream. + * @param is Reads from this InputStream + * @param cmd Creates a Process from this string array (command, arg, ...) to filter data through stdin/out + */ + public ExecInputStream(InputStream is, String[] cmd) throws IOException + { this(is, Runtime.getRuntime().exec(cmd)); } + /** + * Create a new ExecInputStream on the given InputStream + * using the process to filter the stream. + * @param is Reads from this InputStream + * @param cmd Creates a Process from this string to filter data through stdin/out + * @param env Setup the environment for the command + */ + public ExecInputStream(InputStream is, String cmd, String[] env) throws IOException + { this(is, Runtime.getRuntime().exec(cmd, env)); } + /** + * Create a new ExecInputStream on the given InputStream + * using the process to filter the stream. + * @param is Reads from this InputStream + * @param cmd Creates a Process from this string array (command, arg, ...) to filter data through stdin/out + * @param env Setup the environment for the command + */ + public ExecInputStream(InputStream is, String[] cmd, String[] env) throws IOException + { this(is, Runtime.getRuntime().exec(cmd, env)); } + + public void close() throws IOException + { + try { + proc.waitFor(); + } catch (InterruptedException Ie) {} + //copy.close(); + try { + copy.join(); + } catch (InterruptedException Ie) {} + stdin.close(); + in.close(); + stdout.close(); + } + public void flush() throws IOException + { + copy.flush(); + } + public int available() throws IOException + { return stdout.available(); } + public int read() throws IOException + { return stdout.read(); } + public int read(byte[] b) throws IOException + { return stdout.read(b); } + public int read(byte[] b, int off, int len) throws IOException + { return stdout.read(b, off, len); } + public long skip(long n) throws IOException + { return stdout.skip(n); } + public void mark(int readlimit) + {} + public boolean markSupported() + { return false; } + public void reset() + {} + + public void finalize() + { + try { + close(); + } catch (Exception e) {} + } +} + + + diff --git a/app/src/main/java/cx/ath/matthew/io/ExecOutputStream.java b/app/src/main/java/cx/ath/matthew/io/ExecOutputStream.java new file mode 100644 index 00000000..9ee92d6c --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/ExecOutputStream.java @@ -0,0 +1,137 @@ +/* + * Java Exec Pipe Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; + +import java.io.FilterOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Class to pipe an OutputStream through a command using stdin/stdout. + * E.g. + *
+ *    Writer w = new OutputStreamWriter(new ExecOutputStream(new FileOutputStream("file"), "command"));
+ * 
+ */ +public class ExecOutputStream extends FilterOutputStream +{ + private Process proc; + private InputStream stdout; + private OutputStream stdin; + private InOutCopier copy; + + /** + * Create a new ExecOutputStream on the given OutputStream + * using the process to filter the stream. + * @param os Writes to this OutputStream + * @param p Filters data through stdin/out on this Process + */ + public ExecOutputStream(OutputStream os, Process p) throws IOException + { + super(os); + proc = p; + stdin = p.getOutputStream(); + stdout = p.getInputStream(); + copy = new InOutCopier(stdout, out); + copy.start(); + } + /** + * Create a new ExecOutputStream on the given OutputStream + * using the process to filter the stream. + * @param os Writes to this OutputStream + * @param cmd Creates a Process from this string to filter data through stdin/out + */ + public ExecOutputStream(OutputStream os, String cmd) throws IOException + { this(os, Runtime.getRuntime().exec(cmd)); } + /** + * Create a new ExecOutputStream on the given OutputStream + * using the process to filter the stream. + * @param os Writes to this OutputStream + * @param cmd Creates a Process from this string array (command, arg, ...) to filter data through stdin/out + */ + public ExecOutputStream(OutputStream os, String[] cmd) throws IOException + { this(os, Runtime.getRuntime().exec(cmd)); } + /** + * Create a new ExecOutputStream on the given OutputStream + * using the process to filter the stream. + * @param os Writes to this OutputStream + * @param cmd Creates a Process from this string to filter data through stdin/out + * @param env Setup the environment for the command + */ + public ExecOutputStream(OutputStream os, String cmd, String[] env) throws IOException + { this(os, Runtime.getRuntime().exec(cmd, env)); } + /** + * Create a new ExecOutputStream on the given OutputStream + * using the process to filter the stream. + * @param os Writes to this OutputStream + * @param cmd Creates a Process from this string array (command, arg, ...) to filter data through stdin/out + * @param env Setup the environment for the command + */ + public ExecOutputStream(OutputStream os, String[] cmd, String[] env) throws IOException + { this(os, Runtime.getRuntime().exec(cmd, env)); } + + public void close() throws IOException + { + stdin.close(); + try { + proc.waitFor(); + } catch (InterruptedException Ie) {} + //copy.close(); + try { + copy.join(); + } catch (InterruptedException Ie) {} + stdout.close(); + out.close(); + } + public void flush() throws IOException + { + stdin.flush(); + copy.flush(); + out.flush(); + } + public void write(byte[] b) throws IOException + { + stdin.write(b); + } + public void write(byte[] b, int off, int len) throws IOException + { + stdin.write(b, off, len); + } + public void write(int b) throws IOException + { + stdin.write(b); + } + public void finalize() + { + try { + close(); + } catch (Exception e) {} + } +} + diff --git a/app/src/main/java/cx/ath/matthew/io/InOutCopier.java b/app/src/main/java/cx/ath/matthew/io/InOutCopier.java new file mode 100644 index 00000000..49688539 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/InOutCopier.java @@ -0,0 +1,115 @@ +/* + * Java Exec Pipe Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Copies from an input stream to an output stream using a Thread. + * example: + * + *
+ * InputStream a = getInputStream();
+ * OutputStream b = getOutputStream();
+ * InOutCopier copier = new InOutCopier(a, b);
+ * copier.start();
+ * <do stuff that writes to the inputstream>
+ * 
+ */ +public class InOutCopier extends Thread +{ + private static final int BUFSIZE=1024; + private static final int POLLTIME=100; + private BufferedInputStream is; + private OutputStream os; + private boolean enable; + /** + * Create a copier from an inputstream to an outputstream + * @param is The stream to copy from + * @param os the stream to copy to + */ + public InOutCopier(InputStream is, OutputStream os) throws IOException + { + this.is = new BufferedInputStream(is); + this.os = os; + this.enable = true; + } + /** + * Force close the stream without waiting for EOF on the source + */ + public void close() + { + enable = false; + interrupt(); + } + /** + * Flush the outputstream + */ + public void flush() throws IOException + { + os.flush(); + } + /** Start the thread and wait to make sure its really started */ + public synchronized void start() + { + super.start(); + try { + wait(); + } catch (InterruptedException Ie) {} + } + /** + * Copies from the inputstream to the outputstream + * until EOF on the inputstream or explicitly closed + * @see #close() + */ + public void run() + { + byte[] buf = new byte[BUFSIZE]; + synchronized (this) { + notifyAll(); + } + while (enable) + try { + int n = is.read(buf); + if (0 > n) + break; + if (0 < n) { + os.write(buf, 0, (n> BUFSIZE? BUFSIZE:n)); + os.flush(); + } + } catch (IOException IOe) { + break; + } + try { os.close(); } catch (IOException IOe) {} + } +} + diff --git a/app/src/main/java/cx/ath/matthew/io/TeeInputStream.java b/app/src/main/java/cx/ath/matthew/io/TeeInputStream.java new file mode 100644 index 00000000..f769b114 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/TeeInputStream.java @@ -0,0 +1,155 @@ +/* + * Java Tee Stream Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Class to copy a stream to a file or another stream as it is being sent through a stream pipe + * E.g. + *
+ *    Reader r = new InputStreamReader(new TeeInputStream(new FileInputStream("file"), new File("otherfile")));
+ * 
+ */ +public class TeeInputStream extends FilterInputStream +{ + private InputStream in; + private OutputStream fos; + /** + * Create a new TeeInputStream on the given InputStream + * and copy the stream to the given File. + * @param is Reads from this InputStream + * @param tos Write to this OutputStream + */ + public TeeInputStream(InputStream is, OutputStream tos) throws IOException + { + super(is); + this.in = is; + this.fos = tos; + } + /** + * Create a new TeeInputStream on the given InputStream + * and copy the stream to the given File. + * @param is Reads from this InputStream + * @param f Write to this File + * @param append Append to file not overwrite + */ + public TeeInputStream(InputStream is, File f, boolean append) throws IOException + { + super(is); + this.in = is; + this.fos = new FileOutputStream(f, append); + } + /** + * Create a new TeeInputStream on the given InputStream + * and copy the stream to the given File. + * @param is Reads from this InputStream + * @param f Write to this File + */ + public TeeInputStream(InputStream is, File f) throws IOException + { + super(is); + this.in = is; + this.fos = new FileOutputStream(f); + } + /** + * Create a new TeeInputStream on the given InputStream + * and copy the stream to the given File. + * @param is Reads from this InputStream + * @param f Write to this File + * @param append Append to file not overwrite + */ + public TeeInputStream(InputStream is, String f, boolean append) throws IOException + { + this(is, new File(f), append); + } + /** + * Create a new TeeInputStream on the given InputStream + * and copy the stream to the given File. + * @param is Reads from this InputStream + * @param f Write to this File + */ + public TeeInputStream(InputStream is, String f) throws IOException + { + this(is, new File(f)); + } + public void close() throws IOException + { + in.close(); + fos.close(); + } + public void flush() throws IOException + { + fos.flush(); + } + public int available() throws IOException + { + return in.available(); + } + public int read() throws IOException + { + int i = in.read(); + if (-1 != i) fos.write(i); + return i; + } + public int read(byte[] b) throws IOException + { + int c = in.read(b); + if (-1 != c) fos.write(b, 0, c); + return c; + } + public int read(byte[] b, int off, int len) throws IOException + { + int c = in.read(b, off, len); + if (-1 != c) fos.write(b, off, c); + return c; + } + public long skip(long n) throws IOException + { return in.skip(n); } + public void mark(int readlimit) + {} + public boolean markSupported() + { return false; } + public void reset() throws IOException + { in.reset(); } + + public void finalize() + { + try { + close(); + } catch (Exception e) {} + } +} + + + diff --git a/app/src/main/java/cx/ath/matthew/io/TeeOutputStream.java b/app/src/main/java/cx/ath/matthew/io/TeeOutputStream.java new file mode 100644 index 00000000..30509230 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/TeeOutputStream.java @@ -0,0 +1,141 @@ +/* + * Java Tee Stream Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; +import java.io.OutputStream; +import java.io.IOException; + +/** + * Class to copy a stream to another stream or file as it is being sent through a stream pipe + * E.g. + *
+ *    PrintWriter r = new PrintWriter(new TeeOutputStream(new FileOutputStream("file"), new File("otherfile")));
+ * 
+ */ +public class TeeOutputStream extends FilterOutputStream +{ + private File f; + private OutputStream out; + private OutputStream fos; + /** + * Create a new TeeOutputStream on the given OutputStream + * and copy the stream to the other OuputStream. + * @param os Writes to this OutputStream + * @param tos Write to this OutputStream + */ + public TeeOutputStream(OutputStream os, OutputStream tos) throws IOException + { + super(os); + this.out = os; + this.fos = tos; + } + /** + * Create a new TeeOutputStream on the given OutputStream + * and copy the stream to the given File. + * @param os Writes to this OutputStream + * @param f Write to this File + * @param append Append to file not overwrite + */ + public TeeOutputStream(OutputStream os, File f, boolean append) throws IOException + { + super(os); + this.out = os; + this.fos = new FileOutputStream(f, append); + } + /** + * Create a new TeeOutputStream on the given OutputStream + * and copy the stream to the given File. + * @param os Writes to this OutputStream + * @param f Write to this File + */ + public TeeOutputStream(OutputStream os, File f) throws IOException + { + super(os); + this.out = os; + this.fos = new FileOutputStream(f); + } + /** + * Create a new TeeOutputStream on the given OutputStream + * and copy the stream to the given File. + * @param os Writes to this OutputStream + * @param f Write to this File + * @param append Append to file not overwrite + */ + public TeeOutputStream(OutputStream os, String f, boolean append) throws IOException + { + this(os, new File(f), append); + } + /** + * Create a new TeeOutputStream on the given OutputStream + * and copy the stream to the given File. + * @param os Writes to this OutputStream + * @param f Write to this File + */ + public TeeOutputStream(OutputStream os, String f) throws IOException + { + this(os, new File(f)); + } + public void close() throws IOException + { + out.close(); + fos.close(); + } + public void flush() throws IOException + { + fos.flush(); + out.flush(); + } + public void write(int b) throws IOException + { + fos.write(b); + out.write(b); + } + public void write(byte[] b) throws IOException + { + fos.write(b); + out.write(b); + } + public void write(byte[] b, int off, int len) throws IOException + { + fos.write(b, off, len); + out.write(b, off, len); + } + + public void finalize() + { + try { + close(); + } catch (Exception e) {} + } +} + + + diff --git a/app/src/main/java/cx/ath/matthew/io/test.java b/app/src/main/java/cx/ath/matthew/io/test.java new file mode 100644 index 00000000..0f3c9e43 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/test.java @@ -0,0 +1,48 @@ +/* + * Java IO Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; +import java.io.PrintWriter; +import java.io.OutputStreamWriter; +class test +{ + public static void main(String[] args) throws Exception + { + PrintWriter out = new PrintWriter(new OutputStreamWriter(new ExecOutputStream(System.out, "xsltproc mcr.xsl -")));///java cx.ath.matthew.io.findeof"))); + + out.println(""); + out.println(" "); + out.println(" "); + out.println(" TEST"); + out.println(" "); + out.println("hello, he is helping tie up helen's lemmings"); + out.println("we are being followed and we break out"); + out.println(" "); + out.println(" "); + out.close(); + } +} diff --git a/app/src/main/java/cx/ath/matthew/io/test2.java b/app/src/main/java/cx/ath/matthew/io/test2.java new file mode 100644 index 00000000..bd7a047d --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/test2.java @@ -0,0 +1,39 @@ +/* + * Java IO Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; +import java.io.BufferedReader; +import java.io.InputStreamReader; +class test2 +{ + public static void main(String[] args) throws Exception + { + BufferedReader in = new BufferedReader(new InputStreamReader(new ExecInputStream(System.in, "xsltproc mcr.xsl -"))); + String s; + while (null != (s = in.readLine())) System.out.println(s); + } +} diff --git a/app/src/main/java/cx/ath/matthew/io/test3.java b/app/src/main/java/cx/ath/matthew/io/test3.java new file mode 100644 index 00000000..5d40214b --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/test3.java @@ -0,0 +1,45 @@ +/* + * Java IO Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; +import java.io.PrintWriter; +import java.io.BufferedReader; +import java.io.InputStreamReader; +class test3 +{ + public static void main(String[] args) throws Exception + { + String file = args[0]; + PrintWriter p = new PrintWriter(new TeeOutputStream(System.out, file)); + BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); + String s; + while (null != (s = r.readLine())) + p.println(s); + p.close(); + r.close(); + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/NotConnectedException.java b/app/src/main/java/cx/ath/matthew/unix/NotConnectedException.java new file mode 100644 index 00000000..7f5c4e75 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/NotConnectedException.java @@ -0,0 +1,37 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.net.SocketException; + +public class NotConnectedException extends SocketException +{ + public NotConnectedException() + { + super("The Socket is Not Connected"); + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/USInputStream.java b/app/src/main/java/cx/ath/matthew/unix/USInputStream.java new file mode 100644 index 00000000..95a95e6d --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/USInputStream.java @@ -0,0 +1,84 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.InputStream; +import java.io.IOException; + +public class USInputStream extends InputStream +{ + public static final int MSG_DONTWAIT = 0x40; + private native int native_recv(int sock, byte[] b, int off, int len, int flags, int timeout) throws IOException; + private int sock; + boolean closed = false; + private byte[] onebuf = new byte[1]; + private UnixSocket us; + private boolean blocking = true; + private int flags = 0; + private int timeout = 0; + public USInputStream(int sock, UnixSocket us) + { + this.sock = sock; + this.us = us; + } + public void close() throws IOException + { + closed = true; + us.close(); + } + public boolean markSupported() { return false; } + public int read() throws IOException + { + int rv = 0; + while (0 >= rv) rv = read(onebuf); + if (-1 == rv) return -1; + return 0 > onebuf[0] ? -onebuf[0] : onebuf[0]; + } + public int read(byte[] b, int off, int len) throws IOException + { + if (closed) throw new NotConnectedException(); + int count = native_recv(sock, b, off, len, flags, timeout); + /* Yes, I really want to do this. Recv returns 0 for 'connection shut down'. + * read() returns -1 for 'end of stream. + * Recv returns -1 for 'EAGAIN' (all other errors cause an exception to be raised) + * whereas read() returns 0 for '0 bytes read', so yes, I really want to swap them here. + */ + if (0 == count) return -1; + else if (-1 == count) return 0; + else return count; + } + public boolean isClosed() { return closed; } + public UnixSocket getSocket() { return us; } + public void setBlocking(boolean enable) + { + flags = enable ? 0 : MSG_DONTWAIT; + } + public void setSoTimeout(int timeout) + { + this.timeout = timeout; + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/USOutputStream.java b/app/src/main/java/cx/ath/matthew/unix/USOutputStream.java new file mode 100644 index 00000000..7e06289e --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/USOutputStream.java @@ -0,0 +1,69 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.IOException; +import java.io.OutputStream; + +public class USOutputStream extends OutputStream +{ + private native int native_send(int sock, byte[] b, int off, int len) throws IOException; + private native int native_send(int sock, byte[][] b) throws IOException; + private int sock; + boolean closed = false; + private byte[] onebuf = new byte[1]; + private UnixSocket us; + public USOutputStream(int sock, UnixSocket us) + { + this.sock = sock; + this.us = us; + } + public void close() throws IOException + { + closed = true; + us.close(); + } + public void flush() {} // no-op, we do not buffer + public void write(byte[][] b) throws IOException + { + if (closed) throw new NotConnectedException(); + native_send(sock, b); + } + public void write(byte[] b, int off, int len) throws IOException + { + if (closed) throw new NotConnectedException(); + native_send(sock, b, off, len); + } + public void write(int b) throws IOException + { + onebuf[0] = (byte) (b % 0x7F); + if (1 == (b % 0x80)) onebuf[0] = (byte) -onebuf[0]; + write(onebuf); + } + public boolean isClosed() { return closed; } + public UnixSocket getSocket() { return us; } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/UnixIOException.java b/app/src/main/java/cx/ath/matthew/unix/UnixIOException.java new file mode 100644 index 00000000..61cdc127 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/UnixIOException.java @@ -0,0 +1,44 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.IOException; + +/** + * An IO Exception which occurred during UNIX Socket IO + */ +public class UnixIOException extends IOException +{ + private int no; + private String message; + public UnixIOException(int no, String message) + { + super(message); + this.message = message; + this.no = no; + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/UnixServerSocket.java b/app/src/main/java/cx/ath/matthew/unix/UnixServerSocket.java new file mode 100644 index 00000000..082978c0 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/UnixServerSocket.java @@ -0,0 +1,129 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.IOException; + +/** + * Represents a listening UNIX Socket. + */ +public class UnixServerSocket +{ + static { System.loadLibrary("unix-java"); } + private native int native_bind(String address, boolean abs) throws IOException; + private native void native_close(int sock) throws IOException; + private native int native_accept(int sock) throws IOException; + private UnixSocketAddress address = null; + private boolean bound = false; + private boolean closed = false; + private int sock; + /** + * Create an un-bound server socket. + */ + public UnixServerSocket() + { + } + /** + * Create a server socket bound to the given address. + * @param address Path to the socket. + */ + public UnixServerSocket(UnixSocketAddress address) throws IOException + { + bind(address); + } + /** + * Create a server socket bound to the given address. + * @param address Path to the socket. + */ + public UnixServerSocket(String address) throws IOException + { + this(new UnixSocketAddress(address)); + } + /** + * Accepts a connection on the ServerSocket. + * @return A UnixSocket connected to the accepted connection. + */ + public UnixSocket accept() throws IOException + { + int client_sock = native_accept(sock); + return new UnixSocket(client_sock, address); + } + /** + * Closes the ServerSocket. + */ + public synchronized void close() throws IOException + { + native_close(sock); + sock = 0; + closed = true; + bound = false; + } + /** + * Binds a server socket to the given address. + * @param address Path to the socket. + */ + public void bind(UnixSocketAddress address) throws IOException + { + if (bound) close(); + sock = native_bind(address.path, address.abs); + bound = true; + closed = false; + this.address = address; + } + /** + * Binds a server socket to the given address. + * @param address Path to the socket. + */ + public void bind(String address) throws IOException + { + bind(new UnixSocketAddress(address)); + } + /** + * Return the address this socket is bound to. + * @return The UnixSocketAddress if bound or null if unbound. + */ + public UnixSocketAddress getAddress() + { + return address; + } + /** + * Check the status of the socket. + * @return True if closed. + */ + public boolean isClosed() + { + return closed; + } + /** + * Check the status of the socket. + * @return True if bound. + */ + public boolean isBound() + { + return bound; + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/UnixSocket.java b/app/src/main/java/cx/ath/matthew/unix/UnixSocket.java new file mode 100644 index 00000000..f6526143 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/UnixSocket.java @@ -0,0 +1,320 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +import cx.ath.matthew.debug.Debug; + +/** + * Represents a UnixSocket. + */ +public class UnixSocket +{ + static { System.loadLibrary("unix-java"); } + private native void native_set_pass_cred(int sock, boolean passcred) throws IOException; + private native int native_connect(String address, boolean abs) throws IOException; + private native void native_close(int sock) throws IOException; + private native int native_getPID(int sock); + private native int native_getUID(int sock); + private native int native_getGID(int sock); + private native void native_send_creds(int sock, byte data) throws IOException; + private native byte native_recv_creds(int sock, int[] creds) throws IOException; + + private UnixSocketAddress address = null; + private USOutputStream os = null; + private USInputStream is = null; + private boolean closed = false; + private boolean connected = false; + private boolean passcred = false; + private int sock = 0; + private boolean blocking = true; + private int uid = -1; + private int pid = -1; + private int gid = -1; + UnixSocket(int sock, UnixSocketAddress address) + { + this.sock = sock; + this.address = address; + this.connected = true; + this.os = new USOutputStream(sock, this); + this.is = new USInputStream(sock, this); + } + /** + * Create an unconnected socket. + */ + public UnixSocket() + { + } + /** + * Create a socket connected to the given address. + * @param address The Unix Socket address to connect to + */ + public UnixSocket(UnixSocketAddress address) throws IOException + { + connect(address); + } + /** + * Create a socket connected to the given address. + * @param address The Unix Socket address to connect to + */ + public UnixSocket(String address) throws IOException + { + this(new UnixSocketAddress(address)); + } + /** + * Connect the socket to this address. + * @param address The Unix Socket address to connect to + */ + public void connect(UnixSocketAddress address) throws IOException + { + if (connected) close(); + this.sock = native_connect(address.path, address.abs); + this.os = new USOutputStream(this.sock, this); + this.is = new USInputStream(this.sock, this); + this.address = address; + this.connected = true; + this.closed = false; + this.is.setBlocking(blocking); + } + /** + * Connect the socket to this address. + * @param address The Unix Socket address to connect to + */ + public void connect(String address) throws IOException + { + connect(new UnixSocketAddress(address)); + } + public void finalize() + { + try { + close(); + } catch (IOException IOe) {} + } + /** + * Closes the connection. + */ + public synchronized void close() throws IOException + { + if (Debug.debug) Debug.print(Debug.INFO, "Closing socket"); + native_close(sock); + sock = 0; + this.closed = true; + this.connected = false; + os = null; + is = null; + } + /** + * Returns an InputStream for reading from the socket. + * @return An InputStream connected to this socket. + */ + public InputStream getInputStream() + { + return is; + } + /** + * Returns an OutputStream for writing to the socket. + * @return An OutputStream connected to this socket. + */ + public OutputStream getOutputStream() + { + return os; + } + /** + * Returns the address this socket is connected to. + * Returns null if the socket is unconnected. + * @return The UnixSocketAddress the socket is connected to + */ + public UnixSocketAddress getAddress() + { + return address; + } + /** + * Send a single byte of data with credentials. + * (Works on BSDs) + * @param data The byte of data to send. + */ + public void sendCredentialByte(byte data) throws IOException + { + if (!connected) throw new NotConnectedException(); + native_send_creds(sock, data); + } + /** + * Receive a single byte of data, with credentials. + * (Works on BSDs) + * @see getPeerUID + * @see getPeerPID + * @see getPeerGID + * @param data The byte of data to send. + */ + public byte recvCredentialByte() throws IOException + { + if (!connected) throw new NotConnectedException(); + int[] creds = new int[] { -1, -1, -1 }; + byte data = native_recv_creds(sock, creds); + pid = creds[0]; + uid = creds[1]; + gid = creds[2]; + return data; + } + /** + * Get the credential passing status. + * (only effective on linux) + * @return The current status of credential passing. + * @see setPassCred + */ + public boolean getPassCred() + { + return passcred; + } + /** + * Return the uid of the remote process. + * Some data must have been received on the socket to do this. + * Either setPassCred must be called on Linux first, or recvCredentialByte + * on BSD. + * @return the UID or -1 if it is not available + */ + public int getPeerUID() + { + if (-1 == uid) + uid = native_getUID(sock); + return uid; + } + /** + * Return the gid of the remote process. + * Some data must have been received on the socket to do this. + * Either setPassCred must be called on Linux first, or recvCredentialByte + * on BSD. + * @return the GID or -1 if it is not available + */ + public int getPeerGID() + { + if (-1 == gid) + gid = native_getGID(sock); + return gid; + } + /** + * Return the pid of the remote process. + * Some data must have been received on the socket to do this. + * Either setPassCred must be called on Linux first, or recvCredentialByte + * on BSD. + * @return the PID or -1 if it is not available + */ + public int getPeerPID() + { + if (-1 == pid) + pid = native_getPID(sock); + return pid; + } + /** + * Set the credential passing status. + * (Only does anything on linux, for other OS, you need + * to use send/recv credentials) + * @param enable Set to true for credentials to be passed. + */ + public void setPassCred(boolean enable) throws IOException + { + native_set_pass_cred(sock, enable); + passcred = enable; + } + /** + * Get the blocking mode. + * @return true if reads are blocking. + * @see setBlocking + */ + public boolean getBlocking() + { + return blocking; + } + /** + * Set the blocking mode. + * @param enable Set to false for non-blocking reads. + */ + public void setBlocking(boolean enable) + { + blocking = enable; + if (null != is) is.setBlocking(enable); + } + + /** + * Check the socket status. + * @return true if closed. + */ + public boolean isClosed() + { + return closed; + } + /** + * Check the socket status. + * @return true if connected. + */ + public boolean isConnected() + { + return connected; + } + /** + * Check the socket status. + * @return true if the input stream has been shutdown + */ + public boolean isInputShutdown() + { + return is.isClosed(); + } + /** + * Check the socket status. + * @return true if the output stream has been shutdown + */ + public boolean isOutputShutdown() + { + return os.isClosed(); + } + /** + * Shuts down the input stream. + * Subsequent reads on the associated InputStream will fail. + */ + public void shutdownInput() + { + is.closed = true; + } + /** + * Shuts down the output stream. + * Subsequent writes to the associated OutputStream will fail. + */ + public void shutdownOutput() + { + os.closed = true; + } + /** + * Set timeout of read requests. + */ + public void setSoTimeout(int timeout) + { + is.setSoTimeout(timeout); + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java b/app/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java new file mode 100644 index 00000000..319cd604 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java @@ -0,0 +1,85 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +/** + * Represents an address for a Unix Socket + */ +public class UnixSocketAddress +{ + String path; + boolean abs; + /** + * Create the address. + * @param path The path to the Unix Socket. + * @param abs True if this should be an abstract socket. + */ + public UnixSocketAddress(String path, boolean abs) + { + this.path = path; + this.abs = abs; + } + /** + * Create the address. + * @param path The path to the Unix Socket. + */ + public UnixSocketAddress(String path) + { + this.path = path; + this.abs = false; + } + /** + * Return the path. + */ + public String getPath() + { + return path; + } + /** + * Returns true if this an address for an abstract socket. + */ + public boolean isAbstract() + { + return abs; + } + /** + * Return the Address as a String. + */ + public String toString() + { + return "unix"+(abs?":abstract":"")+":path="+path; + } + public boolean equals(Object o) + { + if (!(o instanceof UnixSocketAddress)) return false; + return ((UnixSocketAddress) o).path.equals(this.path); + } + public int hashCode() + { + return path.hashCode(); + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/java-unix.h b/app/src/main/java/cx/ath/matthew/unix/java-unix.h new file mode 100644 index 00000000..f0d1ebe3 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/java-unix.h @@ -0,0 +1,112 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class cx_ath_matthew_unix_UnixServerSocket */ + +#ifndef _Included_cx_ath_matthew_unix_UnixServerSocket +#define _Included_cx_ath_matthew_unix_UnixServerSocket +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: cx_ath_matthew_unix_UnixServerSocket + * Method: native_bind + * Signature: (Ljava/lang/String;Z)I + */ +JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_UnixServerSocket_native_1bind + (JNIEnv *, jobject, jstring, jboolean); + +/* + * Class: cx_ath_matthew_unix_UnixServerSocket + * Method: native_close + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_cx_ath_matthew_unix_UnixServerSocket_native_1close + (JNIEnv *, jobject, jint); + +/* + * Class: cx_ath_matthew_unix_UnixServerSocket + * Method: native_accept + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_UnixServerSocket_native_1accept + (JNIEnv *, jobject, jint); + +#ifdef __cplusplus +} +#endif +#endif +/* Header for class cx_ath_matthew_unix_UnixSocket */ + +#ifndef _Included_cx_ath_matthew_unix_UnixSocket +#define _Included_cx_ath_matthew_unix_UnixSocket +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: cx_ath_matthew_unix_UnixSocket + * Method: native_set_pass_cred + * Signature: (IZ)V + */ +JNIEXPORT void JNICALL Java_cx_ath_matthew_unix_UnixSocket_native_1set_1pass_1cred + (JNIEnv *, jobject, jint, jboolean); + +/* + * Class: cx_ath_matthew_unix_UnixSocket + * Method: native_connect + * Signature: (Ljava/lang/String;Z)I + */ +JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_UnixSocket_native_1connect + (JNIEnv *, jobject, jstring, jboolean); + +/* + * Class: cx_ath_matthew_unix_UnixSocket + * Method: native_close + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_cx_ath_matthew_unix_UnixSocket_native_1close + (JNIEnv *, jobject, jint); + +#ifdef __cplusplus +} +#endif +#endif +/* Header for class cx_ath_matthew_unix_USInputStream */ + +#ifndef _Included_cx_ath_matthew_unix_USInputStream +#define _Included_cx_ath_matthew_unix_USInputStream +#ifdef __cplusplus +extern "C" { +#endif +#undef cx_ath_matthew_unix_USInputStream_SKIP_BUFFER_SIZE +#define cx_ath_matthew_unix_USInputStream_SKIP_BUFFER_SIZE 2048L +/* + * Class: cx_ath_matthew_unix_USInputStream + * Method: native_recv + * Signature: (I[BII)I + */ +JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_USInputStream_native_1recv + (JNIEnv *, jobject, jint, jbyteArray, jint, jint); + +#ifdef __cplusplus +} +#endif +#endif +/* Header for class cx_ath_matthew_unix_USOutputStream */ + +#ifndef _Included_cx_ath_matthew_unix_USOutputStream +#define _Included_cx_ath_matthew_unix_USOutputStream +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: cx_ath_matthew_unix_USOutputStream + * Method: native_send + * Signature: (I[BII)I + */ +JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_USOutputStream_native_1send + (JNIEnv *, jobject, jint, jbyteArray, jint, jint); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/app/src/main/java/cx/ath/matthew/unix/testclient.java b/app/src/main/java/cx/ath/matthew/unix/testclient.java new file mode 100644 index 00000000..f390b4a9 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/testclient.java @@ -0,0 +1,51 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.unix; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; + +public class testclient +{ + public static void main(String args[]) throws IOException + { + UnixSocket s = new UnixSocket(new UnixSocketAddress("testsock", true)); + OutputStream os = s.getOutputStream(); + PrintWriter o = new PrintWriter(os); + BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); + String l; + while (null != (l = r.readLine())) { + byte[] buf = (l+"\n").getBytes(); + os.write(buf, 0, buf.length); + } + s.close(); + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/testserver.java b/app/src/main/java/cx/ath/matthew/unix/testserver.java new file mode 100644 index 00000000..5e1d4167 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/testserver.java @@ -0,0 +1,55 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.unix; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; + +public class testserver +{ + public static void main(String args[]) throws IOException + { + UnixServerSocket ss = new UnixServerSocket(new UnixSocketAddress("testsock", true)); + UnixSocket s = ss.accept(); + BufferedReader r = new BufferedReader(new InputStreamReader(s.getInputStream())); + String l; + while (null != (l = r.readLine())) + System.out.println(l);/* + InputStream is = s.getInputStream(); + int r; + do { + r = is.read(); + System.out.print((char)r); + } while (-1 != r);*/ + s.close(); + ss.close(); + } +} diff --git a/app/src/main/java/cx/ath/matthew/utils/Hexdump.java b/app/src/main/java/cx/ath/matthew/utils/Hexdump.java new file mode 100644 index 00000000..02cf5c65 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/utils/Hexdump.java @@ -0,0 +1,149 @@ +/* + * Java Hexdump Library + * + * Copyright (c) Matthew Johnson 2005 + * + * 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. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.utils; + +import java.io.PrintStream; + +public class Hexdump +{ + public static final char[] hexchars = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + public static String toHex(byte[] buf) + { + return toHex(buf, 0, buf.length); + } + public static String toHex(byte[] buf, int ofs, int len) + { + StringBuffer sb = new StringBuffer(); + int j = ofs+len; + for (int i = ofs; i < j; i++) { + if (i < buf.length) { + sb.append(hexchars[(buf[i] & 0xF0) >> 4]); + sb.append(hexchars[buf[i] & 0x0F]); + sb.append(' '); + } else { + sb.append(' '); + sb.append(' '); + sb.append(' '); + } + } + return sb.toString(); + } + + public static String toAscii(byte[] buf) + { + return toAscii(buf, 0, buf.length); + } + public static String toAscii(byte[] buf, int ofs, int len) + { + StringBuffer sb = new StringBuffer(); + int j = ofs+len; + for (int i = ofs; i < j ; i++) { + if (i < buf.length) { + if (20 <= buf[i] && 126 >= buf[i]) + sb.append((char) buf[i]); + else + sb.append('.'); + } else + sb.append(' '); + } + return sb.toString(); + } + public static String format(byte[] buf) + { + return format(buf, 80); + } + public static String format(byte[] buf, int width) + { + int bs = (width - 8) / 4; + int i = 0; + StringBuffer sb = new StringBuffer(); + do { + for (int j = 0; j < 6; j++) { + sb.append(hexchars[(i << (j*4) & 0xF00000) >> 20]); + } + sb.append('\t'); + sb.append(toHex(buf, i, bs)); + sb.append(' '); + sb.append(toAscii(buf, i, bs)); + sb.append('\n'); + i += bs; + } while (i < buf.length); + return sb.toString(); + } + public static void print(byte[] buf) + { + print(buf, System.err); + } + public static void print(byte[] buf, int width) + { + print(buf, width, System.err); + } + public static void print(byte[] buf, int width, PrintStream out) + { + out.print(format(buf, width)); + } + public static void print(byte[] buf, PrintStream out) + { + out.print(format(buf)); + } + /** + * Returns a string which can be written to a Java source file as part + * of a static initializer for a byte array. + * Returns data in the format 0xAB, 0xCD, .... + * use like: + * javafile.print("byte[] data = {") + * javafile.print(Hexdump.toByteArray(data)); + * javafile.println("};"); + */ + public static String toByteArray(byte[] buf) + { + return toByteArray(buf, 0, buf.length); + } + /** + * Returns a string which can be written to a Java source file as part + * of a static initializer for a byte array. + * Returns data in the format 0xAB, 0xCD, .... + * use like: + * javafile.print("byte[] data = {") + * javafile.print(Hexdump.toByteArray(data)); + * javafile.println("};"); + */ + public static String toByteArray(byte[] buf, int ofs, int len) + { + StringBuffer sb = new StringBuffer(); + for (int i = ofs; i < len && i < buf.length; i++) { + sb.append('0'); + sb.append('x'); + sb.append(hexchars[(buf[i] & 0xF0) >> 4]); + sb.append(hexchars[buf[i] & 0x0F]); + if ((i+1) < len && (i+1) < buf.length) + sb.append(','); + } + return sb.toString(); + } +} diff --git a/app/src/main/java/org/asteroidos/sync/MainActivity.java b/app/src/main/java/org/asteroidos/sync/MainActivity.java index e44d7b7b..09243bc3 100644 --- a/app/src/main/java/org/asteroidos/sync/MainActivity.java +++ b/app/src/main/java/org/asteroidos/sync/MainActivity.java @@ -419,16 +419,20 @@ private void btEnableAndScan() { } } + private boolean isBound = false; + @Override protected void onResume() { super.onResume(); - bindService(mSyncServiceIntent, mConnection, Context.BIND_AUTO_CREATE); + isBound = bindService(mSyncServiceIntent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onPause() { super.onPause(); - unbindService(mConnection); + if (isBound) { + unbindService(mConnection); + } } @Override diff --git a/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java b/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java index abbdb668..8dc0007d 100644 --- a/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java +++ b/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java @@ -50,7 +50,7 @@ public class AsteroidBleManager extends BleManager { public final HashMap recvCallbacks; public HashMap sendingCharacteristics; - private int currentMtu = 244; + private int currentMtu = 247; public AsteroidBleManager(@NonNull final Context context, SynchronizationService syncService) { super(context); @@ -162,7 +162,7 @@ public final boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gat @Override protected final void initialize() { beginAtomicRequestQueue() - .add(requestMtu(517) + .add(requestMtu(currentMtu) .with((device, mtu) -> log(Log.INFO, "MTU set to " + mtu)) .fail((device, status) -> log(Log.WARN, "Requested MTU not supported: " + status))) .done(device -> log(Log.INFO, "Target initialized")) diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java b/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java index 676eee65..5eda949e 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java @@ -166,6 +166,8 @@ public void sync() { onActiveSessionsChanged(controllers); if (mMediaSessionManager != null) { mMediaSessionManager.addOnActiveSessionsChangedListener(this, new ComponentName(mCtx, NLService.class)); + } else { + Log.e("MediaSessionManager", "missing MediaSessionManager"); } }); } catch (SecurityException e) { diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java b/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java index de8c71ab..7d716424 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java @@ -1,6 +1,6 @@ /* * AsteroidOSSync - * Copyright (c) 2023 AsteroidOS + * Copyright (c) 2024 AsteroidOS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,13 +26,14 @@ import org.asteroidos.sync.NotificationPreferences; import org.asteroidos.sync.asteroid.IAsteroidDevice; import org.asteroidos.sync.dataobjects.Notification; +import org.asteroidos.sync.services.INotificationHandler; import org.asteroidos.sync.utils.AsteroidUUIDS; import java.util.HashMap; import java.util.Objects; import java.util.UUID; -public class NotificationService implements IConnectivityService { +public class NotificationService implements IConnectivityService, INotificationHandler { public static final String TAG = NotificationService.class.toString(); private final Context mCtx; @@ -82,56 +83,61 @@ public final UUID getServiceUUID() { return AsteroidUUIDS.NOTIFICATION_SERVICE_UUID; } + @Override + public void postNotification(Context context, Intent intent) { + String event = intent.getStringExtra("event"); + if (Objects.equals(event, "posted")) { + String packageName = intent.getStringExtra("packageName"); + NotificationPreferences.putPackageToSeen(context, packageName); + NotificationPreferences.NotificationOption notificationOption = + NotificationPreferences.getNotificationPreferenceForApp(context, packageName); + if (notificationOption == NotificationPreferences.NotificationOption.NO_NOTIFICATIONS) + return; + + int id = intent.getIntExtra("id", 0); + String appName = intent.getStringExtra("appName"); + String appIcon = intent.getStringExtra("appIcon"); + String summary = intent.getStringExtra("summary"); + String body = intent.getStringExtra("body"); + String vibration; + if (notificationOption == NotificationPreferences.NotificationOption.SILENT_NOTIFICATION) + vibration = "none"; + else if (notificationOption == null + || notificationOption == NotificationPreferences.NotificationOption.NORMAL_VIBRATION + || notificationOption == NotificationPreferences.NotificationOption.DEFAULT) + vibration = "normal"; + else if (notificationOption == NotificationPreferences.NotificationOption.STRONG_VIBRATION) + vibration = "strong"; + else if(notificationOption == NotificationPreferences.NotificationOption.RINGTONE_VIBRATION) + vibration = "ringtone"; + else + throw new IllegalArgumentException("Not all options handled"); + + if(intent.hasExtra("vibration")) + vibration = intent.getStringExtra("vibration"); + + Notification notification = new Notification( + Notification.MsgType.POSTED, + packageName, + id, + appName, + appIcon, + summary, + body, + vibration); + + mDevice.send(AsteroidUUIDS.NOTIFICATION_UPDATE_CHAR, notification.toBytes(), NotificationService.this); + } else if (Objects.equals(event, "removed")) { + int id = intent.getIntExtra("id", 0); + + mDevice.send(AsteroidUUIDS.NOTIFICATION_UPDATE_CHAR, new Notification(Notification.MsgType.REMOVED, id).toBytes(), NotificationService.this); + } + } + class NotificationReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - String event = intent.getStringExtra("event"); - if (Objects.equals(event, "posted")) { - String packageName = intent.getStringExtra("packageName"); - NotificationPreferences.putPackageToSeen(context, packageName); - NotificationPreferences.NotificationOption notificationOption = - NotificationPreferences.getNotificationPreferenceForApp(context, packageName); - if (notificationOption == NotificationPreferences.NotificationOption.NO_NOTIFICATIONS) - return; - - int id = intent.getIntExtra("id", 0); - String appName = intent.getStringExtra("appName"); - String appIcon = intent.getStringExtra("appIcon"); - String summary = intent.getStringExtra("summary"); - String body = intent.getStringExtra("body"); - String vibration; - if (notificationOption == NotificationPreferences.NotificationOption.SILENT_NOTIFICATION) - vibration = "none"; - else if (notificationOption == null - || notificationOption == NotificationPreferences.NotificationOption.NORMAL_VIBRATION - || notificationOption == NotificationPreferences.NotificationOption.DEFAULT) - vibration = "normal"; - else if (notificationOption == NotificationPreferences.NotificationOption.STRONG_VIBRATION) - vibration = "strong"; - else if(notificationOption == NotificationPreferences.NotificationOption.RINGTONE_VIBRATION) - vibration = "ringtone"; - else - throw new IllegalArgumentException("Not all options handled"); - - if(intent.hasExtra("vibration")) - vibration = intent.getStringExtra("vibration"); - - Notification notification = new Notification( - Notification.MsgType.POSTED, - packageName, - id, - appName, - appIcon, - summary, - body, - vibration); - - mDevice.send(AsteroidUUIDS.NOTIFICATION_UPDATE_CHAR, notification.toBytes(), NotificationService.this); - } else if (Objects.equals(event, "removed")) { - int id = intent.getIntExtra("id", 0); - - mDevice.send(AsteroidUUIDS.NOTIFICATION_UPDATE_CHAR, new Notification(Notification.MsgType.REMOVED, id).toBytes(), NotificationService.this); - } + postNotification(context, intent); } } } diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java index 47305a15..6a3487e7 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java @@ -19,20 +19,28 @@ package org.asteroidos.sync.connectivity; import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; import android.system.Os; import android.system.OsConstants; import android.system.StructPollfd; import android.util.Log; import org.asteroidos.sync.asteroid.IAsteroidDevice; +import org.asteroidos.sync.dbus.IDBusConnectionCallback; +import org.asteroidos.sync.dbus.IDBusConnectionProvider; import org.asteroidos.sync.utils.AsteroidUUIDS; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; import java.io.FileDescriptor; import java.nio.ByteBuffer; +import java.text.ParseException; import java.util.HashMap; import java.util.UUID; +import java.util.function.Consumer; -public class SlirpService implements IConnectivityService { +public class SlirpService implements IConnectivityService, IDBusConnectionProvider { private final IAsteroidDevice mDevice; @@ -40,12 +48,48 @@ public class SlirpService implements IConnectivityService { private final Thread slirpThread; + private final HandlerThread dBusHandlerThread; + + private final Handler dBusHandler; + private volatile int mtu; + private final ByteBuffer rx = ByteBuffer.allocateDirect(1500); + + private final ByteBuffer tx = ByteBuffer.allocateDirect(1500); + + private final Consumer dBusConnectionRunnable = dBusConnectionCallback -> { + DBusConnection connection = null; + try { + synchronized (DBusConnection.class) { + connection = DBusConnection.getConnection("tcp:host=127.0.0.1,bind=*,port=55556,family=ipv4"); + try { + Log.i("SlirpService", "D-Bus connection acquired: " + connection.getAddress().toString()); + } catch (ParseException e) { + Log.i("SlirpService", "D-Bus connection acquired"); + } + } + } catch (Throwable e) { + Log.e("SlirpService", "Failed to connect to D-Bus", e); + } + if (connection != null) { + try { + dBusConnectionCallback.handleConnection(connection); + } catch (Throwable e) { + Log.e("SlirpService", "An error occurred in a D-Bus callback", e); + } + } + }; + + public SlirpService(Context ctx, IAsteroidDevice device) { mDevice = device; mCtx = ctx; + dBusHandlerThread = new HandlerThread("D-Bus Connection"); + dBusHandlerThread.start(); + dBusHandler = new Handler(dBusHandlerThread.getLooper()); + slirpThread = new Thread(() -> { FileDescriptor fd = getVdeFd(); StructPollfd pollfd = new StructPollfd(); @@ -58,41 +102,59 @@ public SlirpService(Context ctx, IAsteroidDevice device) { continue; } - ByteBuffer rx = ByteBuffer.allocateDirect(mtu); - long read = vdeRecv(rx, 0, mtu); - byte[] data = new byte[(int) read]; - rx.get(data); - mDevice.send(AsteroidUUIDS.SLIRP_OUTGOING_CHAR, data, SlirpService.this); - -// resetMtu(); + synchronized (SlirpService.this) { + rx.clear(); + long read = vdeRecv(rx, 0, mtu - 3); + assert read <= (mtu - 3); + if (read > 0) { + Log.d("SlirpService", "Received " + read + " bytes"); + byte[] data = new byte[(int) read]; + rx.get(data); + mDevice.send(AsteroidUUIDS.SLIRP_OUTGOING_CHAR, data, SlirpService.this); + } else { + Log.e("SlirpService", "Read error: " + read); + } + } } catch (Exception e) { - Log.e("SlirpService", e.toString()); + Log.e("SlirpService", "Poller exception", e); } } }); mtu = mDevice.getMtu(); - initNative(mtu - 14); + startNative(mtu - 3); mDevice.registerCallback(AsteroidUUIDS.SLIRP_INCOMING_CHAR, data -> { resetMtu(); - ByteBuffer tx = ByteBuffer.allocateDirect(data.length); - tx.put(data); - vdeSend(tx, 0, data.length); + synchronized (SlirpService.this) { + tx.clear(); + tx.put(data); + vdeSend(tx, 0, data.length); + } }); slirpThread.start(); } + private void startNative(int mtu) { + initNative(mtu - 14); + + vdeAddFwd(false, "0.0.0.0", 45722, "10.0.2.3", 22); + vdeAddFwd(false, "0.0.0.0", 55555, "10.0.2.3", 55555); + vdeAddFwd(false, "0.0.0.0", 55556, "10.0.2.3", 55556); + } + private void resetMtu() { - int newMtu = mDevice.getMtu(); - if (mtu != newMtu) { - mtu = newMtu; + synchronized (SlirpService.this) { + int newMtu = mDevice.getMtu(); + if (mtu != newMtu) { + mtu = newMtu; - finalizeNative(); - initNative(mtu - 14); + finalizeNative(); + startNative(mtu - 3); + } } } @@ -127,8 +189,22 @@ public UUID getServiceUUID() { return AsteroidUUIDS.SLIRP_SERVICE_UUID; } + public void acquireDBusConnection(IDBusConnectionCallback dBusConnectionCallback) { + dBusHandler.post(() -> dBusConnectionRunnable.accept(dBusConnectionCallback)); + } + + @Override + public void acquireDBusConnectionLater(IDBusConnectionCallback dBusConnectionCallback, long delay) { + dBusHandler.postDelayed(() -> dBusConnectionRunnable.accept(dBusConnectionCallback), delay); + } + + @SuppressWarnings({"unused", "FieldMayBeFinal"}) // used internally by the JNI part private long mySlirp = 0; + private native int vdeAddUnixFwd(String path, String ip, int port); + + private native int vdeAddFwd(boolean udp, String hostip, int hostport, String ip, int port); + private native void initNative(int mtu); private native void finalizeNative(); diff --git a/app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationService.java b/app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationService.java new file mode 100644 index 00000000..5b263441 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationService.java @@ -0,0 +1,169 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.dbus; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; + +import org.asteroidos.sync.NotificationPreferences; +import org.freedesktop.Notifications; +import org.asteroidos.sync.services.INotificationHandler; +import org.freedesktop.dbus.DBusSigHandler; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.Variant; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class DBusNotificationService implements INotificationHandler, DBusSigHandler { + private final IDBusConnectionProvider connectionProvider; + + private static final HashFunction murmur32 = Hashing.murmur3_32_fixed(0); + + private final BiMap mapping = HashBiMap.create(); + + private Context mCtx; + + private NotificationReceiver mNReceiver; + + public DBusNotificationService(Context ctx, IDBusConnectionProvider connectionProvider) { + this.mCtx = ctx; + this.connectionProvider = connectionProvider; + + connectionProvider.acquireDBusConnectionLater(notify -> Log.w("DBusNotificationService", Arrays.toString(notify.getNames())), 1000); + } + + @Override + public void postNotification(Context context, Intent intent) { + String event = intent.getStringExtra("event"); + + if (Objects.equals(event, "posted")) { + String packageName = intent.getStringExtra("packageName"); + NotificationPreferences.putPackageToSeen(context, packageName); + NotificationPreferences.NotificationOption notificationOption = + NotificationPreferences.getNotificationPreferenceForApp(context, packageName); + if (notificationOption == NotificationPreferences.NotificationOption.NO_NOTIFICATIONS) + return; + + String key = Objects.requireNonNull(intent.getStringExtra("key")); + String appName = intent.getStringExtra("appName"); + String appIcon = intent.getStringExtra("appIcon"); + String summary = intent.getStringExtra("summary"); + String body = intent.getStringExtra("body"); + String vibration; + if (notificationOption == NotificationPreferences.NotificationOption.SILENT_NOTIFICATION) + vibration = "notif_silent"; + else if (notificationOption == null + || notificationOption == NotificationPreferences.NotificationOption.NORMAL_VIBRATION + || notificationOption == NotificationPreferences.NotificationOption.DEFAULT) + vibration = "notif_normal"; + else if (notificationOption == NotificationPreferences.NotificationOption.STRONG_VIBRATION) + vibration = "notif_strong"; + else if (notificationOption == NotificationPreferences.NotificationOption.RINGTONE_VIBRATION) + vibration = "ringtone"; + else + throw new IllegalArgumentException("Not all options handled"); + + connectionProvider.acquireDBusConnectionLater(notify -> { + synchronized (mapping) { + mapping.put(key, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications.class) + .Notify(appName, mapping.getOrDefault(key, new UInt32(0)), appIcon, summary, body, Collections.emptyList(), + Map.of( + "x-nemo-feedback", new Variant<>(vibration), + "x-nemo-preview-body", new Variant<>(body), + "x-nemo-preview-summary", new Variant<>(summary), + "urgency", new Variant<>((byte) 3)), 0)); + } + }, 500); + } else if (Objects.equals(event, "removed")) { + String key = Objects.requireNonNull(intent.getStringExtra("key")); + // Avoid an infinite loop when the user dismisses the notification on the watch + if (mapping.containsKey(key)) { + UInt32 id; + synchronized (mapping) { + id = mapping.get(key); + mapping.remove(key); + } + connectionProvider.acquireDBusConnectionLater(notify -> notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications.class) + .CloseNotification(id), 500); + } + } + } + + @Override + public void handle(Notifications.NotificationClosed s) { + synchronized (mapping) { + if (s.reason == Notifications.NotificationClosed.REASON_DISMISSED_BY_USER + && mapping.containsValue(s.id)) { + Intent dismiss = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE"); + dismiss.putExtra("command", "dismiss"); + dismiss.putExtra("key", mapping.inverse().get(s.id)); + + mapping.inverse().remove(s.id); + mCtx.sendBroadcast(dismiss); + } + } + } + + @Override + public void sync() { + if (mNReceiver == null) { + IntentFilter filter = new IntentFilter(); + filter.addAction("org.asteroidos.sync.NOTIFICATION_LISTENER"); + mNReceiver = new NotificationReceiver(); + mCtx.registerReceiver(mNReceiver, filter); + + Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE"); + i.putExtra("command", "refresh"); + mCtx.sendBroadcast(i); + } + connectionProvider.acquireDBusConnection(notify -> notify.addSigHandler(Notifications.NotificationClosed.class, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications.class), DBusNotificationService.this)); + } + + @Override + public void unsync() { + connectionProvider.acquireDBusConnection(notify -> notify.removeSigHandler(Notifications.NotificationClosed.class, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications.class), DBusNotificationService.this)); + if (mNReceiver != null) { + try { + mCtx.unregisterReceiver(mNReceiver); + } catch (IllegalArgumentException ignored) { + } + mNReceiver = null; + } + } + + class NotificationReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + postNotification(context, intent); + } + } +} diff --git a/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.java b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.java new file mode 100644 index 00000000..146b60a7 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.java @@ -0,0 +1,27 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.dbus; + +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; + +public interface IDBusConnectionCallback { + + public void handleConnection(DBusConnection connection) throws DBusException; +} diff --git a/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.java b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.java new file mode 100644 index 00000000..88e144a7 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.java @@ -0,0 +1,25 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.dbus; + +public interface IDBusConnectionProvider { + public void acquireDBusConnection(IDBusConnectionCallback dBusConnectionConsumer); + + public void acquireDBusConnectionLater(IDBusConnectionCallback dBusConnectionConsumer, long delay); +} diff --git a/app/src/main/java/org/asteroidos/sync/services/INotificationHandler.java b/app/src/main/java/org/asteroidos/sync/services/INotificationHandler.java new file mode 100644 index 00000000..57f3d48c --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/services/INotificationHandler.java @@ -0,0 +1,28 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.services; + +import android.content.Context; +import android.content.Intent; + +import org.asteroidos.sync.connectivity.IService; + +public interface INotificationHandler extends IService { + void postNotification(Context context, Intent intent); +} diff --git a/app/src/main/java/org/asteroidos/sync/services/NLService.java b/app/src/main/java/org/asteroidos/sync/services/NLService.java index d8626c73..a1104861 100644 --- a/app/src/main/java/org/asteroidos/sync/services/NLService.java +++ b/app/src/main/java/org/asteroidos/sync/services/NLService.java @@ -33,11 +33,17 @@ import androidx.core.app.NotificationCompat; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; + import org.asteroidos.sync.utils.NotificationParser; +import java.nio.charset.Charset; import java.util.Arrays; +import java.util.HashMap; import java.util.Hashtable; import java.util.Map; +import java.util.Objects; import java.util.concurrent.TimeUnit; public class NLService extends NotificationListenerService { @@ -157,7 +163,7 @@ public void onDestroy() { @Override public void onNotificationPosted(StatusBarNotification sbn) { - Notification notification = sbn.getNotification(); + final Notification notification = sbn.getNotification(); String packageName = sbn.getPackageName(); String[] allowedOngoingApps = {"com.google.android.apps.maps", "org.thoughtcrime.securesms"}; @@ -171,7 +177,7 @@ public void onNotificationPosted(StatusBarNotification sbn) { NotificationParser notifParser = new NotificationParser(notification); String summary = notifParser.summary; String body = notifParser.body; - int id = sbn.getId(); + String key = sbn.getKey(); String appIcon = iconFromPackage.get(packageName); String appName = ""; @@ -192,7 +198,7 @@ public void onNotificationPosted(StatusBarNotification sbn) { Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER"); i.putExtra("event", "posted"); i.putExtra("packageName", packageName); - i.putExtra("id", id); + i.putExtra("key", key); i.putExtra("appName", appName); i.putExtra("appIcon", appIcon); i.putExtra("summary", summary); @@ -205,7 +211,7 @@ public void onNotificationPosted(StatusBarNotification sbn) { public void onNotificationRemoved(StatusBarNotification sbn) { Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER"); i.putExtra("event", "removed"); - i.putExtra("id", sbn.getId()); + i.putExtra("key", sbn.getKey()); sendBroadcast(i); } @@ -225,7 +231,7 @@ public void onListenerConnected() { class NLServiceReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (intent.getStringExtra("command").equals("refresh")) { + if (Objects.equals(intent.getStringExtra("command"), "refresh")) { Handler handler = new Handler(); handler.postDelayed(() -> { while (!listenerConnected) { @@ -240,6 +246,11 @@ public void onReceive(Context context, Intent intent) { for (StatusBarNotification notif : notifs) onNotificationPosted(notif); }, 500); + } else if (Objects.equals(intent.getStringExtra("command"), "dismiss")) { + String key = intent.getStringExtra("key"); + if (key != null) { + new Handler().post(() -> cancelNotification(key)); + } } } } diff --git a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java index 9a3f43c7..da8e9d1f 100644 --- a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java +++ b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java @@ -48,12 +48,12 @@ import org.asteroidos.sync.connectivity.IService; import org.asteroidos.sync.connectivity.IServiceCallback; import org.asteroidos.sync.connectivity.MediaService; -import org.asteroidos.sync.connectivity.NotificationService; import org.asteroidos.sync.connectivity.ScreenshotService; import org.asteroidos.sync.connectivity.SilentModeService; import org.asteroidos.sync.connectivity.SlirpService; import org.asteroidos.sync.connectivity.TimeService; import org.asteroidos.sync.connectivity.WeatherService; +import org.asteroidos.sync.dbus.DBusNotificationService; import java.util.ArrayList; import java.util.Arrays; @@ -281,17 +281,20 @@ public void onCreate() { mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(defaultDevMacAddr); } - if (nonBleServices.isEmpty()) - nonBleServices.add(new SilentModeService(getApplicationContext())); + SlirpService slirpService = new SlirpService(getApplicationContext(), this); if (bleServices.isEmpty()) { // Register Services registerBleService(new MediaService(getApplicationContext(), this)); - registerBleService(new NotificationService(getApplicationContext(), this)); registerBleService(new WeatherService(getApplicationContext(), this)); registerBleService(new ScreenshotService(getApplicationContext(), this)); registerBleService(new TimeService(getApplicationContext(), this)); - registerBleService(new SlirpService(getApplicationContext(), this)); + registerBleService(slirpService); + } + + if (nonBleServices.isEmpty()) { + nonBleServices.add(new SilentModeService(getApplicationContext())); + nonBleServices.add(new DBusNotificationService(getApplicationContext(), slirpService)); } handleConnect(); diff --git a/app/src/main/java/org/asteroidos/sync/utils/AppInfoHelper.java b/app/src/main/java/org/asteroidos/sync/utils/AppInfoHelper.java index aeccd41e..58cbc1c4 100644 --- a/app/src/main/java/org/asteroidos/sync/utils/AppInfoHelper.java +++ b/app/src/main/java/org/asteroidos/sync/utils/AppInfoHelper.java @@ -67,10 +67,14 @@ public static ArrayList getPackageInfo(Context context) } } else { - icon = Bitmap.createBitmap(apkIcon.getIntrinsicWidth(), apkIcon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(icon); - apkIcon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - apkIcon.draw(canvas); + if (apkIcon.getIntrinsicWidth() <= 0 || apkIcon.getIntrinsicHeight() <= 0) + icon = Bitmap.createBitmap(144, 144, Bitmap.Config.ARGB_8888); + else { + icon = Bitmap.createBitmap(apkIcon.getIntrinsicWidth(), apkIcon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(icon); + apkIcon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + apkIcon.draw(canvas); + } } } catch(ClassCastException ignored) {} AppInfo appInfo = new AppInfo(pinfo.packageName, diff --git a/app/src/main/java/org/freedesktop/DBus.java b/app/src/main/java/org/freedesktop/DBus.java new file mode 100644 index 00000000..5d99a648 --- /dev/null +++ b/app/src/main/java/org/freedesktop/DBus.java @@ -0,0 +1,490 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import java.util.Map; +import java.util.List; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.Tuple; +import org.freedesktop.dbus.UInt16; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.UInt64; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +public interface DBus extends DBusInterface +{ + public static final int DBUS_NAME_FLAG_ALLOW_REPLACEMENT = 0x01; + public static final int DBUS_NAME_FLAG_REPLACE_EXISTING = 0x02; + public static final int DBUS_NAME_FLAG_DO_NOT_QUEUE = 0x04; + public static final int DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = 1; + public static final int DBUS_REQUEST_NAME_REPLY_IN_QUEUE = 2; + public static final int DBUS_REQUEST_NAME_REPLY_EXISTS = 3; + public static final int DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER = 4; + public static final int DBUS_RELEASE_NAME_REPLY_RELEASED = 1; + public static final int DBUS_RELEASE_NAME_REPLY_NON_EXISTANT = 2; + public static final int DBUS_RELEASE_NAME_REPLY_NOT_OWNER = 3; + public static final int DBUS_START_REPLY_SUCCESS = 1; + public static final int DBUS_START_REPLY_ALREADY_RUNNING = 2; + /** + * All DBus Applications should respond to the Ping method on this interface + */ + public interface Peer extends DBusInterface + { + public void Ping(); + } + /** + * Objects can provide introspection data via this interface and method. + * See the Introspection Format. + */ + public interface Introspectable extends DBusInterface + { + /** + * @return The XML introspection data for this object + */ + public String Introspect(); + } + /** + * A standard properties interface. + */ + public interface Properties extends DBusInterface + { + /** + * Get the value for the given property. + * @param interface_name The interface this property is associated with. + * @param property_name The name of the property. + * @return The value of the property (may be any valid DBus type). + */ + public A Get (String interface_name, String property_name); + /** + * Set the value for the given property. + * @param interface_name The interface this property is associated with. + * @param property_name The name of the property. + * @param value The new value of the property (may be any valid DBus type). + */ + public void Set (String interface_name, String property_name, A value); + /** + * Get all properties and values. + * @param interface_name The interface the properties is associated with. + * @return The properties mapped to their values. + */ + public Map GetAll (String interface_name); + } + /** + * Messages generated locally in the application. + */ + public interface Local extends DBusInterface + { + public class Disconnected extends DBusSignal + { + public Disconnected(String path) throws DBusException + { + super(path); + } + } + } + + /** + * Initial message to register ourselves on the Bus. + * @return The unique name of this connection to the Bus. + */ + public String Hello(); + /** + * Lists all connected names on the Bus. + * @return An array of all connected names. + */ + public String[] ListNames(); + /** + * Determine if a name has an owner. + * @param name The name to query. + * @return true if the name has an owner. + */ + public boolean NameHasOwner(String name); + /** + * Get the connection unique name that owns the given name. + * @param name The name to query. + * @return The connection which owns the name. + */ + public String GetNameOwner(String name); + /** + * Get the Unix UID that owns a connection name. + * @param connection_name The connection name. + * @return The Unix UID that owns it. + */ + public UInt32 GetConnectionUnixUser(String connection_name); + /** + * Start a service. If the given service is not provided + * by any application, it will be started according to the .service file + * for that service. + * @param name The service name to start. + * @param flags Unused. + * @return DBUS_START_REPLY constants. + */ + public UInt32 StartServiceByName(String name, UInt32 flags); + /** + * Request a name on the bus. + * @param name The name to request. + * @param flags DBUS_NAME flags. + * @return DBUS_REQUEST_NAME_REPLY constants. + */ + public UInt32 RequestName(String name, UInt32 flags); + /** + * Release a name on the bus. + * @param name The name to release. + * @return DBUS_RELEASE_NAME_REPLY constants. + */ + public UInt32 ReleaseName(String name); + + /** + * Add a match rule. + * Will cause you to receive messages that aren't directed to you which + * match this rule. + * @param matchrule The Match rule as a string. Format Undocumented. + */ + public void AddMatch(String matchrule) throws Error.MatchRuleInvalid; + + /** + * Remove a match rule. + * Will cause you to stop receiving messages that aren't directed to you which + * match this rule. + * @param matchrule The Match rule as a string. Format Undocumented. + */ + public void RemoveMatch(String matchrule) throws Error.MatchRuleInvalid; + + /** + * List the connections currently queued for a name. + * @param name The name to query + * @return A list of unique connection IDs. + */ + public String[] ListQueuedOwners(String name); + + /** + * Returns the proccess ID associated with a connection. + * @param connection_name The name of the connection + * @return The PID of the connection. + */ + public UInt32 GetConnectionUnixProcessID(String connection_name); + + /** + * Does something undocumented. + */ + public Byte[] GetConnectionSELinuxSecurityContext(String a); + + /** + * Does something undocumented. + */ + public void ReloadConfig(); + + /** + * Signal sent when the owner of a name changes + */ + public class NameOwnerChanged extends DBusSignal + { + public final String name; + public final String old_owner; + public final String new_owner; + public NameOwnerChanged(String path, String name, String old_owner, String new_owner) throws DBusException + { + super(path, new Object[] { name, old_owner, new_owner }); + this.name = name; + this.old_owner = old_owner; + this.new_owner = new_owner; + } + } + /** + * Signal sent to a connection when it loses a name + */ + public class NameLost extends DBusSignal + { + public final String name; + public NameLost(String path, String name) throws DBusException + { + super(path, name); + this.name = name; + } + } + /** + * Signal sent to a connection when it aquires a name + */ + public class NameAcquired extends DBusSignal + { + public final String name; + public NameAcquired(String path, String name) throws DBusException + { + super(path, name); + this.name = name; + } + } + /** + * Contains standard errors that can be thrown from methods. + */ + public interface Error + { + /** + * Thrown if the method called was unknown on the remote object + */ + @SuppressWarnings("serial") + public class UnknownMethod extends DBusExecutionException + { + public UnknownMethod(String message) + { + super(message); + } + } + /** + * Thrown if the object was unknown on a remote connection + */ + @SuppressWarnings("serial") + public class UnknownObject extends DBusExecutionException + { + public UnknownObject(String message) + { + super(message); + } + } + /** + * Thrown if the requested service was not available + */ + @SuppressWarnings("serial") + public class ServiceUnknown extends DBusExecutionException + { + public ServiceUnknown(String message) + { + super(message); + } + } + /** + * Thrown if the match rule is invalid + */ + @SuppressWarnings("serial") + public class MatchRuleInvalid extends DBusExecutionException + { + public MatchRuleInvalid(String message) + { + super(message); + } + } + /** + * Thrown if there is no reply to a method call + */ + @SuppressWarnings("serial") + public class NoReply extends DBusExecutionException + { + public NoReply(String message) + { + super(message); + } + } + /** + * Thrown if a message is denied due to a security policy + */ + @SuppressWarnings("serial") + public class AccessDenied extends DBusExecutionException + { + public AccessDenied(String message) + { + super(message); + } + } + } + /** + * Description of the interface or method, returned in the introspection data + */ + @Retention(RetentionPolicy.RUNTIME) + public @interface Description + { + String value(); + } + /** + * Indicates that a DBus interface or method is deprecated + */ + @Retention(RetentionPolicy.RUNTIME) + public @interface Deprecated {} + /** + * Contains method-specific annotations + */ + public interface Method + { + /** + * Methods annotated with this do not send a reply + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface NoReply {} + /** + * Give an error that the method can return + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Error + { + String value(); + } + } + /** + * Contains GLib-specific annotations + */ + public interface GLib + { + /** + * Define a C symbol to map to this method. Used by GLib only + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface CSymbol + { + String value(); + } + } + /** + * Contains Binding-test interfaces + */ + public interface Binding + { + public interface SingleTests extends DBusInterface + { + @Description("Returns the sum of the values in the input list") + public UInt32 Sum(byte[] a); + } + public interface TestClient extends DBusInterface + { + @Description("when the trigger signal is received, this method should be called on the sending process/object.") + public void Response(UInt16 a, double b); + @Description("Causes a callback") + public static class Trigger extends DBusSignal + { + public final UInt16 a; + public final double b; + public Trigger(String path, UInt16 a, double b) throws DBusException + { + super(path, a, b); + this.a = a; + this.b = b; + } + } + + } + public interface Tests extends DBusInterface + { + @Description("Returns whatever it is passed") + public Variant Identity(Variant input); + @Description("Returns whatever it is passed") + public byte IdentityByte(byte input); + @Description("Returns whatever it is passed") + public boolean IdentityBool(boolean input); + @Description("Returns whatever it is passed") + public short IdentityInt16(short input); + @Description("Returns whatever it is passed") + public UInt16 IdentityUInt16(UInt16 input); + @Description("Returns whatever it is passed") + public int IdentityInt32(int input); + @Description("Returns whatever it is passed") + public UInt32 IdentityUInt32(UInt32 input); + @Description("Returns whatever it is passed") + public long IdentityInt64(long input); + @Description("Returns whatever it is passed") + public UInt64 IdentityUInt64(UInt64 input); + @Description("Returns whatever it is passed") + public double IdentityDouble(double input); + @Description("Returns whatever it is passed") + public String IdentityString(String input); + @Description("Returns whatever it is passed") + public Variant[] IdentityArray(Variant[] input); + @Description("Returns whatever it is passed") + public byte[] IdentityByteArray(byte[] input); + @Description("Returns whatever it is passed") + public boolean[] IdentityBoolArray(boolean[] input); + @Description("Returns whatever it is passed") + public short[] IdentityInt16Array(short[] input); + @Description("Returns whatever it is passed") + public UInt16[] IdentityUInt16Array(UInt16[] input); + @Description("Returns whatever it is passed") + public int[] IdentityInt32Array(int[] input); + @Description("Returns whatever it is passed") + public UInt32[] IdentityUInt32Array(UInt32[] input); + @Description("Returns whatever it is passed") + public long[] IdentityInt64Array(long[] input); + @Description("Returns whatever it is passed") + public UInt64[] IdentityUInt64Array(UInt64[] input); + @Description("Returns whatever it is passed") + public double[] IdentityDoubleArray(double[] input); + @Description("Returns whatever it is passed") + public String[] IdentityStringArray(String[] input); + @Description("Returns the sum of the values in the input list") + public long Sum(int[] a); + @Description("Given a map of A => B, should return a map of B => a list of all the As which mapped to B") + public Map> InvertMapping(Map a); + @Description("This method returns the contents of a struct as separate values") + public Triplet DeStruct(TestStruct a); + @Description("Given any compound type as a variant, return all the primitive types recursively contained within as an array of variants") + public List> Primitize(Variant a); + @Description("inverts it's input") + public boolean Invert(boolean a); + @Description("triggers sending of a signal from the supplied object with the given parameter") + public void Trigger(String a, UInt64 b); + @Description("Causes the server to exit") + public void Exit(); + } + public interface TestSignals extends DBusInterface + { + @Description("Sent in response to a method call") + public static class Triggered extends DBusSignal + { + public final UInt64 a; + public Triggered(String path, UInt64 a) throws DBusException + { + super(path, a); + this.a = a; + } + } + } + public final class Triplet extends Tuple + { + @Position(0) + public final A a; + @Position(1) + public final B b; + @Position(2) + public final C c; + public Triplet(A a, B b, C c) + { + this.a = a; + this.b = b; + this.c = c; + } + } + public final class TestStruct extends Struct + { + @Position(0) + public final String a; + @Position(1) + public final UInt32 b; + @Position(2) + public final Short c; + public TestStruct(String a, UInt32 b, Short c) + { + this.a = a; + this.b = b; + this.c = c; + } + } + } +} diff --git a/app/src/main/java/org/freedesktop/Notifications.java b/app/src/main/java/org/freedesktop/Notifications.java new file mode 100644 index 00000000..e873a5b2 --- /dev/null +++ b/app/src/main/java/org/freedesktop/Notifications.java @@ -0,0 +1,52 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.freedesktop; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; +import java.util.Map; + +public interface Notifications extends DBusInterface { + + public static class NotificationClosed extends DBusSignal { + public static final int REASON_EXPIRED = 1; + public static final int REASON_DISMISSED_BY_USER = 2; + public static final int REASON_CLOSE_NOTIFICATION_CALLED = 3; + public static final int REASON_RESERVED = 4; + + public final UInt32 id; + public final int reason; + public NotificationClosed(String path, UInt32 id, UInt32 reason) throws DBusException { + super(path, id, reason); + this.id = id; + this.reason = reason.intValue(); + } + } + + public List GetCapabilities(); + + public UInt32 Notify(String app_name, UInt32 replaces_id, String app_icon, String summary, String body, List actions, Map hints, int expire_timeout); + + public void CloseNotification(UInt32 id); +} diff --git a/app/src/main/java/org/freedesktop/dbus/AbstractConnection.java b/app/src/main/java/org/freedesktop/dbus/AbstractConnection.java new file mode 100644 index 00000000..2b289cc4 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/AbstractConnection.java @@ -0,0 +1,1030 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import java.io.File; +import java.io.IOException; + +import java.text.MessageFormat; +import java.text.ParseException; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Properties; +import java.util.Vector; + +import java.util.regex.Pattern; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.exceptions.NotConnected; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.FatalDBusException; +import org.freedesktop.dbus.exceptions.FatalException; + +import cx.ath.matthew.debug.Debug; + + +/** Handles a connection to DBus. + */ +public abstract class AbstractConnection +{ + protected class FallbackContainer + { + private Map fallbacks = new HashMap(); + public synchronized void add(String path, ExportedObject eo) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Adding fallback on "+path+" of "+eo); + fallbacks.put(path.split("/"), eo); + } + public synchronized void remove(String path) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Removing fallback on "+path); + fallbacks.remove(path.split("/")); + } + public synchronized ExportedObject get(String path) + { + int best = 0; + int i = 0; + ExportedObject bestobject = null; + String[] pathel = path.split("/"); + for (String[] fbpath: fallbacks.keySet()) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Trying fallback path "+Arrays.deepToString(fbpath)+" to match "+Arrays.deepToString(pathel)); + for (i = 0; i < pathel.length && i < fbpath.length; i++) + if (!pathel[i].equals(fbpath[i])) break; + if (i > 0 && i == fbpath.length && i > best) + bestobject = fallbacks.get(fbpath); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Matches "+i+" bestobject now "+bestobject); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "Found fallback for "+path+" of "+bestobject); + return bestobject; + } + } + protected class _thread extends Thread + { + public _thread() + { + setName("DBusConnection"); + } + public void run() + { + try { + Message m = null; + while (_run) { + m = null; + + // read from the wire + try { + // this blocks on outgoing being non-empty or a message being available. + m = readIncoming(); + if (m != null) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Got Incoming Message: "+m); + synchronized (this) { notifyAll(); } + + if (m instanceof DBusSignal) + handleMessage((DBusSignal) m); + else if (m instanceof MethodCall) + handleMessage((MethodCall) m); + else if (m instanceof MethodReturn) + handleMessage((MethodReturn) m); + else if (m instanceof Error) + handleMessage((Error) m); + + m = null; + } + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + if (e instanceof FatalException) { + disconnect(); + } + } + + } + synchronized (this) { notifyAll(); } + } catch (Exception e) { + if (Debug.debug && EXCEPTION_DEBUG) Debug.print(Debug.ERR, e); + } + } + } + private class _globalhandler implements org.freedesktop.DBus.Peer, org.freedesktop.DBus.Introspectable + { + private String objectpath; + public _globalhandler() + { + this.objectpath = null; + } + public _globalhandler(String objectpath) + { + this.objectpath = objectpath; + } + public boolean isRemote() { return false; } + public void Ping() { return; } + public String Introspect() + { + String intro = objectTree.Introspect(objectpath); + if (null == intro) { + ExportedObject eo = fallbackcontainer.get(objectpath); + if (null != eo) intro = eo.introspectiondata; + } + if (null == intro) + throw new DBus.Error.UnknownObject("Introspecting on non-existant object"); + else return + "\n"+intro; + } + } + protected class _workerthread extends Thread + { + private boolean _run = true; + public void halt() + { + _run = false; + } + public void run() + { + while (_run) { + Runnable r = null; + synchronized (runnables) { + while (runnables.size() == 0 && _run) + try { runnables.wait(); } catch (InterruptedException Ie) {} + if (runnables.size() > 0) + r = runnables.removeFirst(); + } + if (null != r) r.run(); + } + } + } + private class _sender extends Thread + { + public _sender() + { + setName("Sender"); + } + public void run() + { + Message m = null; + + if (Debug.debug) Debug.print(Debug.INFO, "Monitoring outbound queue"); + // block on the outbound queue and send from it + while (_run) { + if (null != outgoing) synchronized (outgoing) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking"); + while (outgoing.size() == 0 && _run) + try { outgoing.wait(); } catch (InterruptedException Ie) {} + if (Debug.debug) Debug.print(Debug.VERBOSE, "Notified"); + if (outgoing.size() > 0) + m = outgoing.remove(); + if (Debug.debug) Debug.print(Debug.DEBUG, "Got message: "+m); + } + if (null != m) + sendMessage(m); + m = null; + } + + if (Debug.debug) Debug.print(Debug.INFO, "Flushing outbound queue and quitting"); + // flush the outbound queue before disconnect. + if (null != outgoing) do { + EfficientQueue ogq = outgoing; + synchronized (ogq) { + outgoing = null; + } + if (!ogq.isEmpty()) + m = ogq.remove(); + else m = null; + sendMessage(m); + } while (null != m); + + // close the underlying streams + } + } + /** + * Timeout in us on checking the BUS for incoming messages and sending outgoing messages + */ + protected static final int TIMEOUT = 100000; + /** Initial size of the pending calls map */ + private static final int PENDING_MAP_INITIAL_SIZE = 10; + static final String BUSNAME_REGEX = "^[-_a-zA-Z][-_a-zA-Z0-9]*(\\.[-_a-zA-Z][-_a-zA-Z0-9]*)*$"; + static final String CONNID_REGEX = "^:[0-9]*\\.[0-9]*$"; + static final String OBJECT_REGEX = "^/([-_a-zA-Z0-9]+(/[-_a-zA-Z0-9]+)*)?$"; + static final byte THREADCOUNT = 4; + static final int MAX_ARRAY_LENGTH = 67108864; + static final int MAX_NAME_LENGTH = 255; + protected Map exportedObjects; + private ObjectTree objectTree; + private _globalhandler _globalhandlerreference; + protected Map importedObjects; + protected Map>> handledSignals; + protected EfficientMap pendingCalls; + protected Map> pendingCallbacks; + protected Map> pendingCallbackReplys; + protected LinkedList runnables; + protected LinkedList<_workerthread> workers; + protected FallbackContainer fallbackcontainer; + protected boolean _run; + EfficientQueue outgoing; + LinkedList pendingErrors; + private static final Map infomap = new HashMap(); + protected _thread thread; + protected _sender sender; + protected Transport transport; + protected String addr; + protected boolean weakreferences = false; + static final Pattern dollar_pattern = Pattern.compile("[$]"); + public static final boolean EXCEPTION_DEBUG; + static final boolean FLOAT_SUPPORT; + protected boolean connected = false; + static { + FLOAT_SUPPORT = (null != System.getenv("DBUS_JAVA_FLOATS")); + EXCEPTION_DEBUG = (null != System.getenv("DBUS_JAVA_EXCEPTION_DEBUG")); + if (EXCEPTION_DEBUG) { + Debug.print("Debugging of internal exceptions enabled"); + Debug.setThrowableTraces(true); + } + if (Debug.debug) { + File f = new File("debug.conf"); + if (f.exists()) { + Debug.print("Loading debug config file: "+f); + try { + Debug.loadConfig(f); + } catch (IOException IOe) {} + } else { + Properties p = new Properties(); + p.setProperty("ALL", "INFO"); + Debug.print("debug config file "+f+" does not exist, not loading."); + } + Debug.setHexDump(true); + } + } + + protected AbstractConnection(String address) throws DBusException + { + exportedObjects = new HashMap(); + importedObjects = new HashMap(); + _globalhandlerreference = new _globalhandler(); + synchronized (exportedObjects) { + exportedObjects.put(null, new ExportedObject(_globalhandlerreference, weakreferences)); + } + handledSignals = new HashMap>>(); + pendingCalls = new EfficientMap(PENDING_MAP_INITIAL_SIZE); + outgoing = new EfficientQueue(PENDING_MAP_INITIAL_SIZE); + pendingCallbacks = new HashMap>(); + pendingCallbackReplys = new HashMap>(); + pendingErrors = new LinkedList(); + runnables = new LinkedList(); + workers = new LinkedList<_workerthread>(); + objectTree = new ObjectTree(); + fallbackcontainer = new FallbackContainer(); + synchronized (workers) { + for (int i = 0; i < THREADCOUNT; i++) { + _workerthread t = new _workerthread(); + t.start(); + workers.add(t); + } + } + _run = true; + addr = address; + } + + protected void listen() + { + // start listening + thread = new _thread(); + thread.start(); + sender = new _sender(); + sender.start(); + } + + /** + * Change the number of worker threads to receive method calls and handle signals. + * Default is 4 threads + * @param newcount The new number of worker Threads to use. + */ + public void changeThreadCount(byte newcount) + { + synchronized (workers) { + if (workers.size() > newcount) { + int n = workers.size() - newcount; + for (int i = 0; i < n; i++) { + _workerthread t = workers.removeFirst(); + t.halt(); + } + } else if (workers.size() < newcount) { + int n = newcount-workers.size(); + for (int i = 0; i < n; i++) { + _workerthread t = new _workerthread(); + t.start(); + workers.add(t); + } + } + } + } + private void addRunnable(Runnable r) + { + synchronized(runnables) { + runnables.add(r); + runnables.notifyAll(); + } + } + + String getExportedObject(DBusInterface i) throws DBusException + { + synchronized (exportedObjects) { + for (String s: exportedObjects.keySet()) + if (i.equals(exportedObjects.get(s).object.get())) + return s; + } + + String s = importedObjects.get(i).objectpath; + if (null != s) return s; + + throw new DBusException("Not an object exported or imported by this connection"); + } + + abstract DBusInterface getExportedObject(String source, String path) throws DBusException; + + /** + * Returns a structure with information on the current method call. + * @return the DBusCallInfo for this method call, or null if we are not in a method call. + */ + public static DBusCallInfo getCallInfo() + { + DBusCallInfo info; + synchronized (infomap) { + info = infomap.get(Thread.currentThread()); + } + return info; + } + + /** + * If set to true the bus will not hold a strong reference to exported objects. + * If they go out of scope they will automatically be unexported from the bus. + * The default is to hold a strong reference, which means objects must be + * explicitly unexported before they will be garbage collected. + */ + public void setWeakReferences(boolean weakreferences) + { + this.weakreferences = weakreferences; + } + + /** + * Export an object so that its methods can be called on DBus. + * @param objectpath The path to the object we are exposing. MUST be in slash-notation, like "/org/freedesktop/Local", + * and SHOULD end with a capitalised term. Only one object may be exposed on each path at any one time, but an object + * may be exposed on several paths at once. + * @param object The object to export. + * @throws DBusException If the objectpath is already exporting an object. + * or if objectpath is incorrectly formatted, + */ + public void exportObject(String objectpath, DBusInterface object) throws DBusException + { + if (null == objectpath || "".equals(objectpath)) + throw new DBusException($_("Must Specify an Object Path")); + if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + synchronized (exportedObjects) { + if (null != exportedObjects.get(objectpath)) + throw new DBusException($_("Object already exported")); + ExportedObject eo = new ExportedObject(object, weakreferences); + exportedObjects.put(objectpath, eo); + objectTree.add(objectpath, eo, eo.introspectiondata); + } + } + /** + * Export an object as a fallback object. + * This object will have it's methods invoked for all paths starting + * with this object path. + * @param objectprefix The path below which the fallback handles calls. + * MUST be in slash-notation, like "/org/freedesktop/Local", + * @param object The object to export. + * @throws DBusException If the objectpath is incorrectly formatted, + */ + public void addFallback(String objectprefix, DBusInterface object) throws DBusException + { + if (null == objectprefix || "".equals(objectprefix)) + throw new DBusException($_("Must Specify an Object Path")); + if (!objectprefix.matches(OBJECT_REGEX)||objectprefix.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectprefix); + ExportedObject eo = new ExportedObject(object, weakreferences); + fallbackcontainer.add(objectprefix, eo); + } + /** + * Remove a fallback + * @param objectprefix The prefix to remove the fallback for. + */ + public void removeFallback(String objectprefix) + { + fallbackcontainer.remove(objectprefix); + } + /** + * Stop Exporting an object + * @param objectpath The objectpath to stop exporting. + */ + public void unExportObject(String objectpath) + { + synchronized (exportedObjects) { + exportedObjects.remove(objectpath); + objectTree.remove(objectpath); + } + } + /** + * Return a reference to a remote object. + * This method will resolve the well known name (if given) to a unique bus name when you call it. + * This means that if a well known name is released by one process and acquired by another calls to + * objects gained from this method will continue to operate on the original process. + * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @param objectpath The path on which the process is exporting the object.$ + * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures + * as the interface the remote object is exporting. + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. + */ + /** + * Send a signal. + * @param signal The signal to send. + */ + public void sendSignal(DBusSignal signal) + { + queueOutgoing(signal); + } + void queueOutgoing(Message m) + { + synchronized (outgoing) { + if (null == outgoing) return; + outgoing.add(m); + if (Debug.debug) Debug.print(Debug.DEBUG, "Notifying outgoing thread"); + outgoing.notifyAll(); + } + } + /** + * Remove a Signal Handler. + * Stops listening for this signal. + * @param type The signal to watch for. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + public void removeSigHandler(Class type, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + removeSigHandler(new DBusMatchRule(type), handler); + } + /** + * Remove a Signal Handler. + * Stops listening for this signal. + * @param type The signal to watch for. + * @param object The object emitting the signal. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + public void removeSigHandler(Class type, DBusInterface object, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + String objectpath = importedObjects.get(object).objectpath; + if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + removeSigHandler(new DBusMatchRule(type, null, objectpath), handler); + } + + protected abstract void removeSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException; + /** + * Add a Signal Handler. + * Adds a signal handler to call when a signal is received which matches the specified type and name. + * @param type The signal to watch for. + * @param handler The handler to call when a signal is received. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + @SuppressWarnings("unchecked") + public void addSigHandler(Class type, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + addSigHandler(new DBusMatchRule(type), (DBusSigHandler) handler); + } + /** + * Add a Signal Handler. + * Adds a signal handler to call when a signal is received which matches the specified type, name and object. + * @param type The signal to watch for. + * @param object The object from which the signal will be emitted + * @param handler The handler to call when a signal is received. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + @SuppressWarnings("unchecked") + public void addSigHandler(Class type, DBusInterface object, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + String objectpath = importedObjects.get(object).objectpath; + if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + addSigHandler(new DBusMatchRule(type, null, objectpath), (DBusSigHandler) handler); + } + + protected abstract void addSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException; + + protected void addSigHandlerWithoutMatch(Class signal, DBusSigHandler handler) throws DBusException + { + DBusMatchRule rule = new DBusMatchRule(signal); + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null == v) { + v = new Vector>(); + v.add(handler); + handledSignals.put(key, v); + } else + v.add(handler); + } + } + + /** + * Disconnect from the Bus. + */ + public void disconnect() + { + connected = false; + if (Debug.debug) Debug.print(Debug.INFO, "Sending disconnected signal"); + try { + handleMessage(new org.freedesktop.DBus.Local.Disconnected("/")); + } catch (Exception ee) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, ee); + } + + if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting Abstract Connection"); + // run all pending tasks. + while (runnables.size() > 0) + synchronized (runnables) { + runnables.notifyAll(); + } + + // stop the main thread + _run = false; + + // unblock the sending thread. + synchronized (outgoing) { + outgoing.notifyAll(); + } + + // disconnect from the trasport layer + try { + if (null != transport) { + transport.disconnect(); + transport = null; + } + } catch (IOException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); + } + + // stop all the workers + synchronized(workers) { + for (_workerthread t: workers) + t.halt(); + } + + // make sure none are blocking on the runnables queue still + synchronized (runnables) { + runnables.notifyAll(); + } + } + + public void finalize() + { + disconnect(); + } + /** + * Return any DBus error which has been received. + * @return A DBusExecutionException, or null if no error is pending. + */ + public DBusExecutionException getError() + { + synchronized (pendingErrors) { + if (pendingErrors.size() == 0) return null; + else + return pendingErrors.removeFirst().getException(); + } + } + + /** + * Call a method asynchronously and set a callback. + * This handler will be called in a separate thread. + * @param object The remote object on which to call the method. + * @param m The name of the method on the interface to call. + * @param callback The callback handler. + * @param parameters The parameters to call the method with. + */ + @SuppressWarnings("unchecked") + public void callWithCallback(DBusInterface object, String m, CallbackHandler callback, Object... parameters) + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "callWithCallback("+object+","+m+", "+callback); + Class[] types = new Class[parameters.length]; + for (int i = 0; i < parameters.length; i++) + types[i] = parameters[i].getClass(); + RemoteObject ro = importedObjects.get(object); + + try { + Method me; + if (null == ro.iface) + me = object.getClass().getMethod(m, types); + else + me = ro.iface.getMethod(m, types); + RemoteInvocationHandler.executeRemoteMethod(ro, me, this, RemoteInvocationHandler.CALL_TYPE_CALLBACK, callback, parameters); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw DBEe; + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusExecutionException(e.getMessage()); + } + } + + /** + * Call a method asynchronously and get a handle with which to get the reply. + * @param object The remote object on which to call the method. + * @param m The name of the method on the interface to call. + * @param parameters The parameters to call the method with. + * @return A handle to the call. + */ + @SuppressWarnings("unchecked") + public DBusAsyncReply callMethodAsync(DBusInterface object, String m, Object... parameters) + { + Class[] types = new Class[parameters.length]; + for (int i = 0; i < parameters.length; i++) + types[i] = parameters[i].getClass(); + RemoteObject ro = importedObjects.get(object); + + try { + Method me; + if (null == ro.iface) + me = object.getClass().getMethod(m, types); + else + me = ro.iface.getMethod(m, types); + return (DBusAsyncReply) RemoteInvocationHandler.executeRemoteMethod(ro, me, this, RemoteInvocationHandler.CALL_TYPE_ASYNC, null, parameters); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw DBEe; + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusExecutionException(e.getMessage()); + } + } + + private void handleMessage(final MethodCall m) throws DBusException + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming method call: "+m); + + ExportedObject eo = null; + Method meth = null; + Object o = null; + + if (null == m.getInterface() || + m.getInterface().equals("org.freedesktop.DBus.Peer") || + m.getInterface().equals("org.freedesktop.DBus.Introspectable")) { + synchronized (exportedObjects) { + eo = exportedObjects.get(null); + } + if (null != eo && null == eo.object.get()) { + unExportObject(null); + eo = null; + } + if (null != eo) { + meth = eo.methods.get(new MethodTuple(m.getName(), m.getSig())); + } + if (null != meth) + o = new _globalhandler(m.getPath()); + else + eo = null; + } + if (null == o) { + // now check for specific exported functions + + synchronized (exportedObjects) { + eo = exportedObjects.get(m.getPath()); + } + if (null != eo && null == eo.object.get()) { + if (Debug.debug) Debug.print(Debug.INFO, "Unexporting "+m.getPath()+" implicitly"); + unExportObject(m.getPath()); + eo = null; + } + + if (null == eo) { + eo = fallbackcontainer.get(m.getPath()); + } + + if (null == eo) { + try { + queueOutgoing(new Error(m, new DBus.Error.UnknownObject(m.getPath()+ $_(" is not an object provided by this process.")))); + } catch (DBusException DBe) {} + return; + } + if (Debug.debug) { + Debug.print(Debug.VERBOSE, "Searching for method "+m.getName()+" with signature "+m.getSig()); + Debug.print(Debug.VERBOSE, "List of methods on "+eo+":"); + for (MethodTuple mt: eo.methods.keySet()) + Debug.print(Debug.VERBOSE, " "+mt+" => "+eo.methods.get(mt)); + } + meth = eo.methods.get(new MethodTuple(m.getName(), m.getSig())); + if (null == meth) { + try { + queueOutgoing(new Error(m, new DBus.Error.UnknownMethod(MessageFormat.format($_("The method `{0}.{1}' does not exist on this object."), new Object[] { m.getInterface(), m.getName() })))); + } catch (DBusException DBe) {} + return; + } + o = eo.object.get(); + } + + // now execute it + final Method me = meth; + final Object ob = o; + final boolean noreply = (1 == (m.getFlags() & Message.Flags.NO_REPLY_EXPECTED)); + final DBusCallInfo info = new DBusCallInfo(m); + final AbstractConnection conn = this; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for method "+meth); + addRunnable(new Runnable() + { + private boolean run = false; + public synchronized void run() + { + if (run) return; + run = true; + if (Debug.debug) Debug.print(Debug.DEBUG, "Running method "+me+" for remote call"); + try { + Type[] ts = me.getGenericParameterTypes(); + m.setArgs(Marshalling.deSerializeParameters(m.getParameters(), ts, conn)); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserialised "+Arrays.deepToString(m.getParameters())+" to types "+Arrays.deepToString(ts)); + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + try { + conn.queueOutgoing(new Error(m, new DBus.Error.UnknownMethod($_("Failure in de-serializing message: ")+e))); + } catch (DBusException DBe) {} + return; + } + + try { + synchronized (infomap) { + infomap.put(Thread.currentThread(), info); + } + Object result; + try { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Invoking Method: "+me+" on "+ob+" with parameters "+Arrays.deepToString(m.getParameters())); + result = me.invoke(ob, m.getParameters()); + } catch (InvocationTargetException ITe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, ITe.getCause()); + throw ITe.getCause(); + } + synchronized (infomap) { + infomap.remove(Thread.currentThread()); + } + if (!noreply) { + MethodReturn reply; + if (Void.TYPE.equals(me.getReturnType())) + reply = new MethodReturn(m, null); + else { + StringBuffer sb = new StringBuffer(); + for (String s: Marshalling.getDBusType(me.getGenericReturnType())) + sb.append(s); + Object[] nr = Marshalling.convertParameters(new Object[] { result }, new Type[] {me.getGenericReturnType()}, conn); + + reply = new MethodReturn(m, sb.toString(),nr); + } + conn.queueOutgoing(reply); + } + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + try { + conn.queueOutgoing(new Error(m, DBEe)); + } catch (DBusException DBe) {} + } catch (Throwable e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + try { + conn.queueOutgoing(new Error(m, new DBusExecutionException(MessageFormat.format($_("Error Executing Method {0}.{1}: {2}"), new Object[] { m.getInterface(), m.getName(), e.getMessage() })))); + } catch (DBusException DBe) {} + } + } + }); + } + @SuppressWarnings({"unchecked","deprecation"}) + private void handleMessage(final DBusSignal s) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming signal: "+s); + Vector> v = new Vector>(); + synchronized(handledSignals) { + Vector> t; + t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), null, null)); + if (null != t) v.addAll(t); + t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), s.getPath(), null)); + if (null != t) v.addAll(t); + t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), null, s.getSource())); + if (null != t) v.addAll(t); + t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), s.getPath(), s.getSource())); + if (null != t) v.addAll(t); + } + if (0 == v.size()) return; + final AbstractConnection conn = this; + for (final DBusSigHandler h: v) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for signal "+s+" with handler "+h); + addRunnable(new Runnable() { + private boolean run = false; + public synchronized void run() + { + if (run) return; + run = true; + try { + DBusSignal rs; + if (s instanceof DBusSignal.internalsig || s.getClass().equals(DBusSignal.class)) + rs = s.createReal(conn); + else + rs = s; + ((DBusSigHandler)h).handle(rs); + } catch (DBusException DBe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + try { + conn.queueOutgoing(new Error(s, new DBusExecutionException("Error handling signal "+s.getInterface()+"."+s.getName()+": "+DBe.getMessage()))); + } catch (DBusException DBe2) {} + } + } + }); + } + } + private void handleMessage(final Error err) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming error: "+err); + MethodCall m = null; + if (null == pendingCalls) return; + synchronized (pendingCalls) { + if (pendingCalls.contains(err.getReplySerial())) + m = pendingCalls.remove(err.getReplySerial()); + } + if (null != m) { + m.setReply(err); + CallbackHandler cbh = null; + DBusAsyncReply asr = null; + synchronized (pendingCallbacks) { + cbh = pendingCallbacks.remove(m); + if (Debug.debug) Debug.print(Debug.VERBOSE, cbh+" = pendingCallbacks.remove("+m+")"); + asr = pendingCallbackReplys.remove(m); + } + // queue callback for execution + if (null != cbh) { + final CallbackHandler fcbh = cbh; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Error Runnable with callback handler "+fcbh); + addRunnable(new Runnable() { + private boolean run = false; + public synchronized void run() + { + if (run) return; + run = true; + try { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Running Error Callback for "+err); + DBusCallInfo info = new DBusCallInfo(err); + synchronized (infomap) { + infomap.put(Thread.currentThread(), info); + } + + fcbh.handleError(err.getException()); + synchronized (infomap) { + infomap.remove(Thread.currentThread()); + } + + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + } + } + }); + } + + } + else + synchronized (pendingErrors) { + pendingErrors.addLast(err); } + } + @SuppressWarnings("unchecked") + private void handleMessage(final MethodReturn mr) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming method return: "+mr); + MethodCall m = null; + if (null == pendingCalls) return; + synchronized (pendingCalls) { + if (pendingCalls.contains(mr.getReplySerial())) + m = pendingCalls.remove(mr.getReplySerial()); + } + if (null != m) { + m.setReply(mr); + mr.setCall(m); + CallbackHandler cbh = null; + DBusAsyncReply asr = null; + synchronized (pendingCallbacks) { + cbh = pendingCallbacks.remove(m); + if (Debug.debug) Debug.print(Debug.VERBOSE, cbh+" = pendingCallbacks.remove("+m+")"); + asr = pendingCallbackReplys.remove(m); + } + // queue callback for execution + if (null != cbh) { + final CallbackHandler fcbh = cbh; + final DBusAsyncReply fasr = asr; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for method "+fasr.getMethod()+" with callback handler "+fcbh); + addRunnable(new Runnable() { + private boolean run = false; + public synchronized void run() + { + if (run) return; + run = true; + try { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Running Callback for "+mr); + DBusCallInfo info = new DBusCallInfo(mr); + synchronized (infomap) { + infomap.put(Thread.currentThread(), info); + } + + fcbh.handle(RemoteInvocationHandler.convertRV(mr.getSig(), mr.getParameters(), fasr.getMethod(), fasr.getConnection())); + synchronized (infomap) { + infomap.remove(Thread.currentThread()); + } + + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + } + } + }); + } + + } else + try { + queueOutgoing(new Error(mr, new DBusExecutionException($_("Spurious reply. No message with the given serial id was awaiting a reply.")))); + } catch (DBusException DBe) {} + } + protected void sendMessage(Message m) + { + try { + if (!connected) throw new NotConnected($_("Disconnected")); + if (m instanceof DBusSignal) + ((DBusSignal) m).appendbody(this); + + if (m instanceof MethodCall) { + if (0 == (m.getFlags() & Message.Flags.NO_REPLY_EXPECTED)) + if (null == pendingCalls) + ((MethodCall) m).setReply(new Error("org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.Disconnected", 0, "s", new Object[] { $_("Disconnected") })); + else synchronized (pendingCalls) { + pendingCalls.put(m.getSerial(),(MethodCall) m); + } + } + + transport.mout.writeMessage(m); + + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + if (m instanceof MethodCall && e instanceof NotConnected) + try { + ((MethodCall) m).setReply(new Error("org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.Disconnected", 0, "s", new Object[] { $_("Disconnected") })); + } catch (DBusException DBe) {} + if (m instanceof MethodCall && e instanceof DBusExecutionException) + try { + ((MethodCall)m).setReply(new Error(m, e)); + } catch (DBusException DBe) {} + else if (m instanceof MethodCall) + try { + if (Debug.debug) Debug.print(Debug.INFO, "Setting reply to "+m+" as an error"); + ((MethodCall)m).setReply(new Error(m, new DBusExecutionException($_("Message Failed to Send: ")+e.getMessage()))); + } catch (DBusException DBe) {} + else if (m instanceof MethodReturn) + try { + transport.mout.writeMessage(new Error(m, e)); + } catch(IOException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); + } catch(DBusException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + } + if (e instanceof IOException) disconnect(); + } + } + private Message readIncoming() throws DBusException + { + if (!connected) throw new NotConnected($_("No transport present")); + Message m = null; + try { + m = transport.min.readMessage(); + } catch (IOException IOe) { + throw new FatalDBusException(IOe.getMessage()); + } + return m; + } + /** + * Returns the address this connection is connected to. + */ + public BusAddress getAddress() throws ParseException { return new BusAddress(addr); } +} diff --git a/app/src/main/java/org/freedesktop/dbus/ArrayFrob.java b/app/src/main/java/org/freedesktop/dbus/ArrayFrob.java new file mode 100644 index 00000000..df4f46bf --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/ArrayFrob.java @@ -0,0 +1,173 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.lang.reflect.Array; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; + +import cx.ath.matthew.debug.Debug; + +class ArrayFrob +{ + static Hashtable, Class> primitiveToWrapper = new Hashtable, Class>(); + static Hashtable, Class> wrapperToPrimitive = new Hashtable, Class>(); + static { + primitiveToWrapper.put( Boolean.TYPE, Boolean.class ); + primitiveToWrapper.put( Byte.TYPE, Byte.class ); + primitiveToWrapper.put( Short.TYPE, Short.class ); + primitiveToWrapper.put( Character.TYPE, Character.class ); + primitiveToWrapper.put( Integer.TYPE, Integer.class ); + primitiveToWrapper.put( Long.TYPE, Long.class ); + primitiveToWrapper.put( Float.TYPE, Float.class ); + primitiveToWrapper.put( Double.TYPE, Double.class ); + wrapperToPrimitive.put( Boolean.class, Boolean.TYPE ); + wrapperToPrimitive.put( Byte.class, Byte.TYPE ); + wrapperToPrimitive.put( Short.class, Short.TYPE ); + wrapperToPrimitive.put( Character.class, Character.TYPE ); + wrapperToPrimitive.put( Integer.class, Integer.TYPE ); + wrapperToPrimitive.put( Long.class, Long.TYPE ); + wrapperToPrimitive.put( Float.class, Float.TYPE ); + wrapperToPrimitive.put( Double.class, Double.TYPE ); + + } + @SuppressWarnings("unchecked") + public static T[] wrap(Object o) throws IllegalArgumentException + { + Class ac = o.getClass(); + if (!ac.isArray()) throw new IllegalArgumentException($_("Not an array")); + Class cc = ac.getComponentType(); + Class ncc = primitiveToWrapper.get(cc); + if (null == ncc) throw new IllegalArgumentException($_("Not a primitive type")); + T[] ns = (T[]) Array.newInstance(ncc, Array.getLength(o)); + for (int i = 0; i < ns.length; i++) + ns[i] = (T) Array.get(o, i); + return ns; + } + @SuppressWarnings("unchecked") + public static Object unwrap(T[] ns) throws IllegalArgumentException + { + Class ac = (Class) ns.getClass(); + Class cc = (Class) ac.getComponentType(); + Class ncc = wrapperToPrimitive.get(cc); + if (null == ncc) throw new IllegalArgumentException($_("Not a wrapper type")); + Object o = Array.newInstance(ncc, ns.length); + for (int i = 0; i < ns.length; i++) + Array.set(o, i, ns[i]); + return o; + } + public static List listify(T[] ns) throws IllegalArgumentException + { + return Arrays.asList(ns); + } + @SuppressWarnings("unchecked") + public static List listify(Object o) throws IllegalArgumentException + { + if (o instanceof Object[]) return listify((T[]) o); + if (!o.getClass().isArray()) throw new IllegalArgumentException($_("Not an array")); + List l = new ArrayList(Array.getLength(o)); + for (int i = 0; i < Array.getLength(o); i++) + l.add((T)Array.get(o, i)); + return l; + } + @SuppressWarnings("unchecked") + public static T[] delist(List l, Class c) throws IllegalArgumentException + { + return l.toArray((T[]) Array.newInstance(c, 0)); + } + public static Object delistprimitive(List l, Class c) throws IllegalArgumentException + { + Object o = Array.newInstance(c, l.size()); + for (int i = 0; i < l.size(); i++) + Array.set(o, i, l.get(i)); + return o; + } + @SuppressWarnings("unchecked") + public static Object convert(Object o, Class c) throws IllegalArgumentException + { + /* Possible Conversions: + * + ** List -> List + ** List -> int[] + ** List -> Integer[] + ** int[] -> int[] + ** int[] -> List + ** int[] -> Integer[] + ** Integer[] -> Integer[] + ** Integer[] -> int[] + ** Integer[] -> List + */ + try { + // List -> List + if (List.class.equals(c) + && o instanceof List) + return o; + + // int[] -> List + // Integer[] -> List + if (List.class.equals(c) + && o.getClass().isArray()) + return listify(o); + + // int[] -> int[] + // Integer[] -> Integer[] + if (o.getClass().isArray() + && c.isArray() + && o.getClass().getComponentType().equals(c.getComponentType())) + return o; + + // int[] -> Integer[] + if (o.getClass().isArray() + && c.isArray() + && o.getClass().getComponentType().isPrimitive()) + return wrap(o); + + // Integer[] -> int[] + if (o.getClass().isArray() + && c.isArray() + && c.getComponentType().isPrimitive()) + return unwrap((Object[]) o); + + // List -> int[] + if (o instanceof List + && c.isArray() + && c.getComponentType().isPrimitive()) + return delistprimitive((List) o, (Class) c.getComponentType()); + + // List -> Integer[] + if (o instanceof List + && c.isArray()) + return delist((List) o, (Class) c.getComponentType()); + + if (o.getClass().isArray() + && c.isArray()) + return type((Object[]) o, (Class) c.getComponentType()); + + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new IllegalArgumentException(e); + } + + throw new IllegalArgumentException(MessageFormat.format($_("Not An Expected Convertion type from {0} to {1}"), new Object[] { o.getClass(), c})); + } + public static Object[] type(Object[] old, Class c) + { + Object[] ns = (Object[]) Array.newInstance(c, old.length); + for (int i = 0; i < ns.length; i++) + ns[i] = old[i]; + return ns; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/BusAddress.java b/app/src/main/java/org/freedesktop/dbus/BusAddress.java new file mode 100644 index 00000000..79d29865 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/BusAddress.java @@ -0,0 +1,41 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; +import static org.freedesktop.dbus.Gettext.$_; +import java.text.ParseException; +import java.util.Map; +import java.util.HashMap; +import cx.ath.matthew.debug.Debug; + +public class BusAddress +{ + private String type; + private Map parameters; + public BusAddress(String address) throws ParseException + { + if (null == address || "".equals(address)) throw new ParseException($_("Bus address is blank"), 0); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Parsing bus address: "+address); + String[] ss = address.split(":", 2); + if (ss.length < 2) throw new ParseException($_("Bus address is invalid: ")+address, 0); + type = ss[0]; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Transport type: "+type); + String[] ps = ss[1].split(","); + parameters = new HashMap(); + for (String p: ps) { + String[] kv = p.split("=", 2); + parameters.put(kv[0], kv[1]); + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "Transport options: "+parameters); + } + public String getType() { return type; } + public String getParameter(String key) { return parameters.get(key); } + public String toString() { return type+": "+parameters; } +} diff --git a/app/src/main/java/org/freedesktop/dbus/CallbackHandler.java b/app/src/main/java/org/freedesktop/dbus/CallbackHandler.java new file mode 100644 index 00000000..b05b5001 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/CallbackHandler.java @@ -0,0 +1,22 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +/** + * Interface for callbacks in async mode + */ +public interface CallbackHandler +{ + public void handle(ReturnType r); + public void handleError(DBusExecutionException e); +} diff --git a/app/src/main/java/org/freedesktop/dbus/Container.java b/app/src/main/java/org/freedesktop/dbus/Container.java new file mode 100644 index 00000000..d2efb677 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Container.java @@ -0,0 +1,88 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.lang.reflect.Field; +import java.lang.reflect.Type; + +/** + * This class is the super class of both Structs and Tuples + * and holds common methods. + */ +abstract class Container +{ + private static Map typecache = new HashMap(); + static void putTypeCache(Type k, Type[] v) + { + typecache.put(k, v); + } + static Type[] getTypeCache(Type k) + { + return typecache.get(k); + } + private Object[] parameters = null; + public Container() {} + private void setup() + { + Field[] fs = getClass().getDeclaredFields(); + Object[] args = new Object[fs.length]; + + int diff = 0; + for (Field f : fs) { + Position p = f.getAnnotation(Position.class); + if (null == p) { + diff++; + continue; + } + try { + args[p.value()] = f.get(this); + } catch (IllegalAccessException IAe) {} + } + + this.parameters = new Object[args.length - diff]; + System.arraycopy(args, 0, parameters, 0, parameters.length); + } + /** + * Returns the struct contents in order. + * @throws DBusException If there is a problem doing this. + */ + public final Object[] getParameters() + { + if (null != parameters) return parameters; + setup(); + return parameters; + } + /** Returns this struct as a string. */ + public final String toString() + { + String s = getClass().getName()+"<"; + if (null == parameters) + setup(); + if (0 == parameters.length) + return s+">"; + for (Object o: parameters) + s += o+", "; + return s.replaceAll(", $", ">"); + } + public final boolean equals(Object other) + { + if (other instanceof Container) { + Container that = (Container) other; + if (this.getClass().equals(that.getClass())) + return Arrays.equals(this.getParameters(), that.getParameters()); + else return false; + } + else return false; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java b/app/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java new file mode 100644 index 00000000..c3843812 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java @@ -0,0 +1,111 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import org.freedesktop.DBus.Error.NoReply; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +import cx.ath.matthew.debug.Debug; + +/** + * A handle to an asynchronous method call. + */ +public class DBusAsyncReply +{ + /** + * Check if any of a set of asynchronous calls have had a reply. + * @param replies A Collection of handles to replies to check. + * @return A Collection only containing those calls which have had replies. + */ + public static Collection> hasReply(Collection> replies) + { + Collection> c = new ArrayList>(replies); + Iterator> i = c.iterator(); + while (i.hasNext()) + if (!i.next().hasReply()) i.remove(); + return c; + } + + private ReturnType rval = null; + private DBusExecutionException error = null; + private MethodCall mc; + private Method me; + private AbstractConnection conn; + DBusAsyncReply(MethodCall mc, Method me, AbstractConnection conn) + { + this.mc = mc; + this.me = me; + this.conn = conn; + } + @SuppressWarnings("unchecked") + private synchronized void checkReply() + { + if (mc.hasReply()) { + Message m = mc.getReply(); + if (m instanceof Error) + error = ((Error) m).getException(); + else if (m instanceof MethodReturn) { + try { + rval = (ReturnType) RemoteInvocationHandler.convertRV(m.getSig(), m.getParameters(), me, conn); + } catch (DBusExecutionException DBEe) { + error = DBEe; + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + error = new DBusExecutionException(DBe.getMessage()); + } + } + } + } + + /** + * Check if we've had a reply. + * @return True if we have a reply + */ + public boolean hasReply() + { + if (null != rval || null != error) return true; + checkReply(); + return null != rval || null != error; + } + + /** + * Get the reply. + * @return The return value from the method. + * @throws DBusExecutionException if the reply to the method was an error. + * @throws NoReply if the method hasn't had a reply yet + */ + public ReturnType getReply() throws DBusExecutionException + { + if (null != rval) return rval; + else if (null != error) throw error; + checkReply(); + if (null != rval) return rval; + else if (null != error) throw error; + else throw new NoReply($_("Async call has not had a reply")); + } + + public String toString() + { + return $_("Waiting for: ")+mc; + } + Method getMethod() { return me; } + AbstractConnection getConnection() { return conn; } + MethodCall getCall() { return mc; } +} + diff --git a/app/src/main/java/org/freedesktop/dbus/DBusCallInfo.java b/app/src/main/java/org/freedesktop/dbus/DBusCallInfo.java new file mode 100644 index 00000000..d34ef4ef --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusCallInfo.java @@ -0,0 +1,51 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +/** + * Holds information on a method call + */ +public class DBusCallInfo +{ + /** + * Indicates the caller won't wait for a reply (and we won't send one). + */ + public static final int NO_REPLY = Message.Flags.NO_REPLY_EXPECTED; + public static final int ASYNC = 0x100; + private String source; + private String destination; + private String objectpath; + private String iface; + private String method; + private int flags; + DBusCallInfo(Message m) + { + this.source = m.getSource(); + this.destination = m.getDestination(); + this.objectpath = m.getPath(); + this.iface = m.getInterface(); + this.method = m.getName(); + this.flags = m.getFlags(); + } + + /** Returns the BusID which called the method */ + public String getSource() { return source; } + /** Returns the name with which we were addressed on the Bus */ + public String getDestination() { return destination; } + /** Returns the object path used to call this method */ + public String getObjectPath() { return objectpath; } + /** Returns the interface this method was called with */ + public String getInterface() { return iface; } + /** Returns the method name used to call this method */ + public String getMethod() { return method; } + /** Returns any flags set on this method call */ + public int getFlags() { return flags; } +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusConnection.java b/app/src/main/java/org/freedesktop/dbus/DBusConnection.java new file mode 100644 index 00000000..b07a764b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusConnection.java @@ -0,0 +1,780 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.lang.reflect.Proxy; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +import java.text.MessageFormat; +import java.text.ParseException; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import cx.ath.matthew.debug.Debug; + +/** Handles a connection to DBus. + *

+ * This is a Singleton class, only 1 connection to the SYSTEM or SESSION busses can be made. + * Repeated calls to getConnection will return the same reference. + *

+ *

+ * Signal Handlers and method calls from remote objects are run in their own threads, you MUST handle the concurrency issues. + *

+ */ +public class DBusConnection extends AbstractConnection +{ + /** + * Add addresses of peers to a set which will watch for them to + * disappear and automatically remove them from the set. + */ + public class PeerSet implements Set, DBusSigHandler + { + private Set addresses; + public PeerSet() + { + addresses = new TreeSet(); + try { + addSigHandler(new DBusMatchRule(DBus.NameOwnerChanged.class, null, null), this); + } catch (DBusException DBe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + } + } + public void handle(DBus.NameOwnerChanged noc) + { + if (Debug.debug) + Debug.print(Debug.DEBUG, "Received NameOwnerChanged("+noc.name+","+noc.old_owner+","+noc.new_owner+")"); + if ("".equals(noc.new_owner) && addresses.contains(noc.name)) + remove(noc.name); + } + public boolean add(String address) + { + if (Debug.debug) + Debug.print(Debug.DEBUG, "Adding "+address); + synchronized (addresses) { + return addresses.add(address); + } + } + public boolean addAll(Collection addresses) + { + synchronized (this.addresses) { + return this.addresses.addAll(addresses); + } + } + public void clear() + { + synchronized (addresses) { + addresses.clear(); + } + } + public boolean contains(Object o) + { + return addresses.contains(o); + } + public boolean containsAll(Collection os) + { + return addresses.containsAll(os); + } + public boolean equals(Object o) + { + if (o instanceof PeerSet) + return ((PeerSet) o).addresses.equals(addresses); + else return false; + } + public int hashCode() + { + return addresses.hashCode(); + } + public boolean isEmpty() + { + return addresses.isEmpty(); + } + public Iterator iterator() + { + return addresses.iterator(); + } + public boolean remove(Object o) + { + if (Debug.debug) + Debug.print(Debug.DEBUG, "Removing "+o); + synchronized(addresses) { + return addresses.remove(o); + } + } + public boolean removeAll(Collection os) + { + synchronized(addresses) { + return addresses.removeAll(os); + } + } + public boolean retainAll(Collection os) + { + synchronized(addresses) { + return addresses.retainAll(os); + } + } + public int size() + { + return addresses.size(); + } + public Object[] toArray() + { + synchronized(addresses) { + return addresses.toArray(); + } + } + public T[] toArray(T[] a) + { + synchronized(addresses) { + return addresses.toArray(a); + } + } + } + private class _sighandler implements DBusSigHandler + { + public void handle(DBusSignal s) + { + if (s instanceof org.freedesktop.DBus.Local.Disconnected) { + if (Debug.debug) Debug.print(Debug.WARN, "Handling Disconnected signal from bus"); + try { + Error err = new Error( + "org.freedesktop.DBus.Local" , "org.freedesktop.DBus.Local.Disconnected", 0, "s", new Object[] { $_("Disconnected") }); + if (null != pendingCalls) synchronized (pendingCalls) { + long[] set = pendingCalls.getKeys(); + for (long l: set) if (-1 != l) { + MethodCall m = pendingCalls.remove(l); + if (null != m) + m.setReply(err); + } + } + synchronized (pendingErrors) { + pendingErrors.add(err); + } + } catch (DBusException DBe) {} + } else if (s instanceof org.freedesktop.DBus.NameAcquired) { + busnames.add(((org.freedesktop.DBus.NameAcquired) s).name); + } + } + } + + /** + * System Bus + */ + public static final int SYSTEM = 0; + /** + * Session Bus + */ + public static final int SESSION = 1; + + public static final String DEFAULT_SYSTEM_BUS_ADDRESS = "unix:path=/var/run/dbus/system_bus_socket"; + + private List busnames; + + private static final Map conn = new HashMap(); + private int _refcount = 0; + private Object _reflock = new Object(); + private DBus _dbus; + + /** + * Connect to the BUS. If a connection already exists to the specified Bus, a reference to it is returned. + * @param address The address of the bus to connect to + * @throws DBusException If there is a problem connecting to the Bus. + */ + public static DBusConnection getConnection(String address) throws DBusException + { + synchronized (conn) { + DBusConnection c = conn.get(address); + if (null != c) { + synchronized (c._reflock) { c._refcount++; } + return c; + } + else { + c = new DBusConnection(address); + conn.put(address, c); + return c; + } + } + } + /** + * Connect to the BUS. If a connection already exists to the specified Bus, a reference to it is returned. + * @param bustype The Bus to connect to. + * @see #SYSTEM + * @see #SESSION + * @throws DBusException If there is a problem connecting to the Bus. + */ + public static DBusConnection getConnection(int bustype) throws DBusException + { + synchronized (conn) { + String s = null; + switch (bustype) { + case SYSTEM: + s = System.getenv("DBUS_SYSTEM_BUS_ADDRESS"); + if (null == s) s = DEFAULT_SYSTEM_BUS_ADDRESS; + break; + case SESSION: + s = System.getenv("DBUS_SESSION_BUS_ADDRESS"); + if (null == s) { + // address gets stashed in $HOME/.dbus/session-bus/`dbus-uuidgen --get`-`sed 's/:\(.\)\..*/\1/' <<< $DISPLAY` + String display = System.getenv("DISPLAY"); + if (null == display) throw new DBusException($_("Cannot Resolve Session Bus Address")); + File uuidfile = new File("/var/lib/dbus/machine-id"); + if (!uuidfile.exists()) throw new DBusException($_("Cannot Resolve Session Bus Address")); + try { + BufferedReader r = new BufferedReader(new FileReader(uuidfile)); + String uuid = r.readLine(); + String homedir = System.getProperty("user.home"); + File addressfile = new File(homedir + "/.dbus/session-bus", + uuid + "-" + display.replaceAll(":([0-9]*)\\..*", "$1")); + if (!addressfile.exists()) throw new DBusException($_("Cannot Resolve Session Bus Address")); + r = new BufferedReader(new FileReader(addressfile)); + String l; + while (null != (l = r.readLine())) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Reading D-Bus session data: "+l); + if (l.matches("DBUS_SESSION_BUS_ADDRESS.*")) { + s = l.replaceAll("^[^=]*=", ""); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Parsing "+l+" to "+s); + } + } + if (null == s || "".equals(s)) throw new DBusException($_("Cannot Resolve Session Bus Address")); + if (Debug.debug) Debug.print(Debug.INFO, "Read bus address "+s+" from file "+addressfile.toString()); + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException($_("Cannot Resolve Session Bus Address")); + } + } + break; + default: + throw new DBusException($_("Invalid Bus Type: ")+bustype); + } + DBusConnection c = conn.get(s); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Getting bus connection for "+s+": "+c); + if (null != c) { + synchronized (c._reflock) { c._refcount++; } + return c; + } + else { + if (Debug.debug) Debug.print(Debug.DEBUG, "Creating new bus connection to: "+s); + c = new DBusConnection(s); + conn.put(s, c); + return c; + } + } + } + @SuppressWarnings("unchecked") + private DBusConnection(String address) throws DBusException + { + super(address); + busnames = new Vector(); + + synchronized (_reflock) { + _refcount = 1; + } + + try { + transport = new Transport(addr, AbstractConnection.TIMEOUT); + connected = true; + } catch (IOException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); + disconnect(); + throw new DBusException($_("Failed to connect to bus ")+IOe.getMessage()); + } catch (ParseException Pe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Pe); + disconnect(); + throw new DBusException($_("Failed to connect to bus ")+Pe.getMessage()); + } + + // start listening for calls + listen(); + + // register disconnect handlers + DBusSigHandler h = new _sighandler(); + addSigHandlerWithoutMatch(org.freedesktop.DBus.Local.Disconnected.class, h); + addSigHandlerWithoutMatch(org.freedesktop.DBus.NameAcquired.class, h); + + // register ourselves + _dbus = getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); + try { + busnames.add(_dbus.Hello()); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + } + + @SuppressWarnings("unchecked") + DBusInterface dynamicProxy(String source, String path) throws DBusException + { + if (Debug.debug) Debug.print(Debug.INFO, "Introspecting "+path+" on "+source+" for dynamic proxy creation"); + try { + DBus.Introspectable intro = getRemoteObject(source, path, DBus.Introspectable.class); + String data = intro.Introspect(); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Got introspection data: "+data); + String[] tags = data.split("[<>]"); + Vector ifaces = new Vector(); + for (String tag: tags) { + if (tag.startsWith("interface")) { + ifaces.add(tag.replaceAll("^interface *name *= *['\"]([^'\"]*)['\"].*$", "$1")); + } + } + Vector> ifcs = new Vector>(); + for(String iface: ifaces) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Trying interface "+iface); + int j = 0; + while (j >= 0) { + try { + Class ifclass = Class.forName(iface); + if (!ifcs.contains(ifclass)) + ifcs.add(ifclass); + break; + } catch (Exception e) {} + j = iface.lastIndexOf("."); + char[] cs = iface.toCharArray(); + if (j >= 0) { + cs[j] = '$'; + iface = String.valueOf(cs); + } + } + } + + if (ifcs.size() == 0) throw new DBusException($_("Could not find an interface to cast to")); + + RemoteObject ro = new RemoteObject(source, path, null, false); + DBusInterface newi = (DBusInterface) + Proxy.newProxyInstance(ifcs.get(0).getClassLoader(), + ifcs.toArray(new Class[0]), + new RemoteInvocationHandler(this, ro)); + importedObjects.put(newi, ro); + return newi; + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException(MessageFormat.format($_("Failed to create proxy object for {0} exported by {1}. Reason: {2}"), new Object[] { path, source, e.getMessage() })); + } + } + + DBusInterface getExportedObject(String source, String path) throws DBusException + { + ExportedObject o = null; + synchronized (exportedObjects) { + o = exportedObjects.get(path); + } + if (null != o && null == o.object.get()) { + unExportObject(path); + o = null; + } + if (null != o) return o.object.get(); + if (null == source) throw new DBusException($_("Not an object exported by this connection and no remote specified")); + return dynamicProxy(source, path); + } + + /** + * Release a bus name. + * Releases the name so that other people can use it + * @param busname The name to release. MUST be in dot-notation like "org.freedesktop.local" + * @throws DBusException If the busname is incorrectly formatted. + */ + public void releaseBusName(String busname) throws DBusException + { + if (!busname.matches(BUSNAME_REGEX)||busname.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name")); + synchronized (this.busnames) { + UInt32 rv; + try { + rv = _dbus.ReleaseName(busname); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + this.busnames.remove(busname); + } + } + /** + * Request a bus name. + * Request the well known name that this should respond to on the Bus. + * @param busname The name to respond to. MUST be in dot-notation like "org.freedesktop.local" + * @throws DBusException If the register name failed, or our name already exists on the bus. + * or if busname is incorrectly formatted. + */ + public void requestBusName(String busname) throws DBusException + { + if (!busname.matches(BUSNAME_REGEX)||busname.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name")); + synchronized (this.busnames) { + UInt32 rv; + try { + rv = _dbus.RequestName(busname, + new UInt32(DBus.DBUS_NAME_FLAG_REPLACE_EXISTING | + DBus.DBUS_NAME_FLAG_DO_NOT_QUEUE)); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + switch (rv.intValue()) { + case DBus.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: break; + case DBus.DBUS_REQUEST_NAME_REPLY_IN_QUEUE: throw new DBusException($_("Failed to register bus name")); + case DBus.DBUS_REQUEST_NAME_REPLY_EXISTS: throw new DBusException($_("Failed to register bus name")); + case DBus.DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: break; + default: break; + } + this.busnames.add(busname); + } + } + + /** + * Returns the unique name of this connection. + */ + public String getUniqueName() + { + return busnames.get(0); + } + /** + * Returns all the names owned by this connection. + */ + public String[] getNames() + { + Set names = new TreeSet(); + names.addAll(busnames); + return names.toArray(new String[0]); + } + public I getPeerRemoteObject(String busname, String objectpath, Class type) throws DBusException + { + return getPeerRemoteObject(busname, objectpath, type, true); + } + /** + * Return a reference to a remote object. + * This method will resolve the well known name (if given) to a unique bus name when you call it. + * This means that if a well known name is released by one process and acquired by another calls to + * objects gained from this method will continue to operate on the original process. + * + * This method will use bus introspection to determine the interfaces on a remote object and so + * may block and may fail. The resulting proxy object will, however, be castable + * to any interface it implements. It will also autostart the process if applicable. Also note + * that the resulting proxy may fail to execute the correct method with overloaded methods + * and that complex types may fail in interesting ways. Basically, if something odd happens, + * try specifying the interface explicitly. + * + * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @param objectpath The path on which the process is exporting the object.$ + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted. + */ + public DBusInterface getPeerRemoteObject(String busname, String objectpath) throws DBusException + { + if (null == busname) throw new DBusException($_("Invalid bus name: null")); + + if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) + || busname.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+busname); + + String unique = _dbus.GetNameOwner(busname); + + return dynamicProxy(unique, objectpath); + } + + /** + * Return a reference to a remote object. + * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. + * In particular this means that if a process providing the well known name disappears and is taken over by another process + * proxy objects gained by this method will make calls on the new proccess. + * + * This method will use bus introspection to determine the interfaces on a remote object and so + * may block and may fail. The resulting proxy object will, however, be castable + * to any interface it implements. It will also autostart the process if applicable. Also note + * that the resulting proxy may fail to execute the correct method with overloaded methods + * and that complex types may fail in interesting ways. Basically, if something odd happens, + * try specifying the interface explicitly. + * + * @param busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @param objectpath The path on which the process is exporting the object. + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted. + */ + public DBusInterface getRemoteObject(String busname, String objectpath) throws DBusException + { + if (null == busname) throw new DBusException($_("Invalid bus name: null")); + if (null == objectpath) throw new DBusException($_("Invalid object path: null")); + + if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) + || busname.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+busname); + + if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + + return dynamicProxy(busname, objectpath); + } + + /** + * Return a reference to a remote object. + * This method will resolve the well known name (if given) to a unique bus name when you call it. + * This means that if a well known name is released by one process and acquired by another calls to + * objects gained from this method will continue to operate on the original process. + * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @param objectpath The path on which the process is exporting the object.$ + * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures + * as the interface the remote object is exporting. + * @param autostart Disable/Enable auto-starting of services in response to calls on this object. + * Default is enabled; when calling a method with auto-start enabled, if the destination is a well-known name + * and is not owned the bus will attempt to start a process to take the name. When disabled an error is + * returned immediately. + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. + */ + public I getPeerRemoteObject(String busname, String objectpath, Class type, boolean autostart) throws DBusException + { + if (null == busname) throw new DBusException($_("Invalid bus name: null")); + + if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) + || busname.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+busname); + + String unique = _dbus.GetNameOwner(busname); + + return getRemoteObject(unique, objectpath, type, autostart); + } + /** + * Return a reference to a remote object. + * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. + * In particular this means that if a process providing the well known name disappears and is taken over by another process + * proxy objects gained by this method will make calls on the new proccess. + * @param busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @param objectpath The path on which the process is exporting the object. + * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures + * as the interface the remote object is exporting. + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. + */ + public I getRemoteObject(String busname, String objectpath, Class type) throws DBusException + { + return getRemoteObject(busname, objectpath, type, true); + } + /** + * Return a reference to a remote object. + * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. + * In particular this means that if a process providing the well known name disappears and is taken over by another process + * proxy objects gained by this method will make calls on the new proccess. + * @param busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @param objectpath The path on which the process is exporting the object. + * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures + * as the interface the remote object is exporting. + * @param autostart Disable/Enable auto-starting of services in response to calls on this object. + * Default is enabled; when calling a method with auto-start enabled, if the destination is a well-known name + * and is not owned the bus will attempt to start a process to take the name. When disabled an error is + * returned immediately. + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. + */ + @SuppressWarnings("unchecked") + public I getRemoteObject(String busname, String objectpath, Class type, boolean autostart) throws DBusException + { + if (null == busname) throw new DBusException($_("Invalid bus name: null")); + if (null == objectpath) throw new DBusException($_("Invalid object path: null")); + if (null == type) throw new ClassCastException($_("Not A DBus Interface")); + + if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) + || busname.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+busname); + + if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + + if (!DBusInterface.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Interface")); + + // don't let people import things which don't have a + // valid D-Bus interface name + if (type.getName().equals(type.getSimpleName())) + throw new DBusException($_("DBusInterfaces cannot be declared outside a package")); + + RemoteObject ro = new RemoteObject(busname, objectpath, type, autostart); + I i = (I) Proxy.newProxyInstance(type.getClassLoader(), + new Class[] { type }, new RemoteInvocationHandler(this, ro)); + importedObjects.put(i, ro); + return i; + } + /** + * Remove a Signal Handler. + * Stops listening for this signal. + * @param type The signal to watch for. + * @param source The source of the signal. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + public void removeSigHandler(Class type, String source, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + if (source.matches(BUSNAME_REGEX)) throw new DBusException($_("Cannot watch for signals based on well known bus name as source, only unique names.")); + if (!source.matches(CONNID_REGEX)||source.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+source); + removeSigHandler(new DBusMatchRule(type, source, null), handler); + } + /** + * Remove a Signal Handler. + * Stops listening for this signal. + * @param type The signal to watch for. + * @param source The source of the signal. + * @param object The object emitting the signal. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + public void removeSigHandler(Class type, String source, DBusInterface object, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + if (source.matches(BUSNAME_REGEX)) throw new DBusException($_("Cannot watch for signals based on well known bus name as source, only unique names.")); + if (!source.matches(CONNID_REGEX)||source.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+source); + String objectpath = importedObjects.get(object).objectpath; + if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + removeSigHandler(new DBusMatchRule(type, source, objectpath), handler); + } + protected void removeSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException + { + + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null != v) { + v.remove(handler); + if (0 == v.size()) { + handledSignals.remove(key); + try { + _dbus.RemoveMatch(rule.toString()); + } catch (NotConnected NC) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, NC); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + } + } + } + } + /** + * Add a Signal Handler. + * Adds a signal handler to call when a signal is received which matches the specified type, name and source. + * @param type The signal to watch for. + * @param source The process which will send the signal. This MUST be a unique bus name and not a well known name. + * @param handler The handler to call when a signal is received. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + @SuppressWarnings("unchecked") + public void addSigHandler(Class type, String source, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + if (source.matches(BUSNAME_REGEX)) throw new DBusException($_("Cannot watch for signals based on well known bus name as source, only unique names.")); + if (!source.matches(CONNID_REGEX)||source.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+source); + addSigHandler(new DBusMatchRule(type, source, null), (DBusSigHandler) handler); + } + /** + * Add a Signal Handler. + * Adds a signal handler to call when a signal is received which matches the specified type, name, source and object. + * @param type The signal to watch for. + * @param source The process which will send the signal. This MUST be a unique bus name and not a well known name. + * @param object The object from which the signal will be emitted + * @param handler The handler to call when a signal is received. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + @SuppressWarnings("unchecked") + public void addSigHandler(Class type, String source, DBusInterface object, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + if (source.matches(BUSNAME_REGEX)) throw new DBusException($_("Cannot watch for signals based on well known bus name as source, only unique names.")); + if (!source.matches(CONNID_REGEX)||source.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+source); + String objectpath = importedObjects.get(object).objectpath; + if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + addSigHandler(new DBusMatchRule(type, source, objectpath), (DBusSigHandler) handler); + } + protected void addSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException + { + try { + _dbus.AddMatch(rule.toString()); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null == v) { + v = new Vector>(); + v.add(handler); + handledSignals.put(key, v); + } else + v.add(handler); + } + } + /** + * Disconnect from the Bus. + * This only disconnects when the last reference to the bus has disconnect called on it + * or has been destroyed. + */ + public void disconnect() + { + synchronized (conn) { + synchronized (_reflock) { + if (0 == --_refcount) { + if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting DBusConnection"); + // Set all pending messages to have an error. + try { + Error err = new Error( + "org.freedesktop.DBus.Local" , "org.freedesktop.DBus.Local.Disconnected", 0, "s", new Object[] { $_("Disconnected") }); + synchronized (pendingCalls) { + long[] set = pendingCalls.getKeys(); + for (long l: set) if (-1 != l) { + MethodCall m = pendingCalls.remove(l); + if (null != m) + m.setReply(err); + } + pendingCalls = null; + } + synchronized (pendingErrors) { + pendingErrors.add(err); + } + } catch (DBusException DBe) {} + + conn.remove(addr); + super.disconnect(); + } + } + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusInterface.java b/app/src/main/java/org/freedesktop/dbus/DBusInterface.java new file mode 100644 index 00000000..7b0d30af --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusInterface.java @@ -0,0 +1,31 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; +/** + * Denotes a class as exportable or a remote interface which can be called. + *

+ * Any interface which should be exported or imported should extend this + * interface. All public methods from that interface are exported/imported + * with the given method signatures. + *

+ *

+ * All method calls on exported objects are run in their own threads. + * Application writers are responsible for any concurrency issues. + *

+ */ +public interface DBusInterface +{ + /** + * Returns true on remote objects. + * Local objects implementing this interface MUST return false. + */ + public boolean isRemote(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java b/app/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java new file mode 100644 index 00000000..5400a929 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java @@ -0,0 +1,27 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Force the interface name to be different to the Java class name. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface DBusInterfaceName +{ + /** The replacement interface name. */ + String value(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusMap.java b/app/src/main/java/org/freedesktop/dbus/DBusMap.java new file mode 100644 index 00000000..b9d06d72 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusMap.java @@ -0,0 +1,152 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + +class DBusMap implements Map +{ + Object[][] entries; + public DBusMap(Object[][] entries) + { + this.entries=entries; + } + class Entry implements Map.Entry, Comparable + { + private int entry; + public Entry(int i) + { + this.entry = i; + } + public boolean equals(Object o) + { + if (null == o) return false; + if (!(o instanceof DBusMap.Entry)) return false; + return this.entry == ((Entry) o).entry; + } + @SuppressWarnings("unchecked") + public K getKey() + { + return (K) entries[entry][0]; + } + @SuppressWarnings("unchecked") + public V getValue() + { + return (V) entries[entry][1]; + } + public int hashCode() + { + return entries[entry][0].hashCode(); + } + public V setValue(V value) + { + throw new UnsupportedOperationException(); + } + public int compareTo(Entry e) + { + return entry - e.entry; + } + } + + public void clear() + { + throw new UnsupportedOperationException(); + } + public boolean containsKey(Object key) + { + for (int i = 0; i < entries.length; i++) + if (key == entries[i][0] || (key != null && key.equals(entries[i][0]))) + return true; + return false; + } + public boolean containsValue(Object value) + { + for (int i = 0; i < entries.length; i++) + if (value == entries[i][1] || (value != null && value.equals(entries[i][1]))) + return true; + return false; + } + public Set> entrySet() + { + Set> s = new TreeSet>(); + for (int i = 0; i < entries.length; i++) + s.add(new Entry(i)); + return s; + } + @SuppressWarnings("unchecked") + public V get(Object key) + { + for (int i = 0; i < entries.length; i++) + if (key == entries[i][0] || (key != null && key.equals(entries[i][0]))) + return (V) entries[i][1]; + return null; + } + public boolean isEmpty() + { + return entries.length == 0; + } + @SuppressWarnings("unchecked") + public Set keySet() + { + Set s = new TreeSet(); + for (Object[] entry: entries) + s.add((K) entry[0]); + return s; + } + public V put(K key, V value) + { + throw new UnsupportedOperationException(); + } + public void putAll(Map t) + { + throw new UnsupportedOperationException(); + } + public V remove(Object key) + { + throw new UnsupportedOperationException(); + } + public int size() + { + return entries.length; + } + @SuppressWarnings("unchecked") + public Collection values() + { + List l = new Vector(); + for (Object[] entry: entries) + l.add((V) entry[1]); + return l; + } + public int hashCode() + { + return Arrays.deepHashCode(entries); + } + @SuppressWarnings("unchecked") + public boolean equals(Object o) + { + if (null == o) return false; + if (!(o instanceof Map)) return false; + return ((Map) o).entrySet().equals(entrySet()); + } + public String toString() + { + String s = "{ "; + for (int i = 0; i < entries.length; i++) + s += entries[i][0] + " => " + entries[i][1] + ","; + return s.replaceAll(".$", " }"); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusMatchRule.java b/app/src/main/java/org/freedesktop/dbus/DBusMatchRule.java new file mode 100644 index 00000000..d75f5b66 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusMatchRule.java @@ -0,0 +1,143 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +import java.util.HashMap; + +public class DBusMatchRule +{ + /* signal, error, method_call, method_reply */ + private String type; + private String iface; + private String member; + private String object; + private String source; + private static HashMap> signalTypeMap = + new HashMap>(); + static Class getCachedSignalType(String type) + { + return signalTypeMap.get(type); + } + public DBusMatchRule(String type, String iface, String member) + { + this.type = type; + this.iface = iface; + this.member = member; + } + public DBusMatchRule(DBusExecutionException e) throws DBusException + { + this(e.getClass()); + member = null; + type = "error"; + } + public DBusMatchRule(Message m) + { + iface = m.getInterface(); + member = m.getName(); + if (m instanceof DBusSignal) + type = "signal"; + else if (m instanceof Error) { + type = "error"; + member = null; + } + else if (m instanceof MethodCall) + type = "method_call"; + else if (m instanceof MethodReturn) + type = "method_reply"; + } + public DBusMatchRule(Class c, String method) throws DBusException + { + this(c); + member = method; + type = "method_call"; + } + public DBusMatchRule(Class c, String source, String object) throws DBusException + { + this(c); + this.source = source; + this.object = object; + } + @SuppressWarnings("unchecked") + public DBusMatchRule(Class c) throws DBusException + { + if (DBusInterface.class.isAssignableFrom(c)) { + if (null != c.getAnnotation(DBusInterfaceName.class)) + iface = c.getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(c.getName()).replaceAll("."); + if (!iface.matches(".*\\..*")) + throw new DBusException($_("DBusInterfaces must be defined in a package.")); + member = null; + type = null; + } + else if (DBusSignal.class.isAssignableFrom(c)) { + if (null == c.getEnclosingClass()) + throw new DBusException($_("Signals must be declared as a member of a class implementing DBusInterface which is the member of a package.")); + else + if (null != c.getEnclosingClass().getAnnotation(DBusInterfaceName.class)) + iface = c.getEnclosingClass().getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(c.getEnclosingClass().getName()).replaceAll("."); + // Don't export things which are invalid D-Bus interfaces + if (!iface.matches(".*\\..*")) + throw new DBusException($_("DBusInterfaces must be defined in a package.")); + if (c.isAnnotationPresent(DBusMemberName.class)) + member = c.getAnnotation(DBusMemberName.class).value(); + else + member = c.getSimpleName(); + signalTypeMap.put(iface+'$'+member, (Class) c); + type = "signal"; + } + else if (Error.class.isAssignableFrom(c)) { + if (null != c.getAnnotation(DBusInterfaceName.class)) + iface = c.getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(c.getName()).replaceAll("."); + if (!iface.matches(".*\\..*")) + throw new DBusException($_("DBusInterfaces must be defined in a package.")); + member = null; + type = "error"; + } + else if (DBusExecutionException.class.isAssignableFrom(c)) { + if (null != c.getClass().getAnnotation(DBusInterfaceName.class)) + iface = c.getClass().getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(c.getClass().getName()).replaceAll("."); + if (!iface.matches(".*\\..*")) + throw new DBusException($_("DBusInterfaces must be defined in a package.")); + member = null; + type = "error"; + } + else + throw new DBusException($_("Invalid type for match rule: ")+c); + } + public String toString() + { + String s = null; + if (null != type) s = null == s ? "type='"+type+"'" : s + ",type='"+type+"'"; + if (null != member) s = null == s ? "member='"+member+"'" : s + ",member='"+member+"'"; + if (null != iface) s = null == s ? "interface='"+iface+"'" : s + ",interface='"+iface+"'"; + if (null != source) s = null == s ? "sender='"+source+"'" : s + ",sender='"+source+"'"; + if (null != object) s = null == s ? "path='"+object+"'" : s + ",path='"+object+"'"; + return s; + } + public String getType() { return type; } + public String getInterface() { return iface; } + public String getMember() { return member; } + public String getSource() { return source; } + public String getObject() { return object; } + +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusMemberName.java b/app/src/main/java/org/freedesktop/dbus/DBusMemberName.java new file mode 100644 index 00000000..da7f8fd2 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusMemberName.java @@ -0,0 +1,27 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Force the member (method/signal) name on the bus to be different to the Java name. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE,ElementType.METHOD}) +public @interface DBusMemberName +{ + /** The replacement member name. */ + String value(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusSerializable.java b/app/src/main/java/org/freedesktop/dbus/DBusSerializable.java new file mode 100644 index 00000000..8e311375 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusSerializable.java @@ -0,0 +1,38 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; +import org.freedesktop.dbus.exceptions.DBusException; +/** + * Custom classes may be sent over DBus if they implement this interface. + *

+ * In addition to the serialize method, classes MUST implement + * a deserialize method which returns null and takes as it's arguments + * all the DBus types the class will be serialied to in order and + * with type parameterisation. They MUST also provide a + * zero-argument constructor. + *

+ *

+ * The serialize method should return the class properties you wish to + * serialize, correctly formatted for the wire + * (DBusConnection.convertParameters() can help with this), in order in an + * Object array. + *

+ *

+ * The deserialize method will be called once after the zero-argument + * constructor. This should contain all the code to initialise the object + * from the types. + *

+ */ +public interface DBusSerializable +{ + public Object[] serialize() throws DBusException; +} + diff --git a/app/src/main/java/org/freedesktop/dbus/DBusSigHandler.java b/app/src/main/java/org/freedesktop/dbus/DBusSigHandler.java new file mode 100644 index 00000000..a56d8451 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusSigHandler.java @@ -0,0 +1,25 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; +/** Handle a signal on DBus. + * All Signal handlers are run in their own Thread. + * Application writers are responsible for managing any concurrency issues. + */ +public interface DBusSigHandler +{ + /** + * Handle a signal. + * @param s The signal to handle. If such a class exists, the + * signal will be an instance of the class with the correct type signature. + * Otherwise it will be an instance of DBusSignal + */ + public void handle(T s); +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusSignal.java b/app/src/main/java/org/freedesktop/dbus/DBusSignal.java new file mode 100644 index 00000000..424d5bdd --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusSignal.java @@ -0,0 +1,259 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.lang.reflect.Constructor; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.MessageFormatException; + +import cx.ath.matthew.debug.Debug; + +public class DBusSignal extends Message +{ + DBusSignal() { } + public DBusSignal(String source, String path, String iface, String member, String sig, Object... args) throws DBusException + { + super(Message.Endian.BIG, Message.MessageType.SIGNAL, (byte) 0); + + if (null == path || null == member || null == iface) + throw new MessageFormatException($_("Must specify object path, interface and signal name to Signals.")); + headers.put(Message.HeaderField.PATH,path); + headers.put(Message.HeaderField.MEMBER,member); + headers.put(Message.HeaderField.INTERFACE,iface); + + Vector hargs = new Vector(); + hargs.add(new Object[] { Message.HeaderField.PATH, new Object[] { ArgumentType.OBJECT_PATH_STRING, path } }); + hargs.add(new Object[] { Message.HeaderField.INTERFACE, new Object[] { ArgumentType.STRING_STRING, iface } }); + hargs.add(new Object[] { Message.HeaderField.MEMBER, new Object[] { ArgumentType.STRING_STRING, member } }); + + if (null != source) { + headers.put(Message.HeaderField.SENDER,source); + hargs.add(new Object[] { Message.HeaderField.SENDER, new Object[] { ArgumentType.STRING_STRING, source } }); + } + + if (null != sig) { + hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); + headers.put(Message.HeaderField.SIGNATURE,sig); + setArgs(args); + } + + blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", ++serial, hargs.toArray()); + pad((byte)8); + + long c = bytecounter; + if (null != sig) append(sig, args); + marshallint(bytecounter-c, blen, 0, 4); + bodydone = true; + } + static class internalsig extends DBusSignal + { + public internalsig(String source, String objectpath, String type, String name, String sig, Object[] parameters, long serial) throws DBusException + { + super(source, objectpath, type, name, sig, parameters, serial); + } + } + private static Map, Type[]> typeCache = new HashMap, Type[]>(); + private static Map> classCache = new HashMap>(); + private static Map, Constructor> conCache = new HashMap, Constructor>(); + private static Map signames = new HashMap(); + private static Map intnames = new HashMap(); + private Class c; + private boolean bodydone = false; + private byte[] blen; + + static void addInterfaceMap(String java, String dbus) + { + intnames.put(dbus, java); + } + static void addSignalMap(String java, String dbus) + { + signames.put(dbus, java); + } + + static DBusSignal createSignal(Class c, String source, String objectpath, String sig, long serial, Object... parameters) throws DBusException + { + String type = ""; + if (null != c.getEnclosingClass()) { + if (null != c.getEnclosingClass().getAnnotation(DBusInterfaceName.class)) + type = c.getEnclosingClass().getAnnotation(DBusInterfaceName.class).value(); + else + type = AbstractConnection.dollar_pattern.matcher(c.getEnclosingClass().getName()).replaceAll("."); + + } else + throw new DBusException($_("Signals must be declared as a member of a class implementing DBusInterface which is the member of a package.")); + DBusSignal s = new internalsig(source, objectpath, type, c.getSimpleName(), sig, parameters, serial); + s.c = c; + return s; + } + @SuppressWarnings("unchecked") + private static Class createSignalClass(String intname, String signame) throws DBusException + { + String name = intname+'$'+signame; + Class c = classCache.get(name); + if (null == c) c = DBusMatchRule.getCachedSignalType(name); + if (null != c) return c; + do { + try { + c = (Class) Class.forName(name); + } catch (ClassNotFoundException CNFe) {} + name = name.replaceAll("\\.([^\\.]*)$", "\\$$1"); + } while (null == c && name.matches(".*\\..*")); + if (null == c) + throw new DBusException($_("Could not create class from signal ")+intname+'.'+signame); + classCache.put(name, c); + return c; + } + @SuppressWarnings("unchecked") + DBusSignal createReal(AbstractConnection conn) throws DBusException + { + String intname = intnames.get(getInterface()); + String signame = signames.get(getName()); + if (null == intname) intname = getInterface(); + if (null == signame) signame = getName(); + if (null == c) + c = createSignalClass(intname,signame); + if (Debug.debug) Debug.print(Debug.DEBUG, "Converting signal to type: "+c); + Type[] types = typeCache.get(c); + Constructor con = conCache.get(c); + if (null == types) { + con = (Constructor) c.getDeclaredConstructors()[0]; + conCache.put(c, con); + Type[] ts = con.getGenericParameterTypes(); + types = new Type[ts.length-1]; + for (int i = 1; i < ts.length; i++) + if (ts[i] instanceof TypeVariable) + for (Type b: ((TypeVariable) ts[i]).getBounds()) + types[i-1] = b; + else + types[i-1] = ts[i]; + typeCache.put(c, types); + } + + try { + DBusSignal s; + Object[] args = Marshalling.deSerializeParameters(getParameters(), types, conn); + if (null == args) s = (DBusSignal) con.newInstance(getPath()); + else { + Object[] params = new Object[args.length + 1]; + params[0] = getPath(); + System.arraycopy(args, 0, params, 1, args.length); + + if (Debug.debug) Debug.print(Debug.DEBUG, "Creating signal of type "+c+" with parameters "+Arrays.deepToString(params)); + s = (DBusSignal) con.newInstance(params); + } + s.headers = headers; + s.wiredata = wiredata; + s.bytecounter = wiredata.length; + return s; + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException(e.getMessage()); + } + } + /** + * Create a new signal. + * This contructor MUST be called by all sub classes. + * @param objectpath The path to the object this is emitted from. + * @param args The parameters of the signal. + * @throws DBusException This is thrown if the subclass is incorrectly defined. + */ + @SuppressWarnings("unchecked") + protected DBusSignal(String objectpath, Object... args) throws DBusException + { + super(Message.Endian.BIG, Message.MessageType.SIGNAL, (byte) 0); + + if (!objectpath.matches(AbstractConnection.OBJECT_REGEX)) throw new DBusException($_("Invalid object path: ")+objectpath); + + Class tc = getClass(); + String member; + if (tc.isAnnotationPresent(DBusMemberName.class)) + member = tc.getAnnotation(DBusMemberName.class).value(); + else + member = tc.getSimpleName(); + String iface = null; + Class enc = tc.getEnclosingClass(); + if (null == enc || + !DBusInterface.class.isAssignableFrom(enc) || + enc.getName().equals(enc.getSimpleName())) + throw new DBusException($_("Signals must be declared as a member of a class implementing DBusInterface which is the member of a package.")); + else + if (null != enc.getAnnotation(DBusInterfaceName.class)) + iface = enc.getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(enc.getName()).replaceAll("."); + + headers.put(Message.HeaderField.PATH,objectpath); + headers.put(Message.HeaderField.MEMBER,member); + headers.put(Message.HeaderField.INTERFACE,iface); + + Vector hargs = new Vector(); + hargs.add(new Object[] { Message.HeaderField.PATH, new Object[] { ArgumentType.OBJECT_PATH_STRING, objectpath } }); + hargs.add(new Object[] { Message.HeaderField.INTERFACE, new Object[] { ArgumentType.STRING_STRING, iface } }); + hargs.add(new Object[] { Message.HeaderField.MEMBER, new Object[] { ArgumentType.STRING_STRING, member } }); + + String sig = null; + if (0 < args.length) { + try { + Type[] types = typeCache.get(tc); + if (null == types) { + Constructor con = (Constructor) tc.getDeclaredConstructors()[0]; + conCache.put(tc, con); + Type[] ts = con.getGenericParameterTypes(); + types = new Type[ts.length-1]; + for (int i = 1; i <= types.length; i++) + if (ts[i] instanceof TypeVariable) + types[i-1] = ((TypeVariable) ts[i]).getBounds()[0]; + else + types[i-1] = ts[i]; + typeCache.put(tc, types); + } + sig = Marshalling.getDBusType(types); + hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); + headers.put(Message.HeaderField.SIGNATURE,sig); + setArgs(args); + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException($_("Failed to add signal parameters: ")+e.getMessage()); + } + } + + blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", ++serial, hargs.toArray()); + pad((byte)8); + } + void appendbody(AbstractConnection conn) throws DBusException + { + if (bodydone) return; + + Type[] types = typeCache.get(getClass()); + Object[] args = Marshalling.convertParameters(getParameters(), types, conn); + setArgs(args); + String sig = getSig(); + + long c = bytecounter; + if (null != args && 0 < args.length) append(sig, args); + marshallint(bytecounter-c, blen, 0, 4); + bodydone = true; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/DirectConnection.java b/app/src/main/java/org/freedesktop/dbus/DirectConnection.java new file mode 100644 index 00000000..96bce7b7 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DirectConnection.java @@ -0,0 +1,251 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.lang.reflect.Proxy; +import java.io.File; +import java.io.IOException; +import java.net.ServerSocket; +import java.text.MessageFormat; +import java.text.ParseException; +import java.util.Random; +import java.util.Vector; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.exceptions.DBusException; + +import cx.ath.matthew.debug.Debug; + +/** Handles a peer to peer connection between two applications withou a bus daemon. + *

+ * Signal Handlers and method calls from remote objects are run in their own threads, you MUST handle the concurrency issues. + *

+ */ +public class DirectConnection extends AbstractConnection +{ + /** + * Create a direct connection to another application. + * @param address The address to connect to. This is a standard D-Bus address, except that the additional parameter 'listen=true' should be added in the application which is creating the socket. + */ + public DirectConnection(String address) throws DBusException + { + super(address); + + try { + transport = new Transport(addr, AbstractConnection.TIMEOUT); + connected = true; + } catch (IOException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); + throw new DBusException($_("Failed to connect to bus ")+IOe.getMessage()); + } catch (ParseException Pe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Pe); + throw new DBusException($_("Failed to connect to bus ")+Pe.getMessage()); + } + + listen(); + } + + /** + * Creates a bus address for a randomly generated tcp port. + * @return a random bus address. + */ + public static String createDynamicTCPSession() + { + String address = "tcp:host=localhost"; + int port; + try { + ServerSocket s = new ServerSocket(); + s.bind(null); + port = s.getLocalPort(); + s.close(); + } catch (Exception e) { + Random r = new Random(); + port = 32768 + (Math.abs(r.nextInt()) % 28232); + } + address += ",port="+port; + address += ",guid="+Transport.genGUID(); + if (Debug.debug) Debug.print("Created Session address: "+address); + return address; + } + + /** + * Creates a bus address for a randomly generated abstract unix socket. + * @return a random bus address. + */ + public static String createDynamicSession() + { + String address = "unix:"; + String path = "/tmp/dbus-XXXXXXXXXX"; + Random r = new Random(); + do { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 10; i++) + sb.append((char) ((Math.abs(r.nextInt()) % 26) + 65)); + path = path.replaceAll("..........$", sb.toString()); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Trying path "+path); + } while ((new File(path)).exists()); + address += "abstract="+path; + address += ",guid="+Transport.genGUID(); + if (Debug.debug) Debug.print("Created Session address: "+address); + return address; + } + DBusInterface dynamicProxy(String path) throws DBusException + { + try { + DBus.Introspectable intro = (DBus.Introspectable) getRemoteObject(path, DBus.Introspectable.class); + String data = intro.Introspect(); + String[] tags = data.split("[<>]"); + Vector ifaces = new Vector(); + for (String tag: tags) { + if (tag.startsWith("interface")) { + ifaces.add(tag.replaceAll("^interface *name *= *['\"]([^'\"]*)['\"].*$", "$1")); + } + } + Vector> ifcs = new Vector>(); + for(String iface: ifaces) { + int j = 0; + while (j >= 0) { + try { + ifcs.add(Class.forName(iface)); + break; + } catch (Exception e) {} + j = iface.lastIndexOf("."); + char[] cs = iface.toCharArray(); + if (j >= 0) { + cs[j] = '$'; + iface = String.valueOf(cs); + } + } + } + + if (ifcs.size() == 0) throw new DBusException($_("Could not find an interface to cast to")); + + RemoteObject ro = new RemoteObject(null, path, null, false); + DBusInterface newi = (DBusInterface) + Proxy.newProxyInstance(ifcs.get(0).getClassLoader(), + ifcs.toArray(new Class[0]), + new RemoteInvocationHandler(this, ro)); + importedObjects.put(newi, ro); + return newi; + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException(MessageFormat.format($_("Failed to create proxy object for {0}; reason: {1}."), new Object[] { path, e.getMessage()})); + } + } + + DBusInterface getExportedObject(String path) throws DBusException + { + ExportedObject o = null; + synchronized (exportedObjects) { + o = exportedObjects.get(path); + } + if (null != o && null == o.object.get()) { + unExportObject(path); + o = null; + } + if (null != o) return o.object.get(); + return dynamicProxy(path); + } + + /** + * Return a reference to a remote object. + * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. + * In particular this means that if a process providing the well known name disappears and is taken over by another process + * proxy objects gained by this method will make calls on the new proccess. + * + * This method will use bus introspection to determine the interfaces on a remote object and so + * may block and may fail. The resulting proxy object will, however, be castable + * to any interface it implements. It will also autostart the process if applicable. Also note + * that the resulting proxy may fail to execute the correct method with overloaded methods + * and that complex types may fail in interesting ways. Basically, if something odd happens, + * try specifying the interface explicitly. + * + * @param objectpath The path on which the process is exporting the object. + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted. + */ + public DBusInterface getRemoteObject(String objectpath) throws DBusException + { + if (null == objectpath) throw new DBusException($_("Invalid object path: null")); + + if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + + return dynamicProxy(objectpath); + } + + /** + * Return a reference to a remote object. + * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. + * In particular this means that if a process providing the well known name disappears and is taken over by another process + * proxy objects gained by this method will make calls on the new proccess. + * @param objectpath The path on which the process is exporting the object. + * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures + * as the interface the remote object is exporting. + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. + */ + public DBusInterface getRemoteObject(String objectpath, Class type) throws DBusException + { + if (null == objectpath) throw new DBusException($_("Invalid object path: null")); + if (null == type) throw new ClassCastException($_("Not A DBus Interface")); + + if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + + if (!DBusInterface.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Interface")); + + // don't let people import things which don't have a + // valid D-Bus interface name + if (type.getName().equals(type.getSimpleName())) + throw new DBusException($_("DBusInterfaces cannot be declared outside a package")); + + RemoteObject ro = new RemoteObject(null, objectpath, type, false); + DBusInterface i = (DBusInterface) Proxy.newProxyInstance(type.getClassLoader(), + new Class[] { type }, new RemoteInvocationHandler(this, ro)); + importedObjects.put(i, ro); + return i; + } + protected void removeSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException + { + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null != v) { + v.remove(handler); + if (0 == v.size()) { + handledSignals.remove(key); + } + } + } + } + protected void addSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException + { + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null == v) { + v = new Vector>(); + v.add(handler); + handledSignals.put(key, v); + } else + v.add(handler); + } + } + DBusInterface getExportedObject(String source, String path) throws DBusException + { + return getExportedObject(path); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/EfficientMap.java b/app/src/main/java/org/freedesktop/dbus/EfficientMap.java new file mode 100644 index 00000000..67a24848 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/EfficientMap.java @@ -0,0 +1,116 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +/** + * Provides a long => MethodCall map which doesn't allocate objects + * on insertion/removal. Keys must be inserted in ascending order. */ +class EfficientMap +{ + private long[] kv; + private MethodCall[] vv; + private int start; + private int end; + private int init_size; + public EfficientMap(int initial_size) + { + init_size = initial_size; + shrink(); + } + private void grow() + { + // create new vectors twice as long + long[] oldkv = kv; + kv = new long[oldkv.length*2]; + MethodCall[] oldvv = vv; + vv = new MethodCall[oldvv.length*2]; + + // copy start->length to the start of the new vector + System.arraycopy(oldkv,start,kv,0,oldkv.length-start); + System.arraycopy(oldvv,start,vv,0,oldvv.length-start); + // copy 0->end to the next part of the new vector + if (end != (oldkv.length-1)) { + System.arraycopy(oldkv,0,kv,oldkv.length-start,end+1); + System.arraycopy(oldvv,0,vv,oldvv.length-start,end+1); + } + // reposition pointers + start = 0; + end = oldkv.length; + } + // create a new vector with just the valid keys in and return it + public long[] getKeys() + { + int size; + if (start < end) size = end-start; + else size = kv.length-(start-end); + long[] lv = new long[size]; + int copya; + if (size > kv.length-start) copya = kv.length-start; + else copya = size; + System.arraycopy(kv,start,lv,0,copya); + if (copya < size) { + System.arraycopy(kv,0,lv,copya,size-copya); + } + return lv; + } + private void shrink() + { + if (null != kv && kv.length == init_size) return; + // reset to original size + kv = new long[init_size]; + vv = new MethodCall[init_size]; + start = 0; + end = 0; + } + public void put(long l, MethodCall m) + { + // put this at the end + kv[end] = l; + vv[end] = m; + // move the end + if (end == (kv.length-1)) end = 0; else end++; + // if we are out of space, grow. + if (end == start) grow(); + } + public MethodCall remove(long l) + { + // find the item + int pos = find(l); + // if we don't have it return null + if (-1 == pos) return null; + // get the value + MethodCall m = vv[pos]; + // set it as unused + vv[pos] = null; + kv[pos] = -1; + // move the pointer to the first full element + while (-1 == kv[start]) { + if (start == (kv.length-1)) start = 0; else start++; + // if we have emptied the list, shrink it + if (start == end) { shrink(); break; } + } + return m; + } + public boolean contains(long l) + { + // check if find succeeds + return -1 != find(l); + } + /* could binary search, but it's probably the first one */ + private int find(long l) + { + int i = start; + while (i != end && kv[i] != l) + if (i == (kv.length-1)) i = 0; else i++; + if (i == end) return -1; + return i; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/EfficientQueue.java b/app/src/main/java/org/freedesktop/dbus/EfficientQueue.java new file mode 100644 index 00000000..5724730f --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/EfficientQueue.java @@ -0,0 +1,107 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; + +/** + * Provides a Message queue which doesn't allocate objects + * on insertion/removal. */ +class EfficientQueue +{ + private Message[] mv; + private int start; + private int end; + private int init_size; + public EfficientQueue(int initial_size) + { + init_size = initial_size; + shrink(); + } + private void grow() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Growing"); + // create new vectors twice as long + Message[] oldmv = mv; + mv = new Message[oldmv.length*2]; + + // copy start->length to the start of the new vector + System.arraycopy(oldmv,start,mv,0,oldmv.length-start); + // copy 0->end to the next part of the new vector + if (end != (oldmv.length-1)) { + System.arraycopy(oldmv,0,mv,oldmv.length-start,end+1); + } + // reposition pointers + start = 0; + end = oldmv.length; + } + // create a new vector with just the valid keys in and return it + public Message[] getKeys() + { + if (start == end) return new Message[0]; + Message[] lv; + if (start < end) { + int size = end-start; + lv = new Message[size]; + System.arraycopy(mv, start, lv, 0, size); + } else { + int size = mv.length-start+end; + lv = new Message[size]; + System.arraycopy(mv, start, lv, 0, mv.length-start); + System.arraycopy(mv, 0, lv, mv.length-start, end); + } + return lv; + } + private void shrink() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Shrinking"); + if (null != mv && mv.length == init_size) return; + // reset to original size + mv = new Message[init_size]; + start = 0; + end = 0; + } + public void add(Message m) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Enqueueing Message "+m); + // put this at the end + mv[end] = m; + // move the end + if (end == (mv.length-1)) end = 0; else end++; + // if we are out of space, grow. + if (end == start) grow(); + } + public Message remove() + { + if (start == end) return null; + // find the item + int pos = start; + // get the value + Message m = mv[pos]; + // set it as unused + mv[pos] = null; + if (start == (mv.length-1)) start = 0; else start++; + if (Debug.debug) Debug.print(Debug.DEBUG, "Dequeueing "+m); + return m; + } + public boolean isEmpty() + { + // check if find succeeds + return start == end; + } + public int size() + { + if (end >= start) + return end-start; + else + return mv.length-start+end; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/Error.java b/app/src/main/java/org/freedesktop/dbus/Error.java new file mode 100644 index 00000000..93f14af6 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Error.java @@ -0,0 +1,142 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.lang.reflect.Constructor; +import java.util.Vector; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.MessageFormatException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import cx.ath.matthew.debug.Debug; + +/** + * Error messages which can be sent over the bus. + */ +public class Error extends Message +{ + Error() { } + public Error(String dest, String errorName, long replyserial, String sig, Object... args) throws DBusException + { + this(null, dest, errorName, replyserial, sig, args); + } + public Error(String source, String dest, String errorName, long replyserial, String sig, Object... args) throws DBusException + { + super(Message.Endian.BIG, Message.MessageType.ERROR, (byte) 0); + + if (null == errorName) + throw new MessageFormatException($_("Must specify error name to Errors.")); + headers.put(Message.HeaderField.REPLY_SERIAL,replyserial); + headers.put(Message.HeaderField.ERROR_NAME,errorName); + + Vector hargs = new Vector(); + hargs.add(new Object[] { Message.HeaderField.ERROR_NAME, new Object[] { ArgumentType.STRING_STRING, errorName } }); + hargs.add(new Object[] { Message.HeaderField.REPLY_SERIAL, new Object[] { ArgumentType.UINT32_STRING, replyserial } }); + + if (null != source) { + headers.put(Message.HeaderField.SENDER,source); + hargs.add(new Object[] { Message.HeaderField.SENDER, new Object[] { ArgumentType.STRING_STRING, source } }); + } + + if (null != dest) { + headers.put(Message.HeaderField.DESTINATION,dest); + hargs.add(new Object[] { Message.HeaderField.DESTINATION, new Object[] { ArgumentType.STRING_STRING, dest } }); + } + + if (null != sig) { + hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); + headers.put(Message.HeaderField.SIGNATURE,sig); + setArgs(args); + } + + byte[] blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", serial, hargs.toArray()); + pad((byte)8); + + long c = bytecounter; + if (null != sig) append(sig, args); + marshallint(bytecounter-c, blen, 0, 4); + } + public Error(String source, Message m, Throwable e) throws DBusException + { + this(source, m.getSource(), AbstractConnection.dollar_pattern.matcher(e.getClass().getName()).replaceAll("."), m.getSerial(), "s", e.getMessage()); + } + public Error(Message m, Throwable e) throws DBusException + { + this(m.getSource(), AbstractConnection.dollar_pattern.matcher(e.getClass().getName()).replaceAll("."), m.getSerial(), "s", e.getMessage()); + } + @SuppressWarnings("unchecked") + private static Class createExceptionClass(String name) + { + if (name == "org.freedesktop.DBus.Local.Disconnected") return NotConnected.class; + Class c = null; + do { + try { + c = (Class) Class.forName(name); + } catch (ClassNotFoundException CNFe) {} + name = name.replaceAll("\\.([^\\.]*)$", "\\$$1"); + } while (null == c && name.matches(".*\\..*")); + return c; + } + /** + * Turns this into an exception of the correct type + */ + public DBusExecutionException getException() + { + try { + Class c = createExceptionClass(getName()); + if (null == c || !DBusExecutionException.class.isAssignableFrom(c)) c = DBusExecutionException.class; + Constructor con = c.getConstructor(String.class); + DBusExecutionException ex; + Object[] args = getParameters(); + if (null == args || 0 == args.length) + ex = con.newInstance(""); + else { + String s = ""; + for (Object o: args) + s += o + " "; + ex = con.newInstance(s.trim()); + } + ex.setType(getName()); + return ex; + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug && null != e.getCause()) + Debug.print(Debug.ERR, e.getCause()); + DBusExecutionException ex; + Object[] args = null; + try { + args = getParameters(); + } catch (Exception ee) {} + if (null == args || 0 == args.length) + ex = new DBusExecutionException(""); + else { + String s = ""; + for (Object o: args) + s += o + " "; + ex = new DBusExecutionException(s.trim()); + } + ex.setType(getName()); + return ex; + } + } + /** + * Throw this as an exception of the correct type + */ + public void throwException() throws DBusExecutionException + { + throw getException(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/ExportedObject.java b/app/src/main/java/org/freedesktop/dbus/ExportedObject.java new file mode 100644 index 00000000..ed84add0 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/ExportedObject.java @@ -0,0 +1,166 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +class ExportedObject +{ + @SuppressWarnings("unchecked") + private String getAnnotations(AnnotatedElement c) + { + String ans = ""; + for (Annotation a: c.getDeclaredAnnotations()) { + Class t = a.annotationType(); + String value = ""; + try { + Method m = t.getMethod("value"); + value = m.invoke(a).toString(); + } catch (NoSuchMethodException NSMe) { + } catch (InvocationTargetException ITe) { + } catch (IllegalAccessException IAe) {} + + ans += " \n"; + } + return ans; + } + @SuppressWarnings("unchecked") + private Map getExportedMethods(Class c) throws DBusException + { + if (DBusInterface.class.equals(c)) return new HashMap(); + Map m = new HashMap(); + for (Class i: c.getInterfaces()) + if (DBusInterface.class.equals(i)) { + // add this class's public methods + if (null != c.getAnnotation(DBusInterfaceName.class)) { + String name = ((DBusInterfaceName) c.getAnnotation(DBusInterfaceName.class)).value(); + introspectiondata += " \n"; + DBusSignal.addInterfaceMap(c.getName(), name); + } else { + // don't let people export things which don't have a + // valid D-Bus interface name + if (c.getName().equals(c.getSimpleName())) + throw new DBusException($_("DBusInterfaces cannot be declared outside a package")); + if (c.getName().length() > DBusConnection.MAX_NAME_LENGTH) + throw new DBusException($_("Introspected interface name exceeds 255 characters. Cannot export objects of type ")+c.getName()); + else + introspectiondata += " \n"; + } + introspectiondata += getAnnotations(c); + for (Method meth: c.getDeclaredMethods()) + if (Modifier.isPublic(meth.getModifiers())) { + String ms = ""; + String name; + if (meth.isAnnotationPresent(DBusMemberName.class)) + name = meth.getAnnotation(DBusMemberName.class).value(); + else + name = meth.getName(); + if (name.length() > DBusConnection.MAX_NAME_LENGTH) + throw new DBusException($_("Introspected method name exceeds 255 characters. Cannot export objects with method ")+name); + introspectiondata += " \n"; + introspectiondata += getAnnotations(meth); + for (Class ex: meth.getExceptionTypes()) + if (DBusExecutionException.class.isAssignableFrom(ex)) + introspectiondata += + " \n"; + for (Type pt: meth.getGenericParameterTypes()) + for (String s: Marshalling.getDBusType(pt)) { + introspectiondata += " \n"; + ms += s; + } + if (!Void.TYPE.equals(meth.getGenericReturnType())) { + if (Tuple.class.isAssignableFrom((Class) meth.getReturnType())) { + ParameterizedType tc = (ParameterizedType) meth.getGenericReturnType(); + Type[] ts = tc.getActualTypeArguments(); + + for (Type t: ts) + if (t != null) + for (String s: Marshalling.getDBusType(t)) + introspectiondata += " \n"; + } else if (Object[].class.equals(meth.getGenericReturnType())) { + throw new DBusException($_("Return type of Object[] cannot be introspected properly")); + } else + for (String s: Marshalling.getDBusType(meth.getGenericReturnType())) + introspectiondata += " \n"; + } + introspectiondata += " \n"; + m.put(new MethodTuple(name, ms), meth); + } + for (Class sig: c.getDeclaredClasses()) + if (DBusSignal.class.isAssignableFrom(sig)) { + String name; + if (sig.isAnnotationPresent(DBusMemberName.class)) { + name = ((DBusMemberName) sig.getAnnotation(DBusMemberName.class)).value(); + DBusSignal.addSignalMap(sig.getSimpleName(), name); + } else + name = sig.getSimpleName(); + if (name.length() > DBusConnection.MAX_NAME_LENGTH) + throw new DBusException($_("Introspected signal name exceeds 255 characters. Cannot export objects with signals of type ")+name); + introspectiondata += " \n"; + Constructor con = sig.getConstructors()[0]; + Type[] ts = con.getGenericParameterTypes(); + for (int j = 1; j < ts.length; j++) + for (String s: Marshalling.getDBusType(ts[j])) + introspectiondata += " \n"; + introspectiondata += getAnnotations(sig); + introspectiondata += " \n"; + + } + introspectiondata += " \n"; + } else { + // recurse + m.putAll(getExportedMethods(i)); + } + return m; + } + Map methods; + Reference object; + String introspectiondata; + public ExportedObject(DBusInterface object, boolean weakreferences) throws DBusException + { + if (weakreferences) + this.object = new WeakReference(object); + else + this.object = new StrongReference(object); + introspectiondata = ""; + methods = getExportedMethods(object.getClass()); + introspectiondata += + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"; + introspectiondata += + " \n"+ + " \n"+ + " \n"+ + " \n"; + } +} + + diff --git a/app/src/main/java/org/freedesktop/dbus/Gettext.java b/app/src/main/java/org/freedesktop/dbus/Gettext.java new file mode 100644 index 00000000..5f2d31be --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Gettext.java @@ -0,0 +1,33 @@ +/* + * Pescetti Pseudo-Duplimate Generator + * + * Copyright (C) 2007 Matthew Johnson + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License Version 2 as published by + * the Free Software Foundation. This program is distributed in the hope that + * it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. You should have received a + * copy of the GNU Lesser General Public License along with this program; if not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * To Contact me, please email src@matthew.ath.cx + * + */ +package org.freedesktop.dbus; + +import java.util.ResourceBundle; + +public class Gettext +{ +// private static ResourceBundle myResources = +// ResourceBundle.getBundle("dbusjava_localized"); +// public static String $_(String s) { +// return myResources.getString(s); +// } + public static String $_(String s) { + return s; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/InternalSignal.java b/app/src/main/java/org/freedesktop/dbus/InternalSignal.java new file mode 100644 index 00000000..55954d57 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/InternalSignal.java @@ -0,0 +1,20 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; +import org.freedesktop.dbus.exceptions.DBusException; +class InternalSignal extends DBusSignal +{ + public InternalSignal(String source, String objectpath, String name, String iface, String sig, long serial, Object... parameters) throws DBusException + { + super(objectpath, iface, name, sig, parameters); + this.serial = serial; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/Marshalling.java b/app/src/main/java/org/freedesktop/dbus/Marshalling.java new file mode 100644 index 00000000..e0857e9a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Marshalling.java @@ -0,0 +1,626 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.types.DBusListType; +import org.freedesktop.dbus.types.DBusMapType; +import org.freedesktop.dbus.types.DBusStructType; + +import cx.ath.matthew.debug.Debug; + +/** + * Contains static methods for marshalling values. + */ +public class Marshalling +{ + private static Map typeCache = new HashMap(); + /** + * Will return the DBus type corresponding to the given Java type. + * Note, container type should have their ParameterizedType not their + * Class passed in here. + * @param c The Java types. + * @return The DBus types. + * @throws DBusException If the given type cannot be converted to a DBus type. + */ + public static String getDBusType(Type[] c) throws DBusException + { + StringBuffer sb = new StringBuffer(); + for (Type t: c) + for (String s: getDBusType(t)) + sb.append(s); + return sb.toString(); + } + /** + * Will return the DBus type corresponding to the given Java type. + * Note, container type should have their ParameterizedType not their + * Class passed in here. + * @param c The Java type. + * @return The DBus type. + * @throws DBusException If the given type cannot be converted to a DBus type. + */ + public static String[] getDBusType(Type c) throws DBusException + { + String[] cached = typeCache.get(c); + if (null != cached) return cached; + cached = getDBusType(c, false); + typeCache.put(c, cached); + return cached; + } + /** + * Will return the DBus type corresponding to the given Java type. + * Note, container type should have their ParameterizedType not their + * Class passed in here. + * @param c The Java type. + * @param basic If true enforces this to be a non-compound type. (compound types are Maps, Structs and Lists/arrays). + * @return The DBus type. + * @throws DBusException If the given type cannot be converted to a DBus type. + */ + public static String[] getDBusType(Type c, boolean basic) throws DBusException + { + return recursiveGetDBusType(c, basic, 0); + } + private static StringBuffer[] out = new StringBuffer[10]; + @SuppressWarnings("unchecked") + public static String[] recursiveGetDBusType(Type c, boolean basic, int level) throws DBusException + { + if (out.length <= level) { + StringBuffer[] newout = new StringBuffer[out.length]; + System.arraycopy(out, 0, newout, 0, out.length); + out = newout; + } + if (null == out[level]) out[level] = new StringBuffer(); + else out[level].delete(0, out[level].length()); + + if (basic && !(c instanceof Class)) + throw new DBusException(c+ $_(" is not a basic type")); + + if (c instanceof TypeVariable) out[level].append((char) Message.ArgumentType.VARIANT); + else if (c instanceof GenericArrayType) { + out[level].append((char) Message.ArgumentType.ARRAY); + String[] s = recursiveGetDBusType(((GenericArrayType) c).getGenericComponentType(), false, level+1); + if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); + out[level].append(s[0]); + } else if ((c instanceof Class && + DBusSerializable.class.isAssignableFrom((Class) c)) || + (c instanceof ParameterizedType && + DBusSerializable.class.isAssignableFrom((Class) ((ParameterizedType) c).getRawType()))) { + // it's a custom serializable type + Type[] newtypes = null; + if (c instanceof Class) { + for (Method m: ((Class) c).getDeclaredMethods()) + if (m.getName().equals("deserialize")) + newtypes = m.getGenericParameterTypes(); + } + else + for (Method m: ((Class) ((ParameterizedType) c).getRawType()).getDeclaredMethods()) + if (m.getName().equals("deserialize")) + newtypes = m.getGenericParameterTypes(); + + if (null == newtypes) throw new DBusException($_("Serializable classes must implement a deserialize method")); + + String[] sigs = new String[newtypes.length]; + for (int j = 0; j < sigs.length; j++) { + String[] ss = recursiveGetDBusType(newtypes[j], false, level+1); + if (1 != ss.length) throw new DBusException($_("Serializable classes must serialize to native DBus types")); + sigs[j] = ss[0]; + } + return sigs; + } + else if (c instanceof ParameterizedType) { + ParameterizedType p = (ParameterizedType) c; + if (p.getRawType().equals(Map.class)) { + out[level].append("a{"); + Type[] t = p.getActualTypeArguments(); + try { + String[] s = recursiveGetDBusType(t[0], true, level+1); + if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); + out[level].append(s[0]); + s = recursiveGetDBusType(t[1], false, level+1); + if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); + out[level].append(s[0]); + } catch (ArrayIndexOutOfBoundsException AIOOBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe); + throw new DBusException($_("Map must have 2 parameters")); + } + out[level].append('}'); + } + else if (List.class.isAssignableFrom((Class) p.getRawType())) { + for (Type t: p.getActualTypeArguments()) { + if (Type.class.equals(t)) + out[level].append((char) Message.ArgumentType.SIGNATURE); + else { + String[] s = recursiveGetDBusType(t, false, level+1); + if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); + out[level].append((char) Message.ArgumentType.ARRAY); + out[level].append(s[0]); + } + } + } + else if (p.getRawType().equals(Variant.class)) { + out[level].append((char) Message.ArgumentType.VARIANT); + } + else if (DBusInterface.class.isAssignableFrom((Class) p.getRawType())) { + out[level].append((char) Message.ArgumentType.OBJECT_PATH); + } + else if (Tuple.class.isAssignableFrom((Class) p.getRawType())) { + Type[] ts = p.getActualTypeArguments(); + Vector vs = new Vector(); + for (Type t: ts) + for (String s: recursiveGetDBusType(t, false, level+1)) + vs.add(s); + return vs.toArray(new String[0]); + } + else + throw new DBusException($_("Exporting non-exportable parameterized type ")+c); + } + + else if (c.equals(Byte.class)) out[level].append((char) Message.ArgumentType.BYTE); + else if (c.equals(Byte.TYPE)) out[level].append((char) Message.ArgumentType.BYTE); + else if (c.equals(Boolean.class)) out[level].append((char) Message.ArgumentType.BOOLEAN); + else if (c.equals(Boolean.TYPE)) out[level].append((char) Message.ArgumentType.BOOLEAN); + else if (c.equals(Short.class)) out[level].append((char) Message.ArgumentType.INT16); + else if (c.equals(Short.TYPE)) out[level].append((char) Message.ArgumentType.INT16); + else if (c.equals(UInt16.class)) out[level].append((char) Message.ArgumentType.UINT16); + else if (c.equals(Integer.class)) out[level].append((char) Message.ArgumentType.INT32); + else if (c.equals(Integer.TYPE)) out[level].append((char) Message.ArgumentType.INT32); + else if (c.equals(UInt32.class)) out[level].append((char) Message.ArgumentType.UINT32); + else if (c.equals(Long.class)) out[level].append((char) Message.ArgumentType.INT64); + else if (c.equals(Long.TYPE)) out[level].append((char) Message.ArgumentType.INT64); + else if (c.equals(UInt64.class)) out[level].append((char) Message.ArgumentType.UINT64); + else if (c.equals(Double.class)) out[level].append((char) Message.ArgumentType.DOUBLE); + else if (c.equals(Double.TYPE)) out[level].append((char) Message.ArgumentType.DOUBLE); + else if (c.equals(Float.class) && AbstractConnection.FLOAT_SUPPORT) out[level].append((char) Message.ArgumentType.FLOAT); + else if (c.equals(Float.class)) out[level].append((char) Message.ArgumentType.DOUBLE); + else if (c.equals(Float.TYPE) && AbstractConnection.FLOAT_SUPPORT) out[level].append((char) Message.ArgumentType.FLOAT); + else if (c.equals(Float.TYPE)) out[level].append((char) Message.ArgumentType.DOUBLE); + else if (c.equals(String.class)) out[level].append((char) Message.ArgumentType.STRING); + else if (c.equals(Variant.class)) out[level].append((char) Message.ArgumentType.VARIANT); + else if (c instanceof Class && + DBusInterface.class.isAssignableFrom((Class) c)) out[level].append((char) Message.ArgumentType.OBJECT_PATH); + else if (c instanceof Class && + Path.class.equals((Class) c)) out[level].append((char) Message.ArgumentType.OBJECT_PATH); + else if (c instanceof Class && + ObjectPath.class.equals((Class) c)) out[level].append((char) Message.ArgumentType.OBJECT_PATH); + else if (c instanceof Class && + ((Class) c).isArray()) { + if (Type.class.equals(((Class) c).getComponentType())) + out[level].append((char) Message.ArgumentType.SIGNATURE); + else { + out[level].append((char) Message.ArgumentType.ARRAY); + String[] s = recursiveGetDBusType(((Class) c).getComponentType(), false, level+1); + if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); + out[level].append(s[0]); + } + } else if (c instanceof Class && + Struct.class.isAssignableFrom((Class) c)) { + out[level].append((char) Message.ArgumentType.STRUCT1); + Type[] ts = Container.getTypeCache(c); + if (null == ts) { + Field[] fs = ((Class) c).getDeclaredFields(); + ts = new Type[fs.length]; + for (Field f : fs) { + Position p = f.getAnnotation(Position.class); + if (null == p) continue; + ts[p.value()] = f.getGenericType(); + } + Container.putTypeCache(c, ts); + } + + for (Type t: ts) + if (t != null) + for (String s: recursiveGetDBusType(t, false, level+1)) + out[level].append(s); + out[level].append(')'); + } else { + throw new DBusException($_("Exporting non-exportable type ")+c); + } + + if (Debug.debug) Debug.print(Debug.VERBOSE, "Converted Java type: "+c+" to D-Bus Type: "+out[level]); + + return new String[] { out[level].toString() }; + } + + /** + * Converts a dbus type string into Java Type objects, + * @param dbus The DBus type or types. + * @param rv Vector to return the types in. + * @param limit Maximum number of types to parse (-1 == nolimit). + * @return number of characters parsed from the type string. + */ + public static int getJavaType(String dbus, List rv, int limit) throws DBusException + { + if (null == dbus || "".equals(dbus) || 0 == limit) return 0; + + try { + int i = 0; + for (; i < dbus.length() && (-1 == limit || limit > rv.size()); i++) + switch(dbus.charAt(i)) { + case Message.ArgumentType.STRUCT1: + int j = i+1; + for (int c = 1; c > 0; j++) { + if (')' == dbus.charAt(j)) c--; + else if (Message.ArgumentType.STRUCT1 == dbus.charAt(j)) c++; + } + + Vector contained = new Vector(); + int c = getJavaType(dbus.substring(i+1, j-1), contained, -1); + rv.add(new DBusStructType(contained.toArray(new Type[0]))); + i = j; + break; + case Message.ArgumentType.ARRAY: + if (Message.ArgumentType.DICT_ENTRY1 == dbus.charAt(i+1)) { + contained = new Vector(); + c = getJavaType(dbus.substring(i+2), contained, 2); + rv.add(new DBusMapType(contained.get(0), contained.get(1))); + i += (c+2); + } else { + contained = new Vector(); + c = getJavaType(dbus.substring(i+1), contained, 1); + rv.add(new DBusListType(contained.get(0))); + i += c; + } + break; + case Message.ArgumentType.VARIANT: + rv.add(Variant.class); + break; + case Message.ArgumentType.BOOLEAN: + rv.add(Boolean.class); + break; + case Message.ArgumentType.INT16: + rv.add(Short.class); + break; + case Message.ArgumentType.BYTE: + rv.add(Byte.class); + break; + case Message.ArgumentType.OBJECT_PATH: + rv.add(DBusInterface.class); + break; + case Message.ArgumentType.UINT16: + rv.add(UInt16.class); + break; + case Message.ArgumentType.INT32: + rv.add(Integer.class); + break; + case Message.ArgumentType.UINT32: + rv.add(UInt32.class); + break; + case Message.ArgumentType.INT64: + rv.add(Long.class); + break; + case Message.ArgumentType.UINT64: + rv.add(UInt64.class); + break; + case Message.ArgumentType.DOUBLE: + rv.add(Double.class); + break; + case Message.ArgumentType.FLOAT: + rv.add(Float.class); + break; + case Message.ArgumentType.STRING: + rv.add(String.class); + break; + case Message.ArgumentType.SIGNATURE: + rv.add(Type[].class); + break; + case Message.ArgumentType.DICT_ENTRY1: + rv.add(Map.Entry.class); + contained = new Vector(); + c = getJavaType(dbus.substring(i+1), contained, 2); + i+=c+1; + break; + default: + throw new DBusException(MessageFormat.format($_("Failed to parse DBus type signature: {0} ({1})."), new Object[] { dbus, dbus.charAt(i) })); + } + return i; + } catch (IndexOutOfBoundsException IOOBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOOBe); + throw new DBusException($_("Failed to parse DBus type signature: ")+dbus); + } + } + /** + * Recursively converts types for serialization onto DBus. + * @param parameters The parameters to convert. + * @param types The (possibly generic) types of the parameters. + * @return The converted parameters. + * @throws DBusException Thrown if there is an error in converting the objects. + */ + @SuppressWarnings("unchecked") + public static Object[] convertParameters(Object[] parameters, Type[] types, AbstractConnection conn) throws DBusException + { + if (null == parameters) return null; + for (int i = 0; i < parameters.length; i++) { + if (Debug.debug) Debug.print(Debug.VERBOSE,"Converting "+i+" from "+parameters[i]+" to "+types[i]); + if (null == parameters[i]) continue; + + if (parameters[i] instanceof DBusSerializable) { + for (Method m: parameters[i].getClass().getDeclaredMethods()) + if (m.getName().equals("deserialize")) { + Type[] newtypes = m.getParameterTypes(); + Type[] expand = new Type[types.length + newtypes.length - 1]; + System.arraycopy(types, 0, expand, 0, i); + System.arraycopy(newtypes, 0, expand, i, newtypes.length); + System.arraycopy(types, i+1, expand, i+newtypes.length, types.length-i-1); + types = expand; + Object[] newparams = ((DBusSerializable) parameters[i]).serialize(); + Object[] exparams = new Object[parameters.length + newparams.length - 1]; + System.arraycopy(parameters, 0, exparams, 0, i); + System.arraycopy(newparams, 0, exparams, i, newparams.length); + System.arraycopy(parameters, i+1, exparams, i+newparams.length, parameters.length-i-1); + parameters = exparams; + } + i--; + } else if (parameters[i] instanceof Tuple) { + Type[] newtypes = ((ParameterizedType) types[i]).getActualTypeArguments(); + Type[] expand = new Type[types.length + newtypes.length - 1]; + System.arraycopy(types, 0, expand, 0, i); + System.arraycopy(newtypes, 0, expand, i, newtypes.length); + System.arraycopy(types, i+1, expand, i+newtypes.length, types. length-i-1); + types = expand; + Object[] newparams = ((Tuple) parameters[i]).getParameters(); + Object[] exparams = new Object[parameters.length + newparams.length - 1]; + System.arraycopy(parameters, 0, exparams, 0, i); + System.arraycopy(newparams, 0, exparams, i, newparams.length); + System.arraycopy(parameters, i+1, exparams, i+newparams.length, parameters.length-i-1); + parameters = exparams; + if (Debug.debug) Debug.print(Debug.VERBOSE, "New params: "+Arrays.deepToString(parameters)+" new types: "+Arrays.deepToString(types)); + i--; + } else if (types[i] instanceof TypeVariable && + !(parameters[i] instanceof Variant)) + // its an unwrapped variant, wrap it + parameters[i] = new Variant(parameters[i]); + else if (parameters[i] instanceof DBusInterface) + parameters[i] = conn.getExportedObject((DBusInterface) parameters[i]); + } + return parameters; + } + @SuppressWarnings("unchecked") + static Object deSerializeParameter(Object parameter, Type type, AbstractConnection conn) throws Exception + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing from "+parameter.getClass()+" to "+type.getClass()); + if (null == parameter) + return null; + + // its a wrapped variant, unwrap it + if (type instanceof TypeVariable + && parameter instanceof Variant) { + parameter = ((Variant)parameter).getValue(); + } + + // Turn a signature into a Type[] + if (type instanceof Class + && ((Class) type).isArray() + && ((Class) type).getComponentType().equals(Type.class) + && parameter instanceof String) { + Vector rv = new Vector(); + getJavaType((String) parameter, rv, -1); + parameter = rv.toArray(new Type[0]); + } + + // its an object path, get/create the proxy + if (parameter instanceof ObjectPath) { + if (type instanceof Class && DBusInterface.class.isAssignableFrom((Class) type)) + parameter = conn.getExportedObject( + ((ObjectPath) parameter).source, + ((ObjectPath) parameter).path); + else + parameter = new Path(((ObjectPath) parameter).path); + } + + // it should be a struct. create it + if (parameter instanceof Object[] && + type instanceof Class && + Struct.class.isAssignableFrom((Class) type)) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Creating Struct "+type+" from "+parameter); + Type[] ts = Container.getTypeCache(type); + if (null == ts) { + Field[] fs = ((Class) type).getDeclaredFields(); + ts = new Type[fs.length]; + for (Field f : fs) { + Position p = f.getAnnotation(Position.class); + if (null == p) continue; + ts[p.value()] = f.getGenericType(); + } + Container.putTypeCache(type, ts); + } + + // recurse over struct contents + parameter = deSerializeParameters((Object[]) parameter, ts, conn); + for (Constructor con: ((Class) type).getDeclaredConstructors()) { + try { + parameter = con.newInstance((Object[]) parameter); + break; + } catch (IllegalArgumentException IAe) {} + } + } + + // recurse over arrays + if (parameter instanceof Object[]) { + Type[] ts = new Type[((Object[]) parameter).length]; + Arrays.fill(ts, parameter.getClass().getComponentType()); + parameter = deSerializeParameters((Object[]) parameter, + ts, conn); + } + if (parameter instanceof List) { + Type type2; + if (type instanceof ParameterizedType) + type2 = ((ParameterizedType) type).getActualTypeArguments()[0]; + else if (type instanceof GenericArrayType) + type2 = ((GenericArrayType) type).getGenericComponentType(); + else if (type instanceof Class && ((Class) type).isArray()) + type2 = ((Class) type).getComponentType(); + else + type2 = null; + if (null != type2) + parameter = deSerializeParameters((List) parameter, type2, conn); + } + + // correct floats if appropriate + if (type.equals(Float.class) || type.equals(Float.TYPE)) + if (!(parameter instanceof Float)) + parameter = ((Number) parameter).floatValue(); + + // make sure arrays are in the correct format + if (parameter instanceof Object[] || + parameter instanceof List || + parameter.getClass().isArray()) { + if (type instanceof ParameterizedType) + parameter = ArrayFrob.convert(parameter, + (Class) ((ParameterizedType) type).getRawType()); + else if (type instanceof GenericArrayType) { + Type ct = ((GenericArrayType) type).getGenericComponentType(); + Class cc = null; + if (ct instanceof Class) + cc = (Class) ct; + if (ct instanceof ParameterizedType) + cc = (Class) ((ParameterizedType) ct).getRawType(); + Object o = Array.newInstance(cc, 0); + parameter = ArrayFrob.convert(parameter, + o.getClass()); + } else if (type instanceof Class && + ((Class) type).isArray()) { + Class cc = ((Class) type).getComponentType(); + if ((cc.equals(Float.class) || cc.equals(Float.TYPE)) + && (parameter instanceof double[])) { + double[] tmp1 = (double[]) parameter; + float[] tmp2 = new float[tmp1.length]; + for (int i = 0; i < tmp1.length; i++) + tmp2[i] = (float) tmp1[i]; + parameter = tmp2; + } + Object o = Array.newInstance(cc, 0); + parameter = ArrayFrob.convert(parameter, + o.getClass()); + } + } + if (parameter instanceof DBusMap) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing a Map"); + DBusMap dmap = (DBusMap) parameter; + Type[] maptypes = ((ParameterizedType) type).getActualTypeArguments(); + for (int i = 0; i < dmap.entries.length; i++) { + dmap.entries[i][0] = deSerializeParameter(dmap.entries[i][0], maptypes[0], conn); + dmap.entries[i][1] = deSerializeParameter(dmap.entries[i][1], maptypes[1], conn); + } + } + return parameter; + } + static List deSerializeParameters(List parameters, Type type, AbstractConnection conn) throws Exception + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing from "+parameters+" to "+type); + if (null == parameters) return null; + for (int i = 0; i < parameters.size(); i++) { + if (null == parameters.get(i)) continue; + + /* DO NOT DO THIS! IT'S REALLY NOT SUPPORTED! + * if (type instanceof Class && + DBusSerializable.class.isAssignableFrom((Class) types[i])) { + for (Method m: ((Class) types[i]).getDeclaredMethods()) + if (m.getName().equals("deserialize")) { + Type[] newtypes = m.getGenericParameterTypes(); + try { + Object[] sub = new Object[newtypes.length]; + System.arraycopy(parameters, i, sub, 0, newtypes.length); + sub = deSerializeParameters(sub, newtypes, conn); + DBusSerializable sz = (DBusSerializable) ((Class) types[i]).newInstance(); + m.invoke(sz, sub); + Object[] compress = new Object[parameters.length - newtypes.length + 1]; + System.arraycopy(parameters, 0, compress, 0, i); + compress[i] = sz; + System.arraycopy(parameters, i + newtypes.length, compress, i+1, parameters.length - i - newtypes.length); + parameters = compress; + } catch (ArrayIndexOutOfBoundsException AIOOBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe); + throw new DBusException("Not enough elements to create custom object from serialized data ("+(parameters.size()-i)+" < "+(newtypes.length)+")"); + } + } + } else*/ + parameters.set(i, deSerializeParameter(parameters.get(i), type, conn)); + } + return parameters; + } + + @SuppressWarnings("unchecked") + static Object[] deSerializeParameters(Object[] parameters, Type[] types, AbstractConnection conn) throws Exception + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing from "+Arrays.deepToString(parameters)+" to "+Arrays.deepToString(types)); + if (null == parameters) return null; + + if (types.length == 1 && types[0] instanceof ParameterizedType + && Tuple.class.isAssignableFrom((Class) ((ParameterizedType) types[0]).getRawType())) { + types = ((ParameterizedType) types[0]).getActualTypeArguments(); + } + + for (int i = 0; i < parameters.length; i++) { + // CHECK IF ARRAYS HAVE THE SAME LENGTH <-- has to happen after expanding parameters + if (i >= types.length) { + if (Debug.debug) { + for (int j = 0; j < parameters.length; j++) { + Debug.print(Debug.ERR, String.format("Error, Parameters difference (%1d, '%2s')", j, parameters[j].toString())); + } + } + throw new DBusException($_("Error deserializing message: number of parameters didn't match receiving signature")); + } + if (null == parameters[i]) continue; + + if ((types[i] instanceof Class && + DBusSerializable.class.isAssignableFrom((Class) types[i])) || + (types[i] instanceof ParameterizedType && + DBusSerializable.class.isAssignableFrom((Class) ((ParameterizedType) types[i]).getRawType()))) { + Class dsc; + if (types[i] instanceof Class) + dsc = (Class) types[i]; + else + dsc = (Class) ((ParameterizedType) types[i]).getRawType(); + for (Method m: dsc.getDeclaredMethods()) + if (m.getName().equals("deserialize")) { + Type[] newtypes = m.getGenericParameterTypes(); + try { + Object[] sub = new Object[newtypes.length]; + System.arraycopy(parameters, i, sub, 0, newtypes.length); + sub = deSerializeParameters(sub, newtypes, conn); + DBusSerializable sz = dsc.newInstance(); + m.invoke(sz, sub); + Object[] compress = new Object[parameters.length - newtypes.length + 1]; + System.arraycopy(parameters, 0, compress, 0, i); + compress[i] = sz; + System.arraycopy(parameters, i + newtypes.length, compress, i+1, parameters.length - i - newtypes.length); + parameters = compress; + } catch (ArrayIndexOutOfBoundsException AIOOBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe); + throw new DBusException(MessageFormat.format($_("Not enough elements to create custom object from serialized data ({0} < {1})."), + new Object[] { parameters.length-i, newtypes.length })); + } + } + } else + parameters[i] = deSerializeParameter(parameters[i], types[i], conn); + } + return parameters; + } +} + + diff --git a/app/src/main/java/org/freedesktop/dbus/Message.java b/app/src/main/java/org/freedesktop/dbus/Message.java new file mode 100644 index 00000000..c7762330 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Message.java @@ -0,0 +1,1132 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.lang.reflect.Array; +import java.lang.reflect.Type; +import java.io.UnsupportedEncodingException; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.utils.Hexdump; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.MarshallingException; +import org.freedesktop.dbus.exceptions.UnknownTypeCodeException; + +/** + * Superclass of all messages which are sent over the Bus. + * This class deals with all the marshalling to/from the wire format. + */ +public class Message +{ + /** Defines constants representing the endianness of the message. */ + public static interface Endian { + public static final byte BIG = 'B'; + public static final byte LITTLE = 'l'; + } + /** Defines constants representing the flags which can be set on a message. */ + public static interface Flags { + public static final byte NO_REPLY_EXPECTED = 0x01; + public static final byte NO_AUTO_START = 0x02; + public static final byte ASYNC = 0x40; + } + /** Defines constants for each message type. */ + public static interface MessageType { + public static final byte METHOD_CALL = 1; + public static final byte METHOD_RETURN = 2; + public static final byte ERROR = 3; + public static final byte SIGNAL = 4; + } + /** The current protocol major version. */ + public static final byte PROTOCOL = 1; + /** Defines constants for each valid header field type. */ + public static interface HeaderField { + public static final byte PATH = 1; + public static final byte INTERFACE = 2; + public static final byte MEMBER = 3; + public static final byte ERROR_NAME = 4; + public static final byte REPLY_SERIAL = 5; + public static final byte DESTINATION = 6; + public static final byte SENDER = 7; + public static final byte SIGNATURE = 8; + } + /** Defines constants for each argument type. + * There are two constants for each argument type, + * as a byte or as a String (the _STRING version) */ + public static interface ArgumentType { + public static final String BYTE_STRING="y"; + public static final String BOOLEAN_STRING="b"; + public static final String INT16_STRING="n"; + public static final String UINT16_STRING="q"; + public static final String INT32_STRING="i"; + public static final String UINT32_STRING="u"; + public static final String INT64_STRING="x"; + public static final String UINT64_STRING="t"; + public static final String DOUBLE_STRING="d"; + public static final String FLOAT_STRING="f"; + public static final String STRING_STRING="s"; + public static final String OBJECT_PATH_STRING="o"; + public static final String SIGNATURE_STRING="g"; + public static final String ARRAY_STRING="a"; + public static final String VARIANT_STRING="v"; + public static final String STRUCT_STRING="r"; + public static final String STRUCT1_STRING="("; + public static final String STRUCT2_STRING=")"; + public static final String DICT_ENTRY_STRING="e"; + public static final String DICT_ENTRY1_STRING="{"; + public static final String DICT_ENTRY2_STRING="}"; + + public static final byte BYTE='y'; + public static final byte BOOLEAN='b'; + public static final byte INT16='n'; + public static final byte UINT16='q'; + public static final byte INT32='i'; + public static final byte UINT32='u'; + public static final byte INT64='x'; + public static final byte UINT64='t'; + public static final byte DOUBLE='d'; + public static final byte FLOAT='f'; + public static final byte STRING='s'; + public static final byte OBJECT_PATH='o'; + public static final byte SIGNATURE='g'; + public static final byte ARRAY='a'; + public static final byte VARIANT='v'; + public static final byte STRUCT='r'; + public static final byte STRUCT1='('; + public static final byte STRUCT2=')'; + public static final byte DICT_ENTRY='e'; + public static final byte DICT_ENTRY1='{'; + public static final byte DICT_ENTRY2='}'; + } + /** Keep a static reference to each size of padding array to prevent allocation. */ + private static byte[][] padding; + static { + padding = new byte[][] { + null, + new byte[1], + new byte[2], + new byte[3], + new byte[4], + new byte[5], + new byte[6], + new byte[7] }; + } + /** Steps to increment the buffer array. */ + private static final int BUFFERINCREMENT = 20; + + private boolean big; + protected byte[][] wiredata; + protected long bytecounter; + protected Map headers; + protected static long globalserial = 0; + protected long serial; + protected byte type; + protected byte flags; + protected byte protover; + private Object[] args; + private byte[] body; + private long bodylen = 0; + private int preallocated = 0; + private int paofs = 0; + private byte[] pabuf; + private int bufferuse = 0; + + /** + * Returns the name of the given header field. + */ + public static String getHeaderFieldName(byte field) + { + switch (field) { + case HeaderField.PATH: return "Path"; + case HeaderField.INTERFACE: return "Interface"; + case HeaderField.MEMBER: return "Member"; + case HeaderField.ERROR_NAME: return "Error Name"; + case HeaderField.REPLY_SERIAL: return "Reply Serial"; + case HeaderField.DESTINATION: return "Destination"; + case HeaderField.SENDER: return "Sender"; + case HeaderField.SIGNATURE: return "Signature"; + default: return "Invalid"; + } + } + + /** + * Create a message; only to be called by sub-classes. + * @param endian The endianness to create the message. + * @param type The message type. + * @param flags Any message flags. + */ + protected Message(byte endian, byte type, byte flags) throws DBusException + { + wiredata = new byte[BUFFERINCREMENT][]; + headers = new HashMap(); + big = (Endian.BIG == endian); + bytecounter = 0; + synchronized (Message.class) { + serial = ++globalserial; + } + if (Debug.debug) Debug.print(Debug.DEBUG, "Creating message with serial "+serial); + this.type = type; + this.flags = flags; + preallocate(4); + append("yyyy", endian, type, flags, Message.PROTOCOL); + } + /** + * Create a blank message. Only to be used when calling populate. + */ + protected Message() + { + wiredata = new byte[BUFFERINCREMENT][]; + headers = new HashMap(); + bytecounter = 0; + } + /** + * Create a message from wire-format data. + * @param msg D-Bus serialized data of type yyyuu + * @param headers D-Bus serialized data of type a(yv) + * @param body D-Bus serialized data of the signature defined in headers. + */ + @SuppressWarnings("unchecked") + void populate(byte[] msg, byte[] headers, byte[] body) throws DBusException + { + big = (msg[0] == Endian.BIG); + type = msg[1]; + flags = msg[2]; + protover = msg[3]; + wiredata[0] = msg; + wiredata[1] = headers; + wiredata[2] = body; + this.body = body; + bufferuse = 3; + bodylen = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 4)[0]).longValue(); + serial = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 8)[0]).longValue(); + bytecounter = msg.length+headers.length+body.length; + if (Debug.debug) Debug.print(Debug.VERBOSE, headers); + Object[] hs = extract("a(yv)", headers, 0); + if (Debug.debug) Debug.print(Debug.VERBOSE, Arrays.deepToString(hs)); + for (Object o: (Vector) hs[0]) { + this.headers.put((Byte) ((Object[])o)[0], ((Variant)((Object[])o)[1]).getValue()); + } + } + /** + * Create a buffer of num bytes. + * Data is copied to this rather than added to the buffer list. + */ + private void preallocate(int num) + { + preallocated = 0; + pabuf = new byte[num]; + appendBytes(pabuf); + preallocated = num; + paofs = 0; + } + /** + * Ensures there are enough free buffers. + * @param num number of free buffers to create. + */ + private void ensureBuffers(int num) + { + int increase = num - wiredata.length + bufferuse; + if (increase > 0) { + if (increase < BUFFERINCREMENT) increase = BUFFERINCREMENT; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing "+bufferuse); + byte[][] temp = new byte[wiredata.length+increase][]; + System.arraycopy(wiredata, 0, temp, 0, wiredata.length); + wiredata = temp; + } + } + /** + * Appends a buffer to the buffer list. + */ + protected void appendBytes(byte[] buf) + { + if (null == buf) return; + if (preallocated > 0) { + if (paofs+buf.length > pabuf.length) + throw new ArrayIndexOutOfBoundsException(MessageFormat.format($_("Array index out of bounds, paofs={0}, pabuf.length={1}, buf.length={2}."), new Object[] { paofs, pabuf.length, buf.length })); + System.arraycopy(buf, 0, pabuf, paofs, buf.length); + paofs += buf.length; + preallocated -= buf.length; + } else { + if (bufferuse == wiredata.length) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing "+bufferuse); + byte[][] temp = new byte[wiredata.length+BUFFERINCREMENT][]; + System.arraycopy(wiredata, 0, temp, 0, wiredata.length); + wiredata = temp; + } + wiredata[bufferuse++] = buf; + bytecounter += buf.length; + } + } + /** + * Appends a byte to the buffer list. + */ + protected void appendByte(byte b) + { + if (preallocated > 0) { + pabuf[paofs++] = b; + preallocated--; + } else { + if (bufferuse == wiredata.length) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing "+bufferuse); + byte[][] temp = new byte[wiredata.length+BUFFERINCREMENT][]; + System.arraycopy(wiredata, 0, temp, 0, wiredata.length); + wiredata = temp; + } + wiredata[bufferuse++] = new byte[] { b }; + bytecounter++; + } + } + /** + * Demarshalls an integer of a given width from a buffer. + * Endianness is determined from the format of the message. + * @param buf The buffer to demarshall from. + * @param ofs The offset to demarshall from. + * @param width The byte-width of the int. + */ + public long demarshallint(byte[] buf, int ofs, int width) + { return big ? demarshallintBig(buf,ofs,width) : demarshallintLittle(buf,ofs,width); } + /** + * Demarshalls an integer of a given width from a buffer. + * @param buf The buffer to demarshall from. + * @param ofs The offset to demarshall from. + * @param endian The endianness to use in demarshalling. + * @param width The byte-width of the int. + */ + public static long demarshallint(byte[] buf, int ofs, byte endian, int width) + { return endian==Endian.BIG ? demarshallintBig(buf,ofs,width) : demarshallintLittle(buf,ofs,width); } + /** + * Demarshalls an integer of a given width from a buffer using big-endian format. + * @param buf The buffer to demarshall from. + * @param ofs The offset to demarshall from. + * @param width The byte-width of the int. + */ + public static long demarshallintBig(byte[] buf, int ofs, int width) + { + long l = 0; + for (int i = 0; i < width; i++) { + l <<=8; + l |= (buf[ofs+i] & 0xFF); + } + return l; + } + /** + * Demarshalls an integer of a given width from a buffer using little-endian format. + * @param buf The buffer to demarshall from. + * @param ofs The offset to demarshall from. + * @param width The byte-width of the int. + */ + public static long demarshallintLittle(byte[] buf, int ofs, int width) + { + long l = 0; + for (int i = (width-1); i >= 0; i--) { + l <<=8; + l |= (buf[ofs+i] & 0xFF); + } + return l; + } + /** + * Marshalls an integer of a given width and appends it to the message. + * Endianness is determined from the message. + * @param l The integer to marshall. + * @param width The byte-width of the int. + */ + public void appendint(long l, int width) + { + byte[] buf = new byte[width]; + marshallint(l, buf, 0, width); + appendBytes(buf); + } + /** + * Marshalls an integer of a given width into a buffer. + * Endianness is determined from the message. + * @param l The integer to marshall. + * @param buf The buffer to marshall to. + * @param ofs The offset to marshall to. + * @param width The byte-width of the int. + */ + public void marshallint(long l, byte[] buf, int ofs, int width) + { + if (big) marshallintBig(l, buf, ofs, width); else marshallintLittle(l, buf, ofs, width); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Marshalled int "+l+" to "+Hexdump.toHex(buf,ofs,width)); + } + /** + * Marshalls an integer of a given width into a buffer using big-endian format. + * @param l The integer to marshall. + * @param buf The buffer to marshall to. + * @param ofs The offset to marshall to. + * @param width The byte-width of the int. + */ + public static void marshallintBig(long l, byte[] buf, int ofs, int width) + { + for (int i = (width-1); i >= 0; i--) { + buf[i+ofs] = (byte) (l & 0xFF); + l >>= 8; + } + } + /** + * Marshalls an integer of a given width into a buffer using little-endian format. + * @param l The integer to marshall. + * @param buf The buffer to demarshall to. + * @param ofs The offset to demarshall to. + * @param width The byte-width of the int. + */ + public static void marshallintLittle(long l, byte[] buf, int ofs, int width) + { + for (int i = 0; i < width; i++) { + buf[i+ofs] = (byte) (l & 0xFF); + l >>= 8; + } + } + public byte[][] getWireData() + { + return wiredata; + } + /** + * Formats the message in a human-readable format. + */ + public String toString() + { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getSimpleName()); + sb.append ('('); + sb.append (flags); + sb.append (','); + sb.append(serial); + sb.append (')'); + sb.append (' '); + sb.append ('{'); + sb.append(' '); + if (headers.size() == 0) + sb.append('}'); + else { + for (Byte field: headers.keySet()) { + sb.append(getHeaderFieldName(field)); + sb.append('='); + sb.append('>'); + sb.append(headers.get(field).toString()); + sb.append(','); + sb.append(' '); + } + sb.setCharAt(sb.length()-2,' '); + sb.setCharAt(sb.length()-1,'}'); + } + sb.append(' '); + sb.append('{'); + sb.append(' '); + Object[] args = null; + try { + args = getParameters(); + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + } + if (null == args || 0 == args.length) + sb.append('}'); + else { + for (Object o: args) { + if (o instanceof Object[]) + sb.append(Arrays.deepToString((Object[]) o)); + else if (o instanceof byte[]) + sb.append(Arrays.toString((byte[]) o)); + else if (o instanceof int[]) + sb.append(Arrays.toString((int[]) o)); + else if (o instanceof short[]) + sb.append(Arrays.toString((short[]) o)); + else if (o instanceof long[]) + sb.append(Arrays.toString((long[]) o)); + else if (o instanceof boolean[]) + sb.append(Arrays.toString((boolean[]) o)); + else if (o instanceof double[]) + sb.append(Arrays.toString((double[]) o)); + else if (o instanceof float[]) + sb.append(Arrays.toString((float[]) o)); + else + sb.append(o.toString()); + sb.append(','); + sb.append(' '); + } + sb.setCharAt(sb.length()-2,' '); + sb.setCharAt(sb.length()-1,'}'); + } + return sb.toString(); + } + /** + * Returns the value of the header field of a given field. + * @param type The field to return. + * @return The value of the field or null if unset. + */ + public Object getHeader(byte type) { return headers.get(type); } + /** + * Appends a value to the message. + * The type of the value is read from a D-Bus signature and used to marshall + * the value. + * @param sigb A buffer of the D-Bus signature. + * @param sigofs The offset into the signature corresponding to this value. + * @param data The value to marshall. + * @return The offset into the signature of the end of this value's type. + */ + @SuppressWarnings("unchecked") + private int appendone(byte[] sigb, int sigofs, Object data) throws DBusException + { + try { + int i = sigofs; + if (Debug.debug) Debug.print(Debug.VERBOSE, (Object) bytecounter); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending type: "+((char)sigb[i])+" value: "+data); + + // pad to the alignment of this type. + pad(sigb[i]); + switch (sigb[i]) { + case ArgumentType.BYTE: + appendByte(((Number) data).byteValue()); + break; + case ArgumentType.BOOLEAN: + appendint(((Boolean) data).booleanValue() ? 1 : 0, 4); + break; + case ArgumentType.DOUBLE: + long l = Double.doubleToLongBits(((Number) data).doubleValue()); + appendint(l, 8); + break; + case ArgumentType.FLOAT: + int rf = Float.floatToIntBits(((Number) data).floatValue()); + appendint(rf, 4); + break; + case ArgumentType.UINT32: + appendint(((Number) data).longValue(), 4); + break; + case ArgumentType.INT64: + appendint(((Number) data).longValue(), 8); + break; + case ArgumentType.UINT64: + if (big) { + appendint(((UInt64) data).top(), 4); + appendint(((UInt64) data).bottom(), 4); + } else { + appendint(((UInt64) data).bottom(), 4); + appendint(((UInt64) data).top(), 4); + } + break; + case ArgumentType.INT32: + appendint(((Number) data).intValue(), 4); + break; + case ArgumentType.UINT16: + appendint(((Number) data).intValue(), 2); + break; + case ArgumentType.INT16: + appendint(((Number) data).shortValue(), 2); + break; + case ArgumentType.STRING: + case ArgumentType.OBJECT_PATH: + // Strings are marshalled as a UInt32 with the length, + // followed by the String, followed by a null byte. + String payload = data.toString(); + byte[] payloadbytes = null; + try { + payloadbytes = payload.getBytes("UTF-8"); + } catch (UnsupportedEncodingException UEe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(UEe); + throw new DBusException($_("System does not support UTF-8 encoding")); + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending String of length "+payloadbytes.length); + appendint(payloadbytes.length, 4); + appendBytes(payloadbytes); + appendBytes(padding[1]); + //pad(ArgumentType.STRING);? do we need this? + break; + case ArgumentType.SIGNATURE: + // Signatures are marshalled as a byte with the length, + // followed by the String, followed by a null byte. + // Signatures are generally short, so preallocate the array + // for the string, length and null byte. + if (data instanceof Type[]) + payload = Marshalling.getDBusType((Type[]) data); + else + payload = (String) data; + byte[] pbytes = payload.getBytes(); + preallocate(2+pbytes.length); + appendByte((byte) pbytes.length); + appendBytes(pbytes); + appendByte((byte) 0); + break; + case ArgumentType.ARRAY: + // Arrays are given as a UInt32 for the length in bytes, + // padding to the element alignment, then elements in + // order. The length is the length from the end of the + // initial padding to the end of the last element. + if (Debug.debug) { + if (data instanceof Object[]) + Debug.print(Debug.VERBOSE, "Appending array: "+Arrays.deepToString((Object[])data)); + } + + byte[] alen = new byte[4]; + appendBytes(alen); + pad(sigb[++i]); + long c = bytecounter; + + // optimise primatives + if (data.getClass().isArray() && + data.getClass().getComponentType().isPrimitive()) { + byte[] primbuf; + int algn = getAlignment(sigb[i]); + int len = Array.getLength(data); + switch (sigb[i]) { + case ArgumentType.BYTE: + primbuf = (byte[]) data; + break; + case ArgumentType.INT16: + case ArgumentType.INT32: + case ArgumentType.INT64: + primbuf = new byte[len*algn]; + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint(Array.getLong(data, j), primbuf, k, algn); + break; + case ArgumentType.BOOLEAN: + primbuf = new byte[len*algn]; + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint(Array.getBoolean(data, j)?1:0, primbuf, k, algn); + break; + case ArgumentType.DOUBLE: + primbuf = new byte[len*algn]; + if (data instanceof float[]) + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint(Double.doubleToRawLongBits(((float[])data)[j]), + primbuf, k, algn); + else + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint(Double.doubleToRawLongBits(((double[])data)[j]), + primbuf, k, algn); + break; + case ArgumentType.FLOAT: + primbuf = new byte[len*algn]; + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint( + Float.floatToRawIntBits(((float[])data)[j]), + primbuf, k, algn); + break; + default: + throw new MarshallingException($_("Primative array being sent as non-primative array.")); + } + appendBytes(primbuf); + } else if (data instanceof List) { + Object[] contents = ((List) data).toArray(); + int diff = i; + ensureBuffers(contents.length*4); + for (Object o: contents) + diff = appendone(sigb, i, o); + i = diff; + } else if (data instanceof Map) { + int diff = i; + ensureBuffers(((Map) data).size()*6); + for (Map.Entry o: ((Map) data).entrySet()) + diff = appendone(sigb, i, o); + if (i == diff) { + // advance the type parser even on 0-size arrays. + Vector temp = new Vector(); + byte[] temp2 = new byte[sigb.length-diff]; + System.arraycopy(sigb, diff, temp2, 0, temp2.length); + String temp3 = new String(temp2); + int temp4 = Marshalling.getJavaType(temp3, temp, 1); + diff += temp4; + } + i = diff; + } else { + Object[] contents = (Object[]) data; + ensureBuffers(contents.length*4); + int diff = i; + for (Object o: contents) + diff = appendone(sigb, i, o); + i = diff; + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "start: "+c+" end: "+bytecounter+" length: "+(bytecounter-c)); + marshallint(bytecounter-c, alen, 0, 4); + break; + case ArgumentType.STRUCT1: + // Structs are aligned to 8 bytes + // and simply contain each element marshalled in order + Object[] contents; + if (data instanceof Container) + contents = ((Container) data).getParameters(); + else + contents = (Object[]) data; + ensureBuffers(contents.length*4); + int j = 0; + for (i++; sigb[i] != ArgumentType.STRUCT2; i++) + i = appendone(sigb, i, contents[j++]); + break; + case ArgumentType.DICT_ENTRY1: + // Dict entries are the same as structs. + if (data instanceof Map.Entry) { + i++; + i = appendone(sigb, i, ((Map.Entry) data).getKey()); + i++; + i = appendone(sigb, i, ((Map.Entry) data).getValue()); + i++; + } else { + contents = (Object[]) data; + j = 0; + for (i++; sigb[i] != ArgumentType.DICT_ENTRY2; i++) + i = appendone(sigb, i, contents[j++]); + } + break; + case ArgumentType.VARIANT: + // Variants are marshalled as a signature + // followed by the value. + if (data instanceof Variant) { + Variant var = (Variant) data; + appendone(new byte[] {ArgumentType.SIGNATURE}, 0, var.getSig()); + appendone((var.getSig()).getBytes(), 0, var.getValue()); + } else if (data instanceof Object[]) { + contents = (Object[]) data; + appendone(new byte[] {ArgumentType.SIGNATURE}, 0, contents[0]); + appendone(((String) contents[0]).getBytes(), 0, contents[1]); + } else { + String sig = Marshalling.getDBusType(data.getClass())[0]; + appendone(new byte[] {ArgumentType.SIGNATURE}, 0, sig); + appendone((sig).getBytes(), 0, data); + } + break; + } + return i; + } catch (ClassCastException CCe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, CCe); + throw new MarshallingException(MessageFormat.format($_("Trying to marshall to unconvertable type (from {0} to {1})."), new Object[] { data.getClass().getName(), sigb[sigofs] })); + } + } + /** + * Pad the message to the proper alignment for the given type. + */ + public void pad(byte type) + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "padding for "+(char)type); + int a = getAlignment(type); + if (Debug.debug) Debug.print(Debug.VERBOSE, preallocated+" "+paofs+" "+bytecounter+" "+a); + int b = (int) ((bytecounter-preallocated)%a); + if (0 == b) return; + a = (a-b); + if (preallocated > 0) { + paofs += a; + preallocated -= a; + } else + appendBytes(padding[a]); + if (Debug.debug) Debug.print(Debug.VERBOSE, preallocated+" "+paofs+" "+bytecounter+" "+a); + } + /** + * Return the alignment for a given type. + */ + public static int getAlignment(byte type) + { + switch (type) { + case 2: + case ArgumentType.INT16: + case ArgumentType.UINT16: + return 2; + case 4: + case ArgumentType.BOOLEAN: + case ArgumentType.FLOAT: + case ArgumentType.INT32: + case ArgumentType.UINT32: + case ArgumentType.STRING: + case ArgumentType.OBJECT_PATH: + case ArgumentType.ARRAY: + return 4; + case 8: + case ArgumentType.INT64: + case ArgumentType.UINT64: + case ArgumentType.DOUBLE: + case ArgumentType.STRUCT: + case ArgumentType.DICT_ENTRY: + case ArgumentType.STRUCT1: + case ArgumentType.DICT_ENTRY1: + case ArgumentType.STRUCT2: + case ArgumentType.DICT_ENTRY2: + return 8; + case 1: + case ArgumentType.BYTE: + case ArgumentType.SIGNATURE: + case ArgumentType.VARIANT: + default: + return 1; + } + } + /** + * Append a series of values to the message. + * @param sig The signature(s) of the value(s). + * @param data The value(s). + */ + public void append(String sig, Object... data) throws DBusException + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Appending sig: "+sig+" data: "+Arrays.deepToString(data)); + byte[] sigb = sig.getBytes(); + int j = 0; + for (int i = 0; i < sigb.length; i++) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending item: "+i+" "+((char)sigb[i])+" "+j); + i = appendone(sigb, i, data[j++]); + } + } + /** + * Align a counter to the given type. + * @param current The current counter. + * @param type The type to align to. + * @return The new, aligned, counter. + */ + public int align(int current, byte type) + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "aligning to "+(char)type); + int a = getAlignment(type); + if (0 == (current%a)) return current; + return current+(a-(current%a)); + } + /** + * Demarshall one value from a buffer. + * @param sigb A buffer of the D-Bus signature. + * @param buf The buffer to demarshall from. + * @param ofs An array of two ints, the offset into the signature buffer + * and the offset into the data buffer. These values will be + * updated to the start of the next value ofter demarshalling. + * @param contained converts nested arrays to Lists + * @return The demarshalled value. + */ + private Object extractone(byte[] sigb, byte[] buf, int[] ofs, boolean contained) throws DBusException + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Extracting type: "+((char)sigb[ofs[0]])+" from offset "+ofs[1]); + Object rv = null; + ofs[1] = align(ofs[1], sigb[ofs[0]]); + switch (sigb[ofs[0]]) { + case ArgumentType.BYTE: + rv = buf[ofs[1]++]; + break; + case ArgumentType.UINT32: + rv = new UInt32(demarshallint(buf, ofs[1], 4)); + ofs[1] += 4; + break; + case ArgumentType.INT32: + rv = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + break; + case ArgumentType.INT16: + rv = (short) demarshallint(buf, ofs[1], 2); + ofs[1] += 2; + break; + case ArgumentType.UINT16: + rv = new UInt16((int) demarshallint(buf, ofs[1], 2)); + ofs[1] += 2; + break; + case ArgumentType.INT64: + rv = demarshallint(buf, ofs[1], 8); + ofs[1] += 8; + break; + case ArgumentType.UINT64: + long top; + long bottom; + if (big) { + top = demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + bottom = demarshallint(buf, ofs[1], 4); + } else { + bottom = demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + top = demarshallint(buf, ofs[1], 4); + } + rv = new UInt64(top, bottom); + ofs[1] += 4; + break; + case ArgumentType.DOUBLE: + long l = demarshallint(buf, ofs[1], 8); + ofs[1] += 8; + rv = Double.longBitsToDouble(l); + break; + case ArgumentType.FLOAT: + int rf = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + rv = Float.intBitsToFloat(rf); + break; + case ArgumentType.BOOLEAN: + rf = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + rv = (1==rf)?Boolean.TRUE:Boolean.FALSE; + break; + case ArgumentType.ARRAY: + long size = demarshallint(buf, ofs[1], 4); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Reading array of size: "+size); + ofs[1] += 4; + byte algn = (byte) getAlignment(sigb[++ofs[0]]); + ofs[1] = align(ofs[1], sigb[ofs[0]]); + int length = (int) (size / algn); + if (length > DBusConnection.MAX_ARRAY_LENGTH) + throw new MarshallingException($_("Arrays must not exceed ")+DBusConnection.MAX_ARRAY_LENGTH); + // optimise primatives + switch (sigb[ofs[0]]) { + case ArgumentType.BYTE: + rv = new byte[length]; + System.arraycopy(buf, ofs[1], rv, 0, length); + ofs[1] += size; + break; + case ArgumentType.INT16: + rv = new short[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((short[]) rv)[j] = (short) demarshallint(buf, ofs[1], algn); + break; + case ArgumentType.INT32: + rv = new int[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((int[]) rv)[j] = (int) demarshallint(buf, ofs[1], algn); + break; + case ArgumentType.INT64: + rv = new long[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((long[]) rv)[j] = demarshallint(buf, ofs[1], algn); + break; + case ArgumentType.BOOLEAN: + rv = new boolean[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((boolean[]) rv)[j] = (1 == demarshallint(buf, ofs[1], algn)); + break; + case ArgumentType.FLOAT: + rv = new float[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((float[]) rv)[j] = + Float.intBitsToFloat((int)demarshallint(buf, ofs[1], algn)); + break; + case ArgumentType.DOUBLE: + rv = new double[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((double[]) rv)[j] = + Double.longBitsToDouble(demarshallint(buf, ofs[1], algn)); + break; + case ArgumentType.DICT_ENTRY1: + if (0 == size) { + // advance the type parser even on 0-size arrays. + Vector temp = new Vector(); + byte[] temp2 = new byte[sigb.length-ofs[0]]; + System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length); + String temp3 = new String(temp2); + // ofs[0] gets incremented anyway. Leave one character on the stack + int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1; + ofs[0] += temp4; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Aligned type: "+temp3+" "+temp4+" "+ofs[0]); + } + int ofssave = ofs[0]; + long end = ofs[1]+size; + Vector entries = new Vector(); + while (ofs[1] < end) { + ofs[0] = ofssave; + entries.add((Object[]) extractone(sigb, buf, ofs, true)); + } + rv = new DBusMap(entries.toArray(new Object[0][])); + break; + default: + if (0 == size) { + // advance the type parser even on 0-size arrays. + Vector temp = new Vector(); + byte[] temp2 = new byte[sigb.length-ofs[0]]; + System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length); + String temp3 = new String(temp2); + // ofs[0] gets incremented anyway. Leave one character on the stack + int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1; + ofs[0] += temp4; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Aligned type: "+temp3+" "+temp4+" "+ofs[0]); + } + ofssave = ofs[0]; + end = ofs[1]+size; + Vector contents = new Vector(); + while (ofs[1] < end) { + ofs[0] = ofssave; + contents.add(extractone(sigb, buf, ofs, true)); + } + rv = contents; + } + if (contained && !(rv instanceof List) && !(rv instanceof Map)) + rv = ArrayFrob.listify(rv); + break; + case ArgumentType.STRUCT1: + Vector contents = new Vector(); + while (sigb[++ofs[0]] != ArgumentType.STRUCT2) + contents.add(extractone(sigb, buf, ofs, true)); + rv = contents.toArray(); + break; + case ArgumentType.DICT_ENTRY1: + Object[] decontents = new Object[2]; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Extracting Dict Entry ("+Hexdump.toAscii(sigb,ofs[0],sigb.length-ofs[0])+") from: "+Hexdump.toHex(buf,ofs[1],buf.length-ofs[1])); + ofs[0]++; + decontents[0] = extractone(sigb, buf, ofs, true); + ofs[0]++; + decontents[1] = extractone(sigb, buf, ofs, true); + ofs[0]++; + rv = decontents; + break; + case ArgumentType.VARIANT: + int[] newofs = new int[] { 0, ofs[1] }; + String sig = (String) extract(ArgumentType.SIGNATURE_STRING, buf, newofs)[0]; + newofs[0] = 0; + rv = new Variant(extract(sig, buf, newofs)[0] , sig); + ofs[1] = newofs[1]; + break; + case ArgumentType.STRING: + length = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + try { + rv = new String(buf, ofs[1], length, "UTF-8"); + } catch (UnsupportedEncodingException UEe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(UEe); + throw new DBusException($_("System does not support UTF-8 encoding")); + } + ofs[1] += length + 1; + break; + case ArgumentType.OBJECT_PATH: + length = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + rv = new ObjectPath(getSource(), new String(buf, ofs[1], length)); + ofs[1] += length + 1; + break; + case ArgumentType.SIGNATURE: + length = (buf[ofs[1]++] & 0xFF); + rv = new String(buf, ofs[1], length); + ofs[1] += length + 1; + break; + default: + throw new UnknownTypeCodeException(sigb[ofs[0]]); + } + if (Debug.debug) if (rv instanceof Object[]) + Debug.print(Debug.VERBOSE, "Extracted: "+Arrays.deepToString((Object[]) rv)+" (now at "+ofs[1]+")"); + else + Debug.print(Debug.VERBOSE, "Extracted: "+rv+" (now at "+ofs[1]+")"); + return rv; + } + /** + * Demarshall values from a buffer. + * @param sig The D-Bus signature(s) of the value(s). + * @param buf The buffer to demarshall from. + * @param ofs The offset into the data buffer to start. + * @return The demarshalled value(s). + */ + public Object[] extract(String sig, byte[] buf, int ofs) throws DBusException + { + return extract(sig, buf, new int[] { 0, ofs }); + } + /** + * Demarshall values from a buffer. + * @param sig The D-Bus signature(s) of the value(s). + * @param buf The buffer to demarshall from. + * @param ofs An array of two ints, the offset into the signature + * and the offset into the data buffer. These values will be + * updated to the start of the next value ofter demarshalling. + * @return The demarshalled value(s). + */ + public Object[] extract(String sig, byte[] buf, int[] ofs) throws DBusException + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "extract("+sig+",#"+buf.length+", {"+ofs[0]+","+ofs[1]+"}"); + Vector rv = new Vector(); + byte[] sigb = sig.getBytes(); + for (int[] i = ofs; i[0] < sigb.length; i[0]++) { + rv.add(extractone(sigb, buf, i, false)); + } + return rv.toArray(); + } + /** + * Returns the Bus ID that sent the message. + */ + public String getSource() { return (String) headers.get(HeaderField.SENDER); } + /** + * Returns the destination of the message. + */ + public String getDestination() { return (String) headers.get(HeaderField.DESTINATION); } + /** + * Returns the interface of the message. + */ + public String getInterface() { return (String) headers.get(HeaderField.INTERFACE); } + /** + * Returns the object path of the message. + */ + public String getPath() + { + Object o = headers.get(HeaderField.PATH); + if (null == o) return null; + return o.toString(); + } + /** + * Returns the member name or error name this message represents. + */ + public String getName() + { + if (this instanceof Error) + return (String) headers.get(HeaderField.ERROR_NAME); + else + return (String) headers.get(HeaderField.MEMBER); + } + /** + * Returns the dbus signature of the parameters. + */ + public String getSig() { return (String) headers.get(HeaderField.SIGNATURE); } + /** + * Returns the message flags. + */ + public int getFlags() { return flags; } + /** + * Returns the message serial ID (unique for this connection) + * @return the message serial. + */ + public long getSerial() { return serial; } + /** + * If this is a reply to a message, this returns its serial. + * @return The reply serial, or 0 if it is not a reply. + */ + public long getReplySerial() + { + Number l = (Number) headers.get(HeaderField.REPLY_SERIAL); + if (null == l) return 0; + return l.longValue(); + } + /** + * Parses and returns the parameters to this message as an Object array. + */ + public Object[] getParameters() throws DBusException + { + if (null == args && null != body) { + String sig = (String) headers.get(HeaderField.SIGNATURE); + if (null != sig && 0 != body.length) { + args = extract(sig, body, 0); + } else args = new Object[0]; + } + return args; + } + protected void setArgs(Object[] args) { this.args = args; } + /** + * Warning, do not use this method unless you really know what you are doing. + */ + public void setSource(String source) throws DBusException + { + if (null != body) { + wiredata = new byte[BUFFERINCREMENT][]; + bufferuse = 0; + bytecounter = 0; + preallocate(12); + append("yyyyuu", big ? Endian.BIG : Endian.LITTLE, type, flags, protover, bodylen, serial); + headers.put(HeaderField.SENDER, source); + Object[][] newhead = new Object[headers.size()][]; + int i = 0; + for (Byte b: headers.keySet()) { + newhead[i] = new Object[2]; + newhead[i][0] = b; + newhead[i][1] = headers.get(b); + i++; + } + append("a(yv)", (Object) newhead); + pad((byte) 8); + appendBytes(body); + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/MessageReader.java b/app/src/main/java/org/freedesktop/dbus/MessageReader.java new file mode 100644 index 00000000..6ce7090a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/MessageReader.java @@ -0,0 +1,175 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.io.BufferedInputStream; +import java.io.EOFException; +import java.io.InputStream; +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.text.MessageFormat; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.utils.Hexdump; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.MessageTypeException; +import org.freedesktop.dbus.exceptions.MessageProtocolVersionException; + +public class MessageReader +{ + private InputStream in; + private byte[] buf = null; + private byte[] tbuf = null; + private byte[] header = null; + private byte[] body = null; + private int[] len = new int[4]; + public MessageReader(InputStream in) + { + this.in = new BufferedInputStream(in); + } + public Message readMessage() throws IOException, DBusException + { + int rv; + /* Read the 12 byte fixed header, retrying as neccessary */ + if (null == buf) { buf = new byte[12]; len[0] = 0; } + if (len[0] < 12) { + try { rv = in.read(buf, len[0], 12-len[0]); } + catch (SocketTimeoutException STe) { return null; } + if (-1 == rv) throw new EOFException($_("Underlying transport returned EOF")); + len[0] += rv; + } + if (len[0] == 0) return null; + if (len[0] < 12) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Only got "+len[0]+" of 12 bytes of header"); + return null; + } + + /* Parse the details from the header */ + byte endian = buf[0]; + byte type = buf[1]; + byte protover = buf[3]; + if (protover > Message.PROTOCOL) { + buf = null; + throw new MessageProtocolVersionException(MessageFormat.format($_("Protocol version {0} is unsupported"), new Object[] { protover })); + } + + /* Read the length of the variable header */ + if (null == tbuf) { tbuf = new byte[4]; len[1] = 0; } + if (len[1] < 4) { + try { rv = in.read(tbuf, len[1], 4-len[1]); } + catch (SocketTimeoutException STe) { return null; } + if (-1 == rv) throw new EOFException($_("Underlying transport returned EOF")); + len[1] += rv; + } + if (len[1] < 4) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Only got "+len[1]+" of 4 bytes of header"); + return null; + } + + /* Parse the variable header length */ + int headerlen = 0; + if (null == header) { + headerlen = (int) Message.demarshallint(tbuf, 0, endian, 4); + if (0 != headerlen % 8) + headerlen += 8-(headerlen%8); + } else + headerlen = header.length-8; + + /* Read the variable header */ + if (null == header) { + header = new byte[headerlen+8]; + System.arraycopy(tbuf, 0, header, 0, 4); + len[2] = 0; + } + if (len[2] < headerlen) { + try { rv = in.read(header, 8+len[2], headerlen-len[2]); } + catch (SocketTimeoutException STe) { return null; } + if (-1 == rv) throw new EOFException($_("Underlying transport returned EOF")); + len[2] += rv; + } + if (len[2] < headerlen) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Only got "+len[2]+" of "+headerlen+" bytes of header"); + return null; + } + + /* Read the body */ + int bodylen = 0; + if (null == body) bodylen = (int) Message.demarshallint(buf, 4, endian, 4); + if (null == body) { body=new byte[bodylen]; len[3] = 0; } + if (len[3] < body.length) { + try { rv = in.read(body, len[3], body.length-len[3]); } + catch (SocketTimeoutException STe) { return null; } + if (-1 == rv) throw new EOFException($_("Underlying transport returned EOF")); + len[3] += rv; + } + if (len[3] < body.length) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Only got "+len[3]+" of "+body.length+" bytes of body"); + return null; + } + + Message m; + switch (type) { + case Message.MessageType.METHOD_CALL: + m = new MethodCall(); + break; + case Message.MessageType.METHOD_RETURN: + m = new MethodReturn(); + break; + case Message.MessageType.SIGNAL: + m = new DBusSignal(); + break; + case Message.MessageType.ERROR: + m = new Error(); + break; + default: + throw new MessageTypeException(MessageFormat.format($_("Message type {0} unsupported"), new Object[] {type})); + } + if (Debug.debug) { + Debug.print(Debug.VERBOSE, Hexdump.format(buf)); + Debug.print(Debug.VERBOSE, Hexdump.format(tbuf)); + Debug.print(Debug.VERBOSE, Hexdump.format(header)); + Debug.print(Debug.VERBOSE, Hexdump.format(body)); + } + try { + m.populate(buf, header, body); + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + buf = null; + tbuf = null; + body = null; + header = null; + throw DBe; + } catch (RuntimeException Re) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Re); + buf = null; + tbuf = null; + body = null; + header = null; + throw Re; + } + if (Debug.debug) { + Debug.print(Debug.INFO, "=> "+m); + } + buf = null; + tbuf = null; + body = null; + header = null; + return m; + } + public void close() throws IOException + { + if (Debug.debug) Debug.print(Debug.INFO, "Closing Message Reader"); + in.close(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/MessageWriter.java b/app/src/main/java/org/freedesktop/dbus/MessageWriter.java new file mode 100644 index 00000000..e95bf78a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/MessageWriter.java @@ -0,0 +1,68 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.io.BufferedOutputStream; +import java.io.OutputStream; +import java.io.IOException; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.unix.USOutputStream; +import cx.ath.matthew.utils.Hexdump; + +public class MessageWriter +{ + private OutputStream out; + private boolean isunix; + public MessageWriter(OutputStream out) + { + this.out = out; + this.isunix = false; + try { + if (out instanceof USOutputStream) + this.isunix = true; + } catch (Throwable t) { + } + if (!this.isunix) + this.out = new BufferedOutputStream(this.out); + } + public void writeMessage(Message m) throws IOException + { + if (Debug.debug) { + Debug.print(Debug.INFO, "<= "+m); + } + if (null == m) return; + if (null == m.getWireData()) { + if (Debug.debug) Debug.print(Debug.WARN, "Message "+m+" wire-data was null!"); + return; + } + if (isunix) { + if (Debug.debug) { + Debug.print(Debug.DEBUG, "Writing all "+m.getWireData().length+" buffers simultaneously to Unix Socket"); + for (byte[] buf: m.getWireData()) + Debug.print(Debug.VERBOSE, "("+buf+"):"+ (null==buf? "": Hexdump.format(buf))); + } + ((USOutputStream) out).write(m.getWireData()); + } else + for (byte[] buf: m.getWireData()) { + if (Debug.debug) + Debug.print(Debug.VERBOSE, "("+buf+"):"+ (null==buf? "": Hexdump.format(buf))); + if (null == buf) break; + out.write(buf); + } + out.flush(); + } + public void close() throws IOException + { + if (Debug.debug) Debug.print(Debug.INFO, "Closing Message Writer"); + out.close(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/MethodCall.java b/app/src/main/java/org/freedesktop/dbus/MethodCall.java new file mode 100644 index 00000000..41b688f7 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/MethodCall.java @@ -0,0 +1,126 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.util.Vector; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.MessageFormatException; +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.utils.Hexdump; + +public class MethodCall extends Message +{ + MethodCall() { } + public MethodCall(String dest, String path, String iface, String member, byte flags, String sig, Object... args) throws DBusException + { + this(null, dest, path, iface, member, flags, sig, args); + } + public MethodCall(String source, String dest, String path, String iface, String member, byte flags, String sig, Object... args) throws DBusException + { + super(Message.Endian.BIG, Message.MessageType.METHOD_CALL, flags); + + if (null == member || null == path) + throw new MessageFormatException($_("Must specify destination, path and function name to MethodCalls.")); + headers.put(Message.HeaderField.PATH,path); + headers.put(Message.HeaderField.MEMBER,member); + + Vector hargs = new Vector(); + + hargs.add(new Object[] { Message.HeaderField.PATH, new Object[] { ArgumentType.OBJECT_PATH_STRING, path } }); + + if (null != source) { + headers.put(Message.HeaderField.SENDER,source); + hargs.add(new Object[] { Message.HeaderField.SENDER, new Object[] { ArgumentType.STRING_STRING, source } }); + } + + if (null != dest) { + headers.put(Message.HeaderField.DESTINATION,dest); + hargs.add(new Object[] { Message.HeaderField.DESTINATION, new Object[] { ArgumentType.STRING_STRING, dest } }); + } + + if (null != iface) { + hargs.add(new Object[] { Message.HeaderField.INTERFACE, new Object[] { ArgumentType.STRING_STRING, iface } }); + headers.put(Message.HeaderField.INTERFACE,iface); + } + + hargs.add(new Object[] { Message.HeaderField.MEMBER, new Object[] { ArgumentType. STRING_STRING, member } }); + + if (null != sig) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Appending arguments with signature: "+sig); + hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); + headers.put(Message.HeaderField.SIGNATURE,sig); + setArgs(args); + } + + byte[] blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", serial, hargs.toArray()); + pad((byte)8); + + long c = bytecounter; + if (null != sig) append(sig, args); + if (Debug.debug) Debug.print(Debug.DEBUG, "Appended body, type: "+sig+" start: "+c+" end: "+bytecounter+" size: "+(bytecounter-c)); + marshallint(bytecounter-c, blen, 0, 4); + if (Debug.debug) Debug.print("marshalled size ("+blen+"): "+Hexdump.format(blen)); + } + private static long REPLY_WAIT_TIMEOUT = 20000; + /** + * Set the default timeout for method calls. + * Default is 20s. + * @param timeout New timeout in ms. + */ + public static void setDefaultTimeout(long timeout) + { + REPLY_WAIT_TIMEOUT = timeout; + } + Message reply = null; + public synchronized boolean hasReply() + { + return null != reply; + } + /** + * Block (if neccessary) for a reply. + * @return The reply to this MethodCall, or null if a timeout happens. + * @param timeout The length of time to block before timing out (ms). + */ + public synchronized Message getReply(long timeout) + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking on "+this); + if (null != reply) return reply; + try { + wait(timeout); + return reply; + } catch (InterruptedException Ie) { return reply; } + } + /** + * Block (if neccessary) for a reply. + * Default timeout is 20s, or can be configured with setDefaultTimeout() + * @return The reply to this MethodCall, or null if a timeout happens. + */ + public synchronized Message getReply() + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking on "+this); + if (null != reply) return reply; + try { + wait(REPLY_WAIT_TIMEOUT); + return reply; + } catch (InterruptedException Ie) { return reply; } + } + protected synchronized void setReply(Message reply) + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting reply to "+this+" to "+reply); + this.reply = reply; + notifyAll(); + } + +} diff --git a/app/src/main/java/org/freedesktop/dbus/MethodReturn.java b/app/src/main/java/org/freedesktop/dbus/MethodReturn.java new file mode 100644 index 00000000..b0f719ae --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/MethodReturn.java @@ -0,0 +1,69 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.util.Vector; +import org.freedesktop.dbus.exceptions.DBusException; + +public class MethodReturn extends Message +{ + MethodReturn() { } + public MethodReturn(String dest, long replyserial, String sig, Object... args) throws DBusException + { + this(null, dest, replyserial, sig, args); + } + public MethodReturn(String source, String dest, long replyserial, String sig, Object... args) throws DBusException + { + super(Message.Endian.BIG, Message.MessageType.METHOD_RETURN, (byte) 0); + + headers.put(Message.HeaderField.REPLY_SERIAL,replyserial); + + Vector hargs = new Vector(); + hargs.add(new Object[] { Message.HeaderField.REPLY_SERIAL, new Object[] { ArgumentType.UINT32_STRING, replyserial } }); + + if (null != source) { + headers.put(Message.HeaderField.SENDER,source); + hargs.add(new Object[] { Message.HeaderField.SENDER, new Object[] { ArgumentType.STRING_STRING, source } }); + } + + if (null != dest) { + headers.put(Message.HeaderField.DESTINATION,dest); + hargs.add(new Object[] { Message.HeaderField.DESTINATION, new Object[] { ArgumentType.STRING_STRING, dest } }); + } + + if (null != sig) { + hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); + headers.put(Message.HeaderField.SIGNATURE,sig); + setArgs(args); + } + + byte[] blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", serial, hargs.toArray()); + pad((byte)8); + + long c = bytecounter; + if (null != sig) append(sig, args); + marshallint(bytecounter-c, blen, 0, 4); + } + public MethodReturn(MethodCall mc, String sig, Object... args) throws DBusException + { + this(null, mc, sig, args); + } + public MethodReturn(String source, MethodCall mc, String sig, Object... args) throws DBusException + { + this(source, mc.getSource(), mc.getSerial(), sig, args); + this.call = mc; + } + MethodCall call; + public MethodCall getCall() { return call; } + protected void setCall(MethodCall call) { this.call = call; } +} diff --git a/app/src/main/java/org/freedesktop/dbus/MethodTuple.java b/app/src/main/java/org/freedesktop/dbus/MethodTuple.java new file mode 100644 index 00000000..dd2c4a98 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/MethodTuple.java @@ -0,0 +1,38 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; + +class MethodTuple +{ + String name; + String sig; + public MethodTuple(String name, String sig) + { + this.name = name; + if (null != sig) + this.sig = sig; + else + this.sig = ""; + if (Debug.debug) Debug.print(Debug.VERBOSE, "new MethodTuple("+this.name+", "+this.sig+")"); + } + public boolean equals(Object o) + { + return o.getClass().equals(MethodTuple.class) + && ((MethodTuple) o).name.equals(this.name) + && ((MethodTuple) o).sig.equals(this.sig); + } + public int hashCode() + { + return name.hashCode()+sig.hashCode(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/ObjectPath.java b/app/src/main/java/org/freedesktop/dbus/ObjectPath.java new file mode 100644 index 00000000..409d26da --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/ObjectPath.java @@ -0,0 +1,23 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +class ObjectPath extends Path +{ + public String source; +// public DBusConnection conn; + public ObjectPath(String source, String path/*, DBusConnection conn*/) + { + super(path); + this.source = source; + // this.conn = conn; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/ObjectTree.java b/app/src/main/java/org/freedesktop/dbus/ObjectTree.java new file mode 100644 index 00000000..f7d05916 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/ObjectTree.java @@ -0,0 +1,163 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; + +import java.util.regex.Pattern; + +/** + * Keeps track of the exported objects for introspection data */ +class ObjectTree +{ + class TreeNode + { + String name; + ExportedObject object; + String data; + TreeNode right; + TreeNode down; + public TreeNode(String name) + { + this.name = name; + } + public TreeNode(String name, ExportedObject object, String data) + { + this.name = name; + this.object = object; + this.data = data; + } + } + private TreeNode root; + public ObjectTree() + { + root = new TreeNode(""); + } + public static final Pattern slashpattern = Pattern.compile("/"); + + private TreeNode recursiveFind(TreeNode current, String path) + { + if ("/".equals(path)) return current; + String[] elements = path.split("/", 2); + // this is us or a parent node + if (path.startsWith(current.name)) { + // this is us + if (path.equals(current.name)) { + return current; + } + // recurse down + else { + if (current.down == null) + return null; + else return recursiveFind(current.down, elements[1]); + } + } + else if (current.right == null) { + return null; + } + else if (0 > current.right.name.compareTo(elements[0])) { + return null; + } + // recurse right + else { + return recursiveFind(current.right, path); + } + } + private TreeNode recursiveAdd(TreeNode current, String path, ExportedObject object, String data) + { + String[] elements = slashpattern.split(path, 2); + // this is us or a parent node + if (path.startsWith(current.name)) { + // this is us + if (1 == elements.length || "".equals(elements[1])) { + current.object = object; + current.data = data; + } + // recurse down + else { + if (current.down == null) { + String[] el = elements[1].split("/", 2); + current.down = new TreeNode(el[0]); + } + current.down = recursiveAdd(current.down, elements[1], object, data); + } + } + // need to create a new sub-tree on the end + else if (current.right == null) { + current.right = new TreeNode(elements[0]); + current.right = recursiveAdd(current.right, path, object, data); + } + // need to insert here + else if (0 > current.right.name.compareTo(elements[0])) { + TreeNode t = new TreeNode(elements[0]); + t.right = current.right; + current.right = t; + current.right = recursiveAdd(current.right, path, object, data); + } + // recurse right + else { + current.right = recursiveAdd(current.right, path, object, data); + } + return current; + } + public void add(String path, ExportedObject object, String data) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Adding "+path+" to object tree"); + root = recursiveAdd(root, path, object, data); + } + public void remove(String path) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Removing "+path+" from object tree"); + TreeNode t = recursiveFind(root, path); + t.object = null; + t.data = null; + } + + public String Introspect(String path) + { + TreeNode t = recursiveFind(root, path); + if (null == t) return null; + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + if (null != t.data) sb.append(t.data); + t = t.down; + while (null != t) { + sb.append("\n"); + t = t.right; + } + sb.append(""); + return sb.toString(); + } + + private String recursivePrint(TreeNode current) + { + String s = ""; + if (null != current) { + s += current.name; + if (null != current.object) + s += "*"; + if (null != current.down) + s += "/{"+recursivePrint(current.down)+"}"; + if (null != current.right) + s += ", "+recursivePrint(current.right); + } + return s; + } + + public String toString() + { + return recursivePrint(root); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/Path.java b/app/src/main/java/org/freedesktop/dbus/Path.java new file mode 100644 index 00000000..baaa7a62 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Path.java @@ -0,0 +1,40 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +public class Path implements Comparable +{ + protected String path; + public Path(String path) + { + this.path = path; + } + public String getPath() + { + return path; + } + public String toString() + { + return path; + } + public boolean equals(Object other) + { + return (other instanceof Path) && path.equals(((Path) other).path); + } + public int hashCode() + { + return path.hashCode(); + } + public int compareTo(Path that) + { + return path.compareTo(that.path); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/Position.java b/app/src/main/java/org/freedesktop/dbus/Position.java new file mode 100644 index 00000000..45e68d88 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Position.java @@ -0,0 +1,28 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Position annotation, to annotate Struct fields + * to be sent over DBus. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Position +{ + /** The order of this field in the Struct. */ + int value(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java b/app/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java new file mode 100644 index 00000000..e0302999 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java @@ -0,0 +1,191 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.text.MessageFormat; +import java.util.Arrays; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import cx.ath.matthew.debug.Debug; + +class RemoteInvocationHandler implements InvocationHandler +{ + public static final int CALL_TYPE_SYNC = 0; + public static final int CALL_TYPE_ASYNC = 1; + public static final int CALL_TYPE_CALLBACK = 2; + public static Object convertRV(String sig, Object[] rp, Method m, AbstractConnection conn) throws DBusException + { + Class c = m.getReturnType(); + + if (null == rp) { + if(null == c || Void.TYPE.equals(c)) return null; + else throw new DBusExecutionException($_("Wrong return type (got void, expected a value)")); + } else { + try { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Converting return parameters from "+Arrays.deepToString(rp)+" to type "+m.getGenericReturnType()); + rp = Marshalling.deSerializeParameters(rp, + new Type[] { m.getGenericReturnType() }, conn); + } + catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusExecutionException(MessageFormat.format($_("Wrong return type (failed to de-serialize correct types: {0} )"), new Object[] { e.getMessage() })); + } + } + + switch (rp.length) { + case 0: + if (null == c || Void.TYPE.equals(c)) + return null; + else throw new DBusExecutionException($_("Wrong return type (got void, expected a value)")); + case 1: + return rp[0]; + default: + + // check we are meant to return multiple values + if (!Tuple.class.isAssignableFrom(c)) + throw new DBusExecutionException($_("Wrong return type (not expecting Tuple)")); + + Constructor cons = c.getConstructors()[0]; + try { + return cons.newInstance(rp); + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException(e.getMessage()); + } + } + } + @SuppressWarnings("unchecked") + public static Object executeRemoteMethod(RemoteObject ro, Method m, AbstractConnection conn, int syncmethod, CallbackHandler callback, Object... args) throws DBusExecutionException + { + Type[] ts = m.getGenericParameterTypes(); + String sig = null; + if (ts.length > 0) try { + sig = Marshalling.getDBusType(ts); + args = Marshalling.convertParameters(args, ts, conn); + } catch (DBusException DBe) { + throw new DBusExecutionException($_("Failed to construct D-Bus type: ")+DBe.getMessage()); + } + MethodCall call; + byte flags = 0; + if (!ro.autostart) flags |= Message.Flags.NO_AUTO_START; + if (syncmethod == CALL_TYPE_ASYNC) flags |= Message.Flags.ASYNC; + if (m.isAnnotationPresent(DBus.Method.NoReply.class)) flags |= Message.Flags.NO_REPLY_EXPECTED; + try { + String name; + if (m.isAnnotationPresent(DBusMemberName.class)) + name = m.getAnnotation(DBusMemberName.class).value(); + else + name = m.getName(); + if (null == ro.iface) + call = new MethodCall(ro.busname, ro.objectpath, null, name,flags, sig, args); + else { + if (null != ro.iface.getAnnotation(DBusInterfaceName.class)) { + call = new MethodCall(ro.busname, ro.objectpath, ro.iface.getAnnotation(DBusInterfaceName.class).value(), name, flags, sig, args); + } else + call = new MethodCall(ro.busname, ro.objectpath, AbstractConnection.dollar_pattern.matcher(ro.iface.getName()).replaceAll("."), name, flags, sig, args); + } + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + throw new DBusExecutionException($_("Failed to construct outgoing method call: ")+DBe.getMessage()); + } + if (null == conn.outgoing) throw new NotConnected($_("Not Connected")); + + switch (syncmethod) { + case CALL_TYPE_ASYNC: + conn.queueOutgoing(call); + return new DBusAsyncReply(call, m, conn); + case CALL_TYPE_CALLBACK: + synchronized (conn.pendingCallbacks) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Queueing Callback "+callback+" for "+call); + conn.pendingCallbacks.put(call, callback); + conn.pendingCallbackReplys.put(call, new DBusAsyncReply(call, m, conn)); + } + conn.queueOutgoing(call); + return null; + case CALL_TYPE_SYNC: + conn.queueOutgoing(call); + break; + } + + // get reply + if (m.isAnnotationPresent(DBus.Method.NoReply.class)) return null; + + Message reply = call.getReply(); + if (null == reply) throw new DBus.Error.NoReply($_("No reply within specified time")); + + if (reply instanceof Error) + ((Error) reply).throwException(); + + try { + return convertRV(reply.getSig(), reply.getParameters(), m, conn); + } catch (DBusException e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusExecutionException(e.getMessage()); + } + } + + AbstractConnection conn; + RemoteObject remote; + public RemoteInvocationHandler(AbstractConnection conn, RemoteObject remote) + { + this.remote = remote; + this.conn = conn; + } + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + if (method.getName().equals("isRemote")) return true; + else if (method.getName().equals("clone")) return null; + else if (method.getName().equals("equals")) { + try { + if (1 == args.length) + return new Boolean(remote.equals(((RemoteInvocationHandler) Proxy.getInvocationHandler(args[0])).remote)); + } catch (IllegalArgumentException IAe) { + return Boolean.FALSE; + } + } + else if (method.getName().equals("finalize")) return null; + else if (method.getName().equals("getClass")) return DBusInterface.class; + else if (method.getName().equals("hashCode")) return remote.hashCode(); + else if (method.getName().equals("notify")) { + remote.notify(); + return null; + } else if (method.getName().equals("notifyAll")) { + remote.notifyAll(); + return null; + } else if (method.getName().equals("wait")) { + if (0 == args.length) remote.wait(); + else if (1 == args.length + && args[0] instanceof Long) remote.wait((Long) args[0]); + else if (2 == args.length + && args[0] instanceof Long + && args[1] instanceof Integer) + remote.wait((Long) args[0], (Integer) args[1]); + if (args.length <= 2) + return null; + } + else if (method.getName().equals("toString")) + return remote.toString(); + + return executeRemoteMethod(remote, method, conn, CALL_TYPE_SYNC, null, args); + } +} + diff --git a/app/src/main/java/org/freedesktop/dbus/RemoteObject.java b/app/src/main/java/org/freedesktop/dbus/RemoteObject.java new file mode 100644 index 00000000..8a1f620b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/RemoteObject.java @@ -0,0 +1,56 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +class RemoteObject +{ + String busname; + String objectpath; + Class iface; + boolean autostart; + public RemoteObject(String busname, String objectpath, Class iface, boolean autostart) + { + this.busname = busname; + this.objectpath = objectpath; + this.iface = iface; + this.autostart = autostart; + } + public boolean equals(Object o) + { + if (!(o instanceof RemoteObject)) return false; + RemoteObject them = (RemoteObject) o; + + if (!them.objectpath.equals(this.objectpath)) return false; + + if (null == this.busname && null != them.busname) return false; + if (null != this.busname && null == them.busname) return false; + if (null != them.busname && !them.busname.equals(this.busname)) return false; + + if (null == this.iface && null != them.iface) return false; + if (null != this.iface && null == them.iface) return false; + if (null != them.iface && !them.iface.equals(this.iface)) return false; + + return true; + } + public int hashCode() + { + return (null == busname ? 0 : busname.hashCode()) + objectpath.hashCode() + + (null == iface ? 0 : iface.hashCode()); + } + public boolean autoStarting() { return autostart; } + public String getBusName() { return busname; } + public String getObjectPath() { return objectpath; } + public Class getInterface() { return iface; } + public String toString() + { + return busname+":"+objectpath+":"+iface; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/SignalTuple.java b/app/src/main/java/org/freedesktop/dbus/SignalTuple.java new file mode 100644 index 00000000..94cadeed --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/SignalTuple.java @@ -0,0 +1,51 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; +class SignalTuple +{ + String type; + String name; + String object; + String source; + public SignalTuple(String type, String name, String object, String source) + { + this.type = type; + this.name = name; + this.object = object; + this.source = source; + } + public boolean equals(Object o) + { + if (!(o instanceof SignalTuple)) return false; + SignalTuple other = (SignalTuple) o; + if (null == this.type && null != other.type) return false; + if (null != this.type && !this.type.equals(other.type)) return false; + if (null == this.name && null != other.name) return false; + if (null != this.name && !this.name.equals(other.name)) return false; + if (null == this.object && null != other.object) return false; + if (null != this.object && !this.object.equals(other.object)) return false; + if (null == this.source && null != other.source) return false; + if (null != this.source && !this.source.equals(other.source)) return false; + return true; + } + public int hashCode() + { + return (null == type ? 0 : type.hashCode()) + + (null == name ? 0 : name.hashCode()) + + (null == source ? 0 : source.hashCode()) + + (null == object ? 0 : object.hashCode()); + } + public String toString() + { + return "SignalTuple("+type+","+name+","+object+","+source+")"; + } +} + diff --git a/app/src/main/java/org/freedesktop/dbus/StrongReference.java b/app/src/main/java/org/freedesktop/dbus/StrongReference.java new file mode 100644 index 00000000..c4d142dc --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/StrongReference.java @@ -0,0 +1,43 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.lang.ref.WeakReference; + +/** + * An alternative to a WeakReference when you don't want + * that behaviour. + */ +public class StrongReference extends WeakReference +{ + T referant; + public StrongReference(T referant) + { + super(referant); + this.referant = referant; + } + public void clear() + { + referant = null; + } + public boolean enqueue() + { + return false; + } + public T get() + { + return referant; + } + public boolean isEnqueued() + { + return false; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/Struct.java b/app/src/main/java/org/freedesktop/dbus/Struct.java new file mode 100644 index 00000000..7d37ed07 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Struct.java @@ -0,0 +1,23 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +/** + * This class should be extended to create Structs. + * Any such class may be sent over DBus to a method which takes a Struct. + * All fields in the Struct which you wish to be serialized and sent to the + * remote method should be annotated with the org.freedesktop.dbus.Position + * annotation, in the order they should appear in to Struct to DBus. + */ +public abstract class Struct extends Container +{ + public Struct() {} +} diff --git a/app/src/main/java/org/freedesktop/dbus/Transport.java b/app/src/main/java/org/freedesktop/dbus/Transport.java new file mode 100644 index 00000000..a7689a7d --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Transport.java @@ -0,0 +1,827 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.text.ParseException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Random; +import java.util.Vector; +import cx.ath.matthew.unix.UnixSocket; +import cx.ath.matthew.unix.UnixServerSocket; +import cx.ath.matthew.unix.UnixSocketAddress; +import cx.ath.matthew.utils.Hexdump; +import cx.ath.matthew.debug.Debug; + +import android.icu.text.Collator; + +public class Transport +{ + public static class SASL + { + public static class Command + { + private int command; + private int mechs; + private String data; + private String response; + public Command() + { + } + public Command(String s) throws IOException + { + String[] ss = s.split(" "); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Creating command from: "+Arrays.toString(ss)); + if (0 == col.compare(ss[0], "OK")) { + command = COMMAND_OK; + data = ss[1]; + } else if (0 == col.compare(ss[0], "AUTH")) { + command = COMMAND_AUTH; + if (ss.length > 1) { + if (0 == col.compare(ss[1], "EXTERNAL")) + mechs = AUTH_EXTERNAL; + else if (0 == col.compare(ss[1], "DBUS_COOKIE_SHA1")) + mechs = AUTH_SHA; + else if (0 == col.compare(ss[1], "ANONYMOUS")) + mechs = AUTH_ANON; + } + if (ss.length > 2) + data = ss[2]; + } else if (0 == col.compare(ss[0], "DATA")) { + command = COMMAND_DATA; + data = ss[1]; + } else if (0 == col.compare(ss[0], "REJECTED")) { + command = COMMAND_REJECTED; + for (int i = 1; i < ss.length; i++) + if (0 == col.compare(ss[i], "EXTERNAL")) + mechs |= AUTH_EXTERNAL; + else if (0 == col.compare(ss[i], "DBUS_COOKIE_SHA1")) + mechs |= AUTH_SHA; + else if (0 == col.compare(ss[i], "ANONYMOUS")) + mechs |= AUTH_ANON; + } else if (0 == col.compare(ss[0], "BEGIN")) { + command = COMMAND_BEGIN; + } else if (0 == col.compare(ss[0], "CANCEL")) { + command = COMMAND_CANCEL; + } else if (0 == col.compare(ss[0], "ERROR")) { + command = COMMAND_ERROR; + data = ss[1]; + } else { + throw new IOException($_("Invalid Command ")+ss[0]); + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "Created command: "+this); + } + public int getCommand() { return command; } + public int getMechs() { return mechs; } + public String getData() { return data; } + public String getResponse() { return response; } + public void setResponse(String s) { response = s; } + public String toString() + { + return "Command("+command+", "+mechs+", "+data+", "+null+")"; + } + } + private static Collator col = Collator.getInstance(); + static { + col.setStrength(Collator.PRIMARY); + } + public static final int LOCK_TIMEOUT = 1000; + public static final int NEW_KEY_TIMEOUT_SECONDS = 60 * 5; + public static final int EXPIRE_KEYS_TIMEOUT_SECONDS = NEW_KEY_TIMEOUT_SECONDS + (60 * 2); + public static final int MAX_TIME_TRAVEL_SECONDS = 60 * 5; + public static final int COOKIE_TIMEOUT = 240; + public static final String COOKIE_CONTEXT = "org_freedesktop_java"; + + private String findCookie(String context, String ID) throws IOException + { + String homedir = System.getProperty("user.home"); + File f = new File(homedir+"/.dbus-keyrings/"+context); + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f))); + String s = null; + String cookie = null; + long now = System.currentTimeMillis()/1000; + while (null != (s = r.readLine())) { + String[] line = s.split(" "); + long timestamp = Long.parseLong(line[1]); + if (line[0].equals(ID) && (! (timestamp < 0 || + (now + MAX_TIME_TRAVEL_SECONDS) < timestamp || + (now - EXPIRE_KEYS_TIMEOUT_SECONDS) > timestamp))) { + cookie = line[2]; + break; + } + } + r.close(); + return cookie; + } + private void addCookie(String context, String ID, long timestamp, String cookie) throws IOException + { + String homedir = System.getProperty("user.home"); + File keydir = new File(homedir+"/.dbus-keyrings/"); + File cookiefile = new File(homedir+"/.dbus-keyrings/"+context); + File lock = new File(homedir+"/.dbus-keyrings/"+context+".lock"); + File temp = new File(homedir+"/.dbus-keyrings/"+context+".temp"); + + // ensure directory exists + if (!keydir.exists()) keydir.mkdirs(); + + // acquire lock + long start = System.currentTimeMillis(); + while (!lock.createNewFile() && LOCK_TIMEOUT > (System.currentTimeMillis()-start)); + + // read old file + Vector lines = new Vector(); + if (cookiefile.exists()) { + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(cookiefile))); + String s = null; + while (null != (s = r.readLine())) { + String[] line = s.split(" "); + long time = Long.parseLong(line[1]); + // expire stale cookies + if ((timestamp - time) < COOKIE_TIMEOUT) + lines.add(s); + } + r.close(); + } + + // add cookie + lines.add(ID+" "+timestamp+" "+cookie); + + // write temp file + PrintWriter w = new PrintWriter(new FileOutputStream(temp)); + for (String l: lines) + w.println(l); + w.close(); + + // atomically move to old file + if (!temp.renameTo(cookiefile)) { + cookiefile.delete(); + temp.renameTo(cookiefile); + } + + // remove lock + lock.delete(); + } + /** + * Takes the string, encodes it as hex and then turns it into a string again. + * No, I don't know why either. + */ + private String stupidlyEncode(String data) + { + return Hexdump.toHex(data.getBytes()).replaceAll(" ",""); + } + private String stupidlyEncode(byte[] data) + { + return Hexdump.toHex(data).replaceAll(" ",""); + } + private byte getNibble(char c) + { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return (byte) (c-'0'); + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return (byte) (c-'A'+10); + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return (byte) (c-'a'+10); + default: + return 0; + } + } + private String stupidlyDecode(String data) + { + char[] cs = new char[data.length()]; + char[] res = new char[cs.length/2]; + data.getChars(0, data.length(), cs, 0); + for (int i = 0, j = 0; j < res.length; i += 2, j++) { + int b = 0; + b |= getNibble(cs[i])<<4; + b |= getNibble(cs[i+1]); + res[j] = (char) b; + } + return new String(res); + } + + public static final int MODE_SERVER=1; + public static final int MODE_CLIENT=2; + + public static final int AUTH_NONE=0; + public static final int AUTH_EXTERNAL=1; + public static final int AUTH_SHA=2; + public static final int AUTH_ANON=4; + + public static final int COMMAND_AUTH=1; + public static final int COMMAND_DATA=2; + public static final int COMMAND_REJECTED=3; + public static final int COMMAND_OK=4; + public static final int COMMAND_BEGIN=5; + public static final int COMMAND_CANCEL=6; + public static final int COMMAND_ERROR=7; + + public static final int INITIAL_STATE=0; + public static final int WAIT_DATA=1; + public static final int WAIT_OK=2; + public static final int WAIT_REJECT=3; + public static final int WAIT_AUTH=4; + public static final int WAIT_BEGIN=5; + public static final int AUTHENTICATED=6; + public static final int FAILED=7; + + public static final int OK=1; + public static final int CONTINUE=2; + public static final int ERROR=3; + public static final int REJECT=4; + + public Command receive(InputStream s) throws IOException + { + StringBuffer sb = new StringBuffer(); + top: while (true) { + int c = s.read(); + switch (c) { + case -1: + throw new IOException("Stream unexpectedly short (broken pipe)"); + case 0: + case '\r': + continue; + case '\n': + break top; + default: + sb.append((char) c); + } + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "received: "+sb); + try { + return new Command(sb.toString()); + } catch (Exception e) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, e); + return new Command(); + } + } + public void send(OutputStream out, int command, String... data) throws IOException + { + StringBuffer sb = new StringBuffer(); + switch (command) { + case COMMAND_AUTH: + sb.append("AUTH"); + break; + case COMMAND_DATA: + sb.append("DATA"); + break; + case COMMAND_REJECTED: + sb.append("REJECTED"); + break; + case COMMAND_OK: + sb.append("OK"); + break; + case COMMAND_BEGIN: + sb.append("BEGIN"); + break; + case COMMAND_CANCEL: + sb.append("CANCEL"); + break; + case COMMAND_ERROR: + sb.append("ERROR"); + break; + default: + return; + } + for (String s: data) { + sb.append(' '); + sb.append(s); + } + sb.append('\r'); + sb.append('\n'); + if (Debug.debug) Debug.print(Debug.VERBOSE, "sending: "+sb); + out.write(sb.toString().getBytes()); + } + public int do_challenge(int auth, Command c) throws IOException + { + switch (auth) { + case AUTH_SHA: + String[] reply=stupidlyDecode(c.getData()).split(" "); + if (Debug.debug) Debug.print(Debug.VERBOSE, Arrays.toString(reply)); + if (3 != reply.length) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Reply is not length 3"); + return ERROR; + } + String context = reply[0]; + String ID = reply[1]; + String serverchallenge = reply[2]; + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA"); + } catch (NoSuchAlgorithmException NSAe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe); + return ERROR; + } + byte[] buf = new byte[8]; + Message.marshallintBig(System.currentTimeMillis(), buf, 0, 8); + String clientchallenge = stupidlyEncode(md.digest(buf)); + md.reset(); + long start = System.currentTimeMillis(); + String cookie = null; + while (null == cookie && (System.currentTimeMillis()-start) < LOCK_TIMEOUT) + cookie = findCookie(context, ID); + if (null == cookie) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Did not find a cookie in context "+context+" with ID "+ID); + return ERROR; + } + String response = serverchallenge+":"+clientchallenge+":"+cookie; + buf = md.digest(response.getBytes()); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Response: "+response+" hash: "+Hexdump.format(buf)); + response = stupidlyEncode(buf); + c.setResponse(stupidlyEncode(clientchallenge+" "+response)); + return OK; + default: + if (Debug.debug) Debug.print(Debug.DEBUG, "Not DBUS_COOKIE_SHA1 authtype."); + return ERROR; + } + } + public String challenge = ""; + public String cookie = ""; + public int do_response(int auth, String Uid, String kernelUid, Command c) + { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA"); + } catch (NoSuchAlgorithmException NSAe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe); + return ERROR; + } + switch (auth) { + case AUTH_NONE: + switch (c.getMechs()) { + case AUTH_ANON: + return OK; + case AUTH_EXTERNAL: + if (0 == col.compare(Uid, c.getData()) && + (null == kernelUid || 0 == col.compare(Uid, kernelUid))) + return OK; + else + return ERROR; + case AUTH_SHA: + String context = COOKIE_CONTEXT; + long id = System.currentTimeMillis(); + byte[] buf = new byte[8]; + Message.marshallintBig(id, buf, 0, 8); + challenge = stupidlyEncode(md.digest(buf)); + Random r = new Random(); + r.nextBytes(buf); + cookie = stupidlyEncode(md.digest(buf)); + try { addCookie(context, ""+id, id/1000, cookie); + } catch (IOException IOe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "Sending challenge: "+context+' '+id+' '+challenge); + c.setResponse(stupidlyEncode(context+' '+id+' '+challenge)); + return CONTINUE; + default: + return ERROR; + } + case AUTH_SHA: + String[] response = stupidlyDecode(c.getData()).split(" "); + if (response.length < 2) return ERROR; + String cchal = response[0]; + String hash = response[1]; + String prehash = challenge+":"+cchal+":"+cookie; + byte[] buf = md.digest(prehash.getBytes()); + String posthash = stupidlyEncode(buf); + if (Debug.debug) Debug.print(Debug.DEBUG, "Authenticating Hash; data="+prehash+" remote hash="+hash+" local hash="+posthash); + if (0 == col.compare(posthash, hash)) + return OK; + else + return ERROR; + default: + return ERROR; + } + } + public String[] getTypes(int types) + { + switch (types) { + case AUTH_EXTERNAL: + return new String[] { "EXTERNAL" }; + case AUTH_SHA: + return new String[] { "DBUS_COOKIE_SHA1" }; + case AUTH_ANON: + return new String[] { "ANONYMOUS" }; + case AUTH_SHA+AUTH_EXTERNAL: + return new String[] { "EXTERNAL", "DBUS_COOKIE_SHA1" }; + case AUTH_SHA+AUTH_ANON: + return new String[] { "ANONYMOUS", "DBUS_COOKIE_SHA1" }; + case AUTH_EXTERNAL+AUTH_ANON: + return new String[] { "ANONYMOUS", "EXTERNAL" }; + case AUTH_EXTERNAL+AUTH_ANON+AUTH_SHA: + return new String[] { "ANONYMOUS", "EXTERNAL", "DBUS_COOKIE_SHA1" }; + default: + return new String[] { }; + } + } + /** + * performs SASL auth on the given streams. + * Mode selects whether to run as a SASL server or client. + * Types is a bitmask of the available auth types. + * Returns true if the auth was successful and false if it failed. + */ + @SuppressWarnings("unchecked") + public boolean auth(int mode, int types, String guid, OutputStream out, InputStream in, UnixSocket us) throws IOException + { + String username = System.getProperty("user.name"); + String Uid = null; + String kernelUid = null; + try { + Class c = Class.forName("com.sun.security.auth.module.UnixSystem"); + Method m = c.getMethod("getUid"); + Object o = c.newInstance(); + long uid = (Long) m.invoke(o); + Uid = stupidlyEncode(""+uid); + } catch (Exception e) { + Uid = stupidlyEncode(username); + } + Command c; + int failed = 0; + int current = 0; + int state = INITIAL_STATE; + + while (state != AUTHENTICATED && state != FAILED) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "AUTH state: "+state); + switch (mode) { + case MODE_CLIENT: + switch (state) { + case INITIAL_STATE: + if (null == us) + out.write(new byte[] { 0 }); + else + us.sendCredentialByte((byte) 0); + send(out, COMMAND_AUTH); + state = WAIT_DATA; + break; + case WAIT_DATA: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_DATA: + switch (do_challenge(current, c)) { + case CONTINUE: + send(out, COMMAND_DATA, c.getResponse()); + break; + case OK: + send(out, COMMAND_DATA, c.getResponse()); + state = WAIT_OK; + break; + case ERROR: + send(out, COMMAND_ERROR, c.getResponse()); + break; + } + break; + case COMMAND_REJECTED: + failed |= current; + int available = c.getMechs() & (~failed); +// if (0 != (available & AUTH_EXTERNAL)){ +// send(out, COMMAND_AUTH, "EXTERNAL", Uid); +// current = AUTH_EXTERNAL; +// } else if (0 != (available & AUTH_SHA)) { +// send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid); +// current = AUTH_SHA; +// } else + if (0 != (available & AUTH_ANON)) { + send(out, COMMAND_AUTH, "ANONYMOUS"); + current = AUTH_ANON; + } + else state = FAILED; + break; + case COMMAND_ERROR: + send(out, COMMAND_CANCEL); + state = WAIT_REJECT; + break; + case COMMAND_OK: + send(out, COMMAND_BEGIN); + state = AUTHENTICATED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + case WAIT_OK: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_OK: + send(out, COMMAND_BEGIN); + state = AUTHENTICATED; + break; + case COMMAND_ERROR: + case COMMAND_DATA: + send(out, COMMAND_CANCEL); + state = WAIT_REJECT; + break; + case COMMAND_REJECTED: + failed |= current; + int available = c.getMechs() & (~failed); + state = WAIT_DATA; + if (0 != (available & AUTH_EXTERNAL)) { + send(out, COMMAND_AUTH, "EXTERNAL", Uid); + current = AUTH_EXTERNAL; + } else if (0 != (available & AUTH_SHA)) { + send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid); + current = AUTH_SHA; + } else if (0 != (available & AUTH_ANON)) { + send(out, COMMAND_AUTH, "ANONYMOUS"); + current = AUTH_ANON; + } + else state = FAILED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + case WAIT_REJECT: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_REJECTED: + failed |= current; + int available = c.getMechs() & (~failed); + if (0 != (available & AUTH_EXTERNAL)) { + send(out, COMMAND_AUTH, "EXTERNAL", Uid); + current = AUTH_EXTERNAL; + } else if (0 != (available & AUTH_SHA)) { + send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid); + current = AUTH_SHA; + } else if (0 != (available & AUTH_ANON)) { + send(out, COMMAND_AUTH, "ANONYMOUS"); + current = AUTH_ANON; + } + else state = FAILED; + break; + default: + state = FAILED; + break; + } + break; + default: + state = FAILED; + } + break; + case MODE_SERVER: + switch (state) { + case INITIAL_STATE: + byte[] buf = new byte[1]; + if (null == us) { + in.read(buf); + } else { + buf[0] = us.recvCredentialByte(); + int kuid = us.getPeerUID(); + if (kuid >= 0) + kernelUid = stupidlyEncode(""+kuid); + } + if (0 != buf[0]) state = FAILED; + else state = WAIT_AUTH; + break; + case WAIT_AUTH: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_AUTH: + if (null == c.getData()) { + send(out, COMMAND_REJECTED, getTypes(types)); + } else { + switch (do_response(current, Uid, kernelUid, c)) { + case CONTINUE: + send(out, COMMAND_DATA, c.getResponse()); + current = c.getMechs(); + state = WAIT_DATA; + break; + case OK: + send(out, COMMAND_OK, guid); + state = WAIT_BEGIN; + current = 0; + break; + case REJECT: + send(out, COMMAND_REJECTED, getTypes(types)); + current = 0; + break; + } + } + break; + case COMMAND_ERROR: + send(out, COMMAND_REJECTED, getTypes(types)); + break; + case COMMAND_BEGIN: + state = FAILED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + case WAIT_DATA: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_DATA: + switch (do_response(current, Uid, kernelUid, c)) { + case CONTINUE: + send(out, COMMAND_DATA, c.getResponse()); + state = WAIT_DATA; + break; + case OK: + send(out, COMMAND_OK, guid); + state = WAIT_BEGIN; + current = 0; + break; + case REJECT: + send(out, COMMAND_REJECTED, getTypes(types)); + current = 0; + break; + } + break; + case COMMAND_ERROR: + case COMMAND_CANCEL: + send(out, COMMAND_REJECTED, getTypes(types)); + state = WAIT_AUTH; + break; + case COMMAND_BEGIN: + state = FAILED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + case WAIT_BEGIN: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_ERROR: + case COMMAND_CANCEL: + send(out, COMMAND_REJECTED, getTypes(types)); + state = WAIT_AUTH; + break; + case COMMAND_BEGIN: + state = AUTHENTICATED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + default: + state = FAILED; + } + break; + default: + return false; + } + } + + return state == AUTHENTICATED; + } + } + public MessageReader min; + public MessageWriter mout; + public Transport() {} + public static String genGUID() + { + Random r = new Random(); + byte[] buf = new byte[16]; + r.nextBytes(buf); + String guid = Hexdump.toHex(buf); + return guid.replaceAll(" ", ""); + } + public Transport(BusAddress address) throws IOException + { + connect(address); + } + public Transport(String address) throws IOException, ParseException + { + connect(new BusAddress(address)); + } + public Transport(String address, int timeout) throws IOException, ParseException + { + connect(new BusAddress(address), timeout); + } + public void connect(String address) throws IOException, ParseException + { + connect(new BusAddress(address), 0); + } + public void connect(String address, int timeout) throws IOException, ParseException + { + connect(new BusAddress(address), timeout); + } + public void connect(BusAddress address) throws IOException + { + connect(address, 0); + } + public void connect(BusAddress address, int timeout) throws IOException + { + if (Debug.debug) Debug.print(Debug.INFO, "Connecting to "+address); + OutputStream out = null; + InputStream in = null; + UnixSocket us = null; + Socket s = null; + int mode = 0; + int types = 0; + if ("unix".equals(address.getType())) { + types = SASL.AUTH_EXTERNAL; + if (null != address.getParameter("listen")) { + mode = SASL.MODE_SERVER; + UnixServerSocket uss = new UnixServerSocket(); + if (null != address.getParameter("abstract")) + uss.bind(new UnixSocketAddress(address.getParameter("abstract"), true)); + else if (null != address.getParameter("path")) + uss.bind(new UnixSocketAddress(address.getParameter("path"), false)); + us = uss.accept(); + } else { + mode = SASL.MODE_CLIENT; + us = new UnixSocket(); + if (null != address.getParameter("abstract")) + us.connect(new UnixSocketAddress(address.getParameter("abstract"), true)); + else if (null != address.getParameter("path")) + us.connect(new UnixSocketAddress(address.getParameter("path"), false)); + } + us.setPassCred(true); + in = us.getInputStream(); + out = us.getOutputStream(); + } else if ("tcp".equals(address.getType())) { + types = SASL.AUTH_SHA; + if (null != address.getParameter("listen")) { + mode = SASL.MODE_SERVER; + ServerSocket ss = new ServerSocket(); + ss.bind(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port")))); + s = ss.accept(); + } else { + types = SASL.AUTH_ANON; + mode = SASL.MODE_CLIENT; + s = new Socket(); + s.connect(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port")))); + } + in = s.getInputStream(); + out = s.getOutputStream(); + } else { + throw new IOException($_("unknown address type ")+address.getType()); + } + + if (!(new SASL()).auth(mode, types, address.getParameter("guid"), out, in, us)) { + out.close(); + throw new IOException($_("Failed to auth")); + } + if (null != us) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting timeout to "+timeout+" on Socket"); + if (timeout == 1) + us.setBlocking(false); + else + us.setSoTimeout(timeout); + } + if (null != s) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting timeout to "+timeout+" on Socket"); + s.setSoTimeout(timeout); + } + mout = new MessageWriter(out); + min = new MessageReader(in); + } + public void disconnect() throws IOException + { + if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting Transport"); + min.close(); + mout.close(); + } +} + + diff --git a/app/src/main/java/org/freedesktop/dbus/Tuple.java b/app/src/main/java/org/freedesktop/dbus/Tuple.java new file mode 100644 index 00000000..becfc60c --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Tuple.java @@ -0,0 +1,24 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +/** + * This class should be extended to create Tuples. + * Any such class may be used as the return type for a method + * which returns multiple values. + * All fields in the Tuple which you wish to be serialized and sent to the + * remote method should be annotated with the org.freedesktop.dbus.Position + * annotation, in the order they should appear to DBus. + */ +public abstract class Tuple extends Container +{ + public Tuple() {} +} diff --git a/app/src/main/java/org/freedesktop/dbus/TypeSignature.java b/app/src/main/java/org/freedesktop/dbus/TypeSignature.java new file mode 100644 index 00000000..c297821d --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/TypeSignature.java @@ -0,0 +1,37 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.lang.reflect.Type; +import org.freedesktop.dbus.exceptions.DBusException; + +public class TypeSignature +{ + String sig; + public TypeSignature(String sig) + { + this.sig = sig; + } + public TypeSignature(Type[] types) throws DBusException + { + StringBuffer sb = new StringBuffer(); + for (Type t: types) { + String[] ts = Marshalling.getDBusType(t); + for (String s: ts) + sb.append(s); + } + this.sig = sb.toString(); + } + public String getSig() + { + return sig; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/UInt16.java b/app/src/main/java/org/freedesktop/dbus/UInt16.java new file mode 100644 index 00000000..a47e2dcc --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/UInt16.java @@ -0,0 +1,79 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.text.MessageFormat; + +/** + * Class to represent 16-bit unsigned integers. + */ +@SuppressWarnings("serial") +public class UInt16 extends Number implements Comparable +{ + /** Maximum possible value. */ + public static final int MAX_VALUE = 65535; + /** Minimum possible value. */ + public static final int MIN_VALUE = 0; + private int value; + /** Create a UInt16 from an int. + * @param value Must be within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE + */ + public UInt16(int value) + { + if (value < MIN_VALUE || value > MAX_VALUE) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_VALUE})); + this.value = value; + } + /** Create a UInt16 from a String. + * @param value Must parse to a valid integer within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_VALUE + */ + public UInt16(String value) + { + this(Integer.parseInt(value)); + } + /** The value of this as a byte. */ + public byte byteValue() { return (byte) value; } + /** The value of this as a double. */ + public double doubleValue() { return (double) value; } + /** The value of this as a float. */ + public float floatValue() { return (float) value; } + /** The value of this as a int. */ + public int intValue() { return /*(int)*/ value; } + /** The value of this as a long. */ + public long longValue() { return (long) value; } + /** The value of this as a short. */ + public short shortValue(){ return (short) value; } + /** Test two UInt16s for equality. */ + public boolean equals(Object o) + { + return o instanceof UInt16 && ((UInt16) o).value == this.value; + } + public int hashCode() + { + return /*(int)*/ value; + } + /** Compare two UInt16s. + * @return 0 if equal, -ve or +ve if they are different. + */ + public int compareTo(UInt16 other) + { + return /*(int)*/ (this.value - other.value); + } + /** The value of this as a string. */ + public String toString() + { + return ""+value; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/UInt32.java b/app/src/main/java/org/freedesktop/dbus/UInt32.java new file mode 100644 index 00000000..f2afc3dd --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/UInt32.java @@ -0,0 +1,83 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.text.MessageFormat; + +/** + * Class to represent unsigned 32-bit numbers. + */ +@SuppressWarnings("serial") +public class UInt32 extends Number implements Comparable +{ + /** Maximum allowed value */ + public static final long MAX_VALUE = 4294967295L; + /** Minimum allowed value */ + public static final long MIN_VALUE = 0; + private long value; + /** Create a UInt32 from a long. + * @param value Must be a valid integer within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE + */ + public UInt32(long value) + { + if (value < MIN_VALUE || value > MAX_VALUE) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_VALUE})); + this.value = value; + } + /** Create a UInt32 from a String. + * @param value Must parse to a valid integer within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_VALUE + */ + public UInt32(String value) + { + this(Long.parseLong(value)); + } + + public static UInt32 fromUnsignedInt(int value) { + return new UInt32(Long.parseUnsignedLong(Integer.toUnsignedString(value, 16), 16)); + } + /** The value of this as a byte. */ + public byte byteValue() { return (byte) value; } + /** The value of this as a double. */ + public double doubleValue() { return (double) value; } + /** The value of this as a float. */ + public float floatValue() { return (float) value; } + /** The value of this as a int. */ + public int intValue() { return (int) value; } + /** The value of this as a long. */ + public long longValue() { return /*(long)*/ value; } + /** The value of this as a short. */ + public short shortValue(){ return (short) value; } + /** Test two UInt32s for equality. */ + public boolean equals(Object o) + { + return o instanceof UInt32 && ((UInt32) o).value == this.value; + } + public int hashCode() + { + return (int) value; + } + /** Compare two UInt32s. + * @return 0 if equal, -ve or +ve if they are different. + */ + public int compareTo(UInt32 other) + { + return (int) (this.value - other.value); + } + /** The value of this as a string */ + public String toString() + { + return ""+value; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/UInt64.java b/app/src/main/java/org/freedesktop/dbus/UInt64.java new file mode 100644 index 00000000..1fe745f9 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/UInt64.java @@ -0,0 +1,151 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.math.BigInteger; + +import java.text.MessageFormat; + +/** + * Class to represent unsigned 64-bit numbers. + * Warning: Any functions which take or return a long + * are restricted to the range of a signed 64bit number. + * Use the BigInteger methods if you wish access to the full + * range. + */ +@SuppressWarnings("serial") +public class UInt64 extends Number implements Comparable +{ + /** Maximum allowed value (when accessed as a long) */ + public static final long MAX_LONG_VALUE = Long.MAX_VALUE; + /** Maximum allowed value (when accessed as a BigInteger) */ + public static final BigInteger MAX_BIG_VALUE = new BigInteger("18446744073709551615"); + /** Minimum allowed value */ + public static final long MIN_VALUE = 0; + private BigInteger value; + private long top; + private long bottom; + /** Create a UInt64 from a long. + * @param value Must be a valid integer within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE + */ + public UInt64(long value) + { + if (value < MIN_VALUE || value > MAX_LONG_VALUE) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_LONG_VALUE})); + this.value = new BigInteger(""+value); + this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue(); + this.bottom = this.value.and(new BigInteger("4294967295")).longValue(); + } + /** + * Create a UInt64 from two longs. + * @param top Most significant 4 bytes. + * @param bottom Least significant 4 bytes. + */ + public UInt64(long top, long bottom) + { + BigInteger a = new BigInteger(""+top); + a = a.shiftLeft(32); + a = a.add(new BigInteger(""+bottom)); + if (0 > a.compareTo(BigInteger.ZERO)) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { a, MIN_VALUE, MAX_BIG_VALUE})); + if (0 < a.compareTo(MAX_BIG_VALUE)) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { a, MIN_VALUE, MAX_BIG_VALUE})); + this.value = a; + this.top = top; + this.bottom = bottom; + } + /** Create a UInt64 from a BigInteger + * @param value Must be a valid BigInteger between MIN_VALUE–MAX_BIG_VALUE + * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_BIG_VALUE + */ + public UInt64(BigInteger value) + { + if (null == value) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); + if (0 > value.compareTo(BigInteger.ZERO)) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); + if (0 < value.compareTo(MAX_BIG_VALUE)) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); + this.value = value; + this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue(); + this.bottom = this.value.and(new BigInteger("4294967295")).longValue(); + } + /** Create a UInt64 from a String. + * @param value Must parse to a valid integer within MIN_VALUE–MAX_BIG_VALUE + * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_BIG_VALUE + */ + public UInt64(String value) + { + if (null == value) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); + BigInteger a = new BigInteger(value); + if (0 > a.compareTo(BigInteger.ZERO)) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); + if (0 < a.compareTo(MAX_BIG_VALUE)) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); + this.value = a; + this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue(); + this.bottom = this.value.and(new BigInteger("4294967295")).longValue(); + } + /** The value of this as a BigInteger. */ + public BigInteger value() { return value; } + /** The value of this as a byte. */ + public byte byteValue() { return value.byteValue(); } + /** The value of this as a double. */ + public double doubleValue() { return value.doubleValue(); } + /** The value of this as a float. */ + public float floatValue() { return value.floatValue(); } + /** The value of this as a int. */ + public int intValue() { return value.intValue(); } + /** The value of this as a long. */ + public long longValue() { return value.longValue(); } + /** The value of this as a short. */ + public short shortValue(){ return value.shortValue(); } + /** Test two UInt64s for equality. */ + public boolean equals(Object o) + { + return o instanceof UInt64 && this.value.equals(((UInt64) o).value); + } + public int hashCode() + { + return value.hashCode(); + } + /** Compare two UInt32s. + * @return 0 if equal, -ve or +ve if they are different. + */ + public int compareTo(UInt64 other) + { + return this.value.compareTo(other.value); + } + /** The value of this as a string. */ + public String toString() + { + return value.toString(); + } + /** + * Most significant 4 bytes. + */ + public long top() + { + return top; + } + /** + * Least significant 4 bytes. + */ + public long bottom() + { + return bottom; + } +} + diff --git a/app/src/main/java/org/freedesktop/dbus/Variant.java b/app/src/main/java/org/freedesktop/dbus/Variant.java new file mode 100644 index 00000000..c729685c --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Variant.java @@ -0,0 +1,112 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import static org.freedesktop.dbus.Gettext.$_; + +import java.lang.reflect.Type; +import java.text.MessageFormat; +import java.util.Vector; +import org.freedesktop.dbus.exceptions.DBusException; + +import cx.ath.matthew.debug.Debug; + +/** + * A Wrapper class for Variant values. + * A method on DBus can send or receive a Variant. + * This will wrap another value whose type is determined at runtime. + * The Variant may be parameterized to restrict the types it may accept. + */ +public class Variant +{ + private final T o; + private final Type type; + private final String sig; + /** + * Create a Variant from a basic type object. + * @param o The wrapped value. + * @throws IllegalArugmentException If you try and wrap Null or an object of a non-basic type. + */ + public Variant(T o) throws IllegalArgumentException + { + if (null == o) throw new IllegalArgumentException($_("Can't wrap Null in a Variant")); + type = o.getClass(); + try { + String[] ss = Marshalling.getDBusType(o.getClass(), true); + if (ss.length != 1) + throw new IllegalArgumentException($_("Can't wrap a multi-valued type in a Variant: ")+type); + this.sig = ss[0]; + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + throw new IllegalArgumentException(MessageFormat.format($_("Can't wrap {0} in an unqualified Variant ({1})."), new Object[] { o.getClass(), DBe.getMessage() })); + } + this.o = o; + } + /** + * Create a Variant. + * @param o The wrapped value. + * @param type The explicit type of the value. + * @throws IllegalArugmentException If you try and wrap Null or an object which cannot be sent over DBus. + */ + public Variant(T o, Type type) throws IllegalArgumentException + { + if (null == o) throw new IllegalArgumentException($_("Can't wrap Null in a Variant")); + this.type = type; + try { + String[] ss = Marshalling.getDBusType(type); + if (ss.length != 1) + throw new IllegalArgumentException($_("Can't wrap a multi-valued type in a Variant: ")+type); + this.sig = ss[0]; + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + throw new IllegalArgumentException(MessageFormat.format($_("Can't wrap {0} in an unqualified Variant ({1})."), new Object[] { type, DBe.getMessage() })); + } + this.o = o; + } + /** + * Create a Variant. + * @param o The wrapped value. + * @param sig The explicit type of the value, as a dbus type string. + * @throws IllegalArugmentException If you try and wrap Null or an object which cannot be sent over DBus. + */ + public Variant(T o, String sig) throws IllegalArgumentException + { + if (null == o) throw new IllegalArgumentException($_("Can't wrap Null in a Variant")); + this.sig = sig; + try { + Vector ts = new Vector(); + Marshalling.getJavaType(sig, ts,1); + if (ts.size() != 1) + throw new IllegalArgumentException($_("Can't wrap multiple or no types in a Variant: ")+sig); + this.type = ts.get(0); + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + throw new IllegalArgumentException(MessageFormat.format($_("Can't wrap {0} in an unqualified Variant ({1})."), new Object[] { sig, DBe.getMessage() })); + } + this.o = o; + } + /** Return the wrapped value. */ + public T getValue() { return o; } + /** Return the type of the wrapped value. */ + public Type getType() { return type; } + /** Return the dbus signature of the wrapped value. */ + public String getSig() { return sig; } + /** Format the Variant as a string. */ + public String toString() { return "["+o+"]"; } + /** Compare this Variant with another by comparing contents */ + @SuppressWarnings("unchecked") + public boolean equals(Object other) + { + if (null == other) return false; + if (!(other instanceof Variant)) return false; + return this.o.equals(((Variant)other).o); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/Caller.java b/app/src/main/java/org/freedesktop/dbus/bin/Caller.java new file mode 100644 index 00000000..ecd6002f --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/Caller.java @@ -0,0 +1,83 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.bin; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Vector; +import java.io.File; +import org.freedesktop.dbus.BusAddress; +import org.freedesktop.dbus.Error; +import org.freedesktop.dbus.Marshalling; +import org.freedesktop.dbus.Message; +import org.freedesktop.dbus.MethodCall; +import org.freedesktop.dbus.Transport; +import cx.ath.matthew.debug.Debug; + +public class Caller +{ + @SuppressWarnings("unchecked") + public static void main(String[] args) + { + try { + if (Debug.debug) { + Debug.setHexDump(true); + Debug.loadConfig(new File("debug.conf")); + } + if (args.length < 4) { + System.out.println ("Syntax: Caller [ ]"); + System.exit(1); + } + String addr = System.getenv("DBUS_SESSION_BUS_ADDRESS"); + BusAddress address = new BusAddress(addr); + Transport conn = new Transport(address); + + Message m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello", (byte) 0, null);; + conn.mout.writeMessage(m); + + if ("".equals(args[2])) args[2] = null; + if (args.length == 4) + m = new MethodCall(args[0], args[1], args[2], args[3], (byte) 0, null); + else { + Vector lts = new Vector(); + Marshalling.getJavaType(args[4],lts, -1); + Type[] ts = lts.toArray(new Type[0]); + Object[] os = new Object[args.length-5]; + for (int i = 5; i < args.length; i++) { + if (ts[i-5] instanceof Class) { + try { + Constructor c = ((Class) ts[i-5]).getConstructor(String.class); + os[i-5] = c.newInstance(args[i]); + } catch (Exception e) { + os[i-5] = args[i]; + } + } else + os[i-5] = args[i]; + } + m = new MethodCall(args[0], args[1], args[2], args[3], (byte) 0, args[4], os); + } + long serial = m.getSerial(); + conn.mout.writeMessage(m); + do { + m = conn.min.readMessage(); + } while (serial != m.getReplySerial()); + if (m instanceof Error) ((Error) m).throwException(); + else { + Object[] os = m.getParameters(); + System.out.println(Arrays.deepToString(os)); + } + } catch (Exception e) { + System.out.println (e.getClass().getSimpleName()+": "+e.getMessage()); + System.exit(1); + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/CreateInterface.java b/app/src/main/java/org/freedesktop/dbus/bin/CreateInterface.java new file mode 100644 index 00000000..cfadcc62 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/CreateInterface.java @@ -0,0 +1,711 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.bin; + +import static org.freedesktop.dbus.Gettext.$_; +import static org.freedesktop.dbus.bin.IdentifierMangler.mangle; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.Reader; +import java.io.StringReader; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.freedesktop.DBus.Introspectable; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.Marshalling; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.types.DBusStructType; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Converts a DBus XML file into Java interface definitions. + */ +public class CreateInterface +{ + @SuppressWarnings("unchecked") + private static String collapseType(Type t, Set imports, Map structs, boolean container, boolean fullnames) throws DBusException + { + if (t instanceof ParameterizedType) { + String s; + Class c = (Class) ((ParameterizedType) t).getRawType(); + if (null != structs && t instanceof DBusStructType) { + int num = 1; + String name = "Struct"; + while (null != structs.get(new StructStruct(name+num))) num++; + name = name+num; + structs.put(new StructStruct(name), ((ParameterizedType) t).getActualTypeArguments()); + return name; + } + if (null != imports) imports.add(c.getName()); + if (fullnames) return c.getName(); + else s = c.getSimpleName(); + s += '<'; + Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); + for (Type st: ts) + s += collapseType(st, imports, structs, true, fullnames)+','; + s = s.replaceAll(",$", ">"); + return s; + } else if (t instanceof Class) { + Class c = (Class) t; + if (c.isArray()) { + return collapseType(c.getComponentType(), imports, structs, container, fullnames)+"[]"; + } else { + Package p = c.getPackage(); + if (null != imports && + !"java.lang".equals(p.getName())) imports.add(c.getName()); + if (container) { + if (fullnames) return c.getName(); + else return c.getSimpleName(); + } else { + try { + Field f = c.getField("TYPE"); + Class d = (Class) f.get(c); + return d.getSimpleName(); + } catch (Exception e) { + return c.getSimpleName(); + } + } + } + } else return ""; + } + private static String getJavaType(String dbus, Set imports, Map structs, boolean container, boolean fullnames) throws DBusException + { + if (null == dbus || "".equals(dbus)) return ""; + Vector v = new Vector(); + int c = Marshalling.getJavaType(dbus, v, 1); + Type t = v.get(0); + return collapseType(t, imports, structs, container, fullnames); + } + public String comment = ""; + boolean builtin; + + public CreateInterface(PrintStreamFactory factory, boolean builtin) + { + this.factory = factory; + this.builtin = builtin; + } + @SuppressWarnings("fallthrough") + String parseReturns(Vector out, Set imports, Map tuples, Map structs) throws DBusException + { + String[] names = new String[] { "Pair", "Triplet", "Quad", "Quintuple", "Sextuple", "Septuple" }; + String sig = ""; + String name = null; + switch (out.size()) { + case 0: + sig += "void "; + break; + case 1: + sig += getJavaType(out.get(0).getAttribute("type"), imports, structs, false, false)+" "; + break; + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + name = names[out.size() - 2]; + default: + if (null == name) + name = "NTuple"+out.size(); + + tuples.put(name, out.size()); + sig += name + "<"; + for (Element arg: out) + sig += getJavaType(arg.getAttribute("type"), imports, structs, true, false)+", "; + sig = sig.replaceAll(", $","> "); + break; + } + return sig; + } + String parseMethod(Element meth, Set imports, Map tuples, Map structs, Set exceptions, Set anns) throws DBusException + { + Vector in = new Vector(); + Vector out = new Vector(); + if (null == meth.getAttribute("name") || + "".equals(meth.getAttribute("name"))) { + System.err.println($_("ERROR: Method name was blank, failed")); + System.exit(1); + } + String annotations = ""; + String throwses = null; + + for (Node a: new IterableNodeList(meth.getChildNodes())) { + + if (Node.ELEMENT_NODE != a.getNodeType()) continue; + + checkNode(a, "arg", "annotation"); + + if ("arg".equals(a.getNodeName())) { + Element arg = (Element) a; + + // methods default to in + if ("out".equals(arg.getAttribute("direction"))) + out.add(arg); + else + in.add(arg); + } + else if ("annotation".equals(a.getNodeName())) { + Element e = (Element) a; + if (e.getAttribute("name").equals("org.freedesktop.DBus.Method.Error")) { + if (null == throwses) + throwses = e.getAttribute("value"); + else + throwses += ", " + e.getAttribute("value"); + exceptions.add(e.getAttribute("value")); + } else + annotations += parseAnnotation(e, imports, anns); + } + } + + String sig = ""; + comment = ""; + sig += parseReturns(out, imports, tuples, structs); + + sig += mangle(meth.getAttribute("name"))+"("; + + char defaultname = 'a'; + String params = ""; + for (Element arg: in) { + String type = getJavaType(arg.getAttribute("type"), imports, structs, false, false); + String name = arg.getAttribute("name"); + if (null == name || "".equals(name)) name = ""+(defaultname++); + params += type+" "+mangle(name)+", "; + } + return ("".equals(comment) ? "" : " /**\n" + comment + " */\n") + + annotations + " public " + sig + + params.replaceAll("..$", "")+")"+ + (null == throwses? "": " throws "+throwses)+";"; + } + String parseSignal(Element signal, Set imports, Map structs, Set anns) throws DBusException + { + Map params = new HashMap(); + Vector porder = new Vector(); + char defaultname = 'a'; + imports.add("org.freedesktop.dbus.DBusSignal"); + imports.add("org.freedesktop.dbus.exceptions.DBusException"); + String annotations = ""; + for (Node a: new IterableNodeList(signal.getChildNodes())) { + + if (Node.ELEMENT_NODE != a.getNodeType()) continue; + + checkNode(a, "arg", "annotation"); + + if ("annotation".equals(a.getNodeName())) + annotations += parseAnnotation((Element) a, imports, anns); + else { + Element arg = (Element) a; + String type = getJavaType(arg.getAttribute("type"), imports, structs, false, false); + String name = arg.getAttribute("name"); + if (null == name || "".equals(name)) name = ""+(defaultname++); + params.put(mangle(name), type); + porder.add(mangle(name)); + } + } + + String out = ""; + out += annotations; + out += " public static class "+signal.getAttribute("name"); + out += " extends DBusSignal\n {\n"; + for (String name: porder) + out += " public final "+params.get(name)+" "+name+";\n"; + out += " public "+signal.getAttribute("name")+"(String path"; + for (String name: porder) + out += ", "+params.get(name)+" "+name; + out += ") throws DBusException\n {\n super(path"; + for (String name: porder) + out += ", "+name; + out += ");\n"; + for (String name: porder) + out += " this."+name+" = "+name+";\n"; + out += " }\n"; + + out += " }\n"; + return out; + } + + String parseAnnotation(Element ann, Set imports, Set annotations) + { + String s = " @"+ann.getAttribute("name").replaceAll(".*\\.([^.]*)$","$1")+"("; + if (null != ann.getAttribute("value") + && !"".equals(ann.getAttribute("value"))) + s += '"'+ann.getAttribute("value")+'"'; + imports.add(ann.getAttribute("name")); + annotations.add(ann.getAttribute("name")); + return s += ")\n"; + } + + void parseInterface(Element iface, PrintStream out, Map tuples, Map structs, Set exceptions, Set anns) throws DBusException + { + if (null == iface.getAttribute("name") || + "".equals(iface.getAttribute("name"))) { + System.err.println($_("ERROR: Interface name was blank, failed")); + System.exit(1); + } + + out.println("package "+iface.getAttribute("name").replaceAll("\\.[^.]*$","")+";"); + + String methods = ""; + String signals = ""; + String annotations = ""; + Set imports = new TreeSet(); + imports.add("org.freedesktop.dbus.DBusInterface"); + for (Node meth: new IterableNodeList(iface.getChildNodes())) { + + if (Node.ELEMENT_NODE != meth.getNodeType()) continue; + + checkNode(meth, "method", "signal", "property", "annotation"); + + if ("method".equals(meth.getNodeName())) + methods += parseMethod((Element) meth, imports, tuples, structs, exceptions, anns) + "\n"; + else if ("signal".equals(meth.getNodeName())) + signals += parseSignal((Element) meth, imports, structs, anns); + else if ("property".equals(meth.getNodeName())) + System.err.println("WARNING: Ignoring property"); + else if ("annotation".equals(meth.getNodeName())) + annotations += parseAnnotation((Element) meth, imports, anns); + } + + if (imports.size() > 0) + for (String i: imports) + out.println("import "+i+";"); + + out.print(annotations); + out.print("public interface "+iface.getAttribute("name").replaceAll("^.*\\.([^.]*)$","$1")); + out.println(" extends DBusInterface"); + out.println("{"); + out.println(signals); + out.println(methods); + out.println("}"); + } + void createException(String name, String pack, PrintStream out) throws DBusException + { + out.println("package "+pack+";"); + out.println("import org.freedesktop.dbus.DBusExecutionException;"); + out.print("public class "+name); + out.println(" extends DBusExecutionException"); + out.println("{"); + out.println(" public "+name+"(String message)"); + out.println(" {"); + out.println(" super(message);"); + out.println(" }"); + out.println("}"); + } + void createAnnotation(String name, String pack, PrintStream out) throws DBusException + { + out.println("package "+pack+";"); + out.println("import java.lang.annotation.Retention;"); + out.println("import java.lang.annotation.RetentionPolicy;"); + out.println("@Retention(RetentionPolicy.RUNTIME)"); + out.println("public @interface "+name); + out.println("{"); + out.println(" String value();"); + out.println("}"); + } + void createStruct(String name, Type[] type, String pack, PrintStream out, Map existing) throws DBusException, IOException + { + out.println("package "+pack+";"); + + Set imports = new TreeSet(); + imports.add("org.freedesktop.dbus.Position"); + imports.add("org.freedesktop.dbus.Struct"); + Map structs = new HashMap(existing); + String[] types = new String[type.length]; + for (int i = 0; i < type.length; i++) + types[i] = collapseType(type[i], imports, structs, false, false); + + for (String im: imports) out.println("import "+im+";"); + + out.println("public final class "+name+" extends Struct"); + out.println("{"); + int i = 0; + char c = 'a'; + String params = ""; + for (String t: types) { + out.println(" @Position("+i++ +")"); + out.println(" public final "+t+" "+c+";"); + params += t+" "+c+", "; + c++; + } + out.println(" public "+name+"("+params.replaceAll("..$", "")+")"); + out.println(" {"); + for (char d = 'a'; d < c; d++) + out.println(" this."+d+" = "+d+";"); + + out.println(" }"); + out.println("}"); + + structs = StructStruct.fillPackages(structs, pack); + Map tocreate = new HashMap(structs); + for (StructStruct ss: existing.keySet()) tocreate.remove(ss); + createStructs(tocreate, structs); + } + void createTuple(String name, int num, String pack, PrintStream out) throws DBusException + { + out.println("package "+pack+";"); + out.println("import org.freedesktop.dbus.Position;"); + out.println("import org.freedesktop.dbus.Tuple;"); + out.println("/** Just a typed container class */"); + out.print("public final class "+name); + String types = " <"; + for (char v = 'A'; v < 'A'+num; v++) + types += v + ","; + out.print(types.replaceAll(",$","> ")); + out.println("extends Tuple"); + out.println("{"); + + char t = 'A'; + char n = 'a'; + for (int i = 0; i < num; i++,t++,n++) { + out.println(" @Position("+i+")"); + out.println(" public final "+t+" "+n+";"); + } + + out.print(" public "+name+"("); + String sig = ""; + t = 'A'; + n = 'a'; + for (int i = 0; i < num; i++,t++,n++) + sig += t+" "+n+", "; + out.println(sig.replaceAll(", $", ")")); + out.println(" {"); + for (char v = 'a'; v < 'a'+num; v++) + out.println(" this."+v+" = "+v+";"); + out.println(" }"); + + out.println("}"); + } + void parseRoot(Element root) throws DBusException, IOException + { + Map structs = new HashMap(); + Set exceptions = new TreeSet(); + Set annotations = new TreeSet(); + + for (Node iface: new IterableNodeList(root.getChildNodes())) { + + if (Node.ELEMENT_NODE != iface.getNodeType()) continue; + + checkNode(iface, "interface", "node"); + + if ("interface".equals(iface.getNodeName())) { + + Map tuples = new HashMap(); + String name = ((Element) iface).getAttribute("name"); + String file = name.replaceAll("\\.","/")+".java"; + String path = file.replaceAll("/[^/]*$", ""); + String pack = name.replaceAll("\\.[^.]*$",""); + + // don't create interfaces in org.freedesktop.DBus by default + if (pack.startsWith("org.freedesktop.DBus") && !builtin) continue; + + factory.init(file, path); + parseInterface((Element) iface, + factory.createPrintStream(file), tuples, structs, exceptions, annotations); + + structs = StructStruct.fillPackages(structs, pack); + createTuples(tuples, pack); + } + else if ("node".equals(iface.getNodeName())) + parseRoot((Element) iface); + else { + System.err.println($_("ERROR: Unknown node: ")+iface.getNodeName()); + System.exit(1); + } + } + + createStructs(structs, structs); + createExceptions(exceptions); + createAnnotations(annotations); + } + private void createAnnotations(Set annotations) throws DBusException, IOException + { + for (String fqn: annotations) { + String name = fqn.replaceAll("^.*\\.([^.]*)$", "$1"); + String pack = fqn.replaceAll("\\.[^.]*$",""); + // don't create things in org.freedesktop.DBus by default + if (pack.startsWith("org.freedesktop.DBus") && !builtin) + continue; + String path = pack.replaceAll("\\.", "/"); + String file = name.replaceAll("\\.","/")+".java"; + factory.init(file, path); + createAnnotation(name, pack, + factory.createPrintStream(path, name)); + } + } + private void createExceptions(Set exceptions) throws DBusException, IOException + { + for (String fqn: exceptions) { + String name = fqn.replaceAll("^.*\\.([^.]*)$", "$1"); + String pack = fqn.replaceAll("\\.[^.]*$",""); + // don't create things in org.freedesktop.DBus by default + if (pack.startsWith("org.freedesktop.DBus") && !builtin) + continue; + String path = pack.replaceAll("\\.", "/"); + String file = name.replaceAll("\\.","/")+".java"; + factory.init(file, path); + createException(name, pack, + factory.createPrintStream(path, name)); + } + } + private void createStructs(Map structs, Map existing) throws DBusException, IOException + { + for (StructStruct ss: structs.keySet()) { + String file = ss.name.replaceAll("\\.","/")+".java"; + String path = ss.pack.replaceAll("\\.", "/"); + factory.init(file, path); + createStruct(ss.name, structs.get(ss), ss.pack, + factory.createPrintStream(path, ss.name), existing); + } + } + + private void createTuples(Map typeMap, String pack) throws DBusException, IOException + { + for (String tname: typeMap.keySet()) + createTuple(tname, typeMap.get(tname), pack, + factory.createPrintStream(pack.replaceAll("\\.","/"), tname)); + } + + public static abstract class PrintStreamFactory + { + + public abstract void init(String file, String path); + + /** + * @param path + * @param tname + * @return PrintStream + * @throws IOException + */ + public PrintStream createPrintStream(String path, String tname) throws IOException + { + final String file = path+"/"+tname+".java"; + return createPrintStream(file); + } + + /** + * @param file + * @return PrintStream + * @throws IOException + */ + public abstract PrintStream createPrintStream(final String file) throws IOException; + + } + static class ConsoleStreamFactory extends PrintStreamFactory + { + + @Override + public + void init(String file, String path) + { + } + + @Override + public + PrintStream createPrintStream(String file) throws IOException + { + System.out.println("/* File: "+file+" */"); + return System.out; + } + + public PrintStream createPrintStream(String path, String tname) throws IOException + { + return super.createPrintStream(path, tname); + } + + } + + static class FileStreamFactory extends PrintStreamFactory + { + public void init(String file, String path) + { + new File(path).mkdirs(); + } + + + /** + * @param file + * @return + * @throws IOException + */ + public PrintStream createPrintStream(final String file) throws IOException + { + return new PrintStream(new FileOutputStream(file)); + } + + } + + static void checkNode(Node n, String... names) + { + String expected = ""; + for (String name: names) { + if (name.equals(n.getNodeName())) return; + expected += name + " or "; + } + System.err.println(MessageFormat.format($_("ERROR: Expected {0}, got {1}, failed."), new Object[] { expected.replaceAll("....$", ""), n.getNodeName() })); + System.exit(1); + } + + private final PrintStreamFactory factory; + + static class Config + { + int bus = DBusConnection.SESSION; + String busname = null; + String object = null; + File datafile = null; + boolean printtree = false; + boolean fileout = false; + boolean builtin = false; + } + + static void printSyntax() + { + printSyntax(System.err); + } + static void printSyntax(PrintStream o) + { + o.println("Syntax: CreateInterface [file | busname object]"); + o.println(" Options: --no-ignore-builtin --system -y --session -s --create-files -f --help -h --version -v"); + } + public static void version() + { + System.out.println("Java D-Bus Version "+System.getProperty("Version")); + System.exit(1); + } + + static Config parseParams(String[] args) + { + Config config = new Config(); + for (String p: args) { + if ("--system".equals(p) || "-y".equals(p)) + config.bus = DBusConnection.SYSTEM; + else if ("--session".equals(p) || "-s".equals(p)) + config.bus = DBusConnection.SESSION; + else if ("--no-ignore-builtin".equals(p)) + config.builtin = true; + else if ("--create-files".equals(p) || "-f".equals(p)) + config.fileout = true; + else if ("--print-tree".equals(p) || "-p".equals(p)) + config.printtree = true; + else if ("--help".equals(p) || "-h".equals(p)) { + printSyntax(System.out); + System.exit(0); + } else if ("--version".equals(p) || "-v".equals(p)) { + version(); + System.exit(0); + } else if (p.startsWith("-")) { + System.err.println($_("ERROR: Unknown option: ")+p); + printSyntax(); + System.exit(1); + } + else { + if (null == config.busname) config.busname = p; + else if (null == config.object) config.object = p; + else { + printSyntax(); + System.exit(1); + } + } + } + if (null == config.busname) { + printSyntax(); + System.exit(1); + } + else if (null == config.object) { + config.datafile = new File(config.busname); + config.busname = null; + } + return config; + } + + public static void main(String[] args) throws Exception + { + Config config = parseParams(args); + + Reader introspectdata = null; + + if (null != config.busname) try { + DBusConnection conn = DBusConnection.getConnection(config.bus); + Introspectable in = conn.getRemoteObject(config.busname, config.object, Introspectable.class); + String id = in.Introspect(); + if (null == id) { + System.err.println($_("ERROR: Failed to get introspection data")); + System.exit(1); + } + introspectdata = new StringReader(id); + conn.disconnect(); + } catch (DBusException DBe) { + System.err.println($_("ERROR: Failure in DBus Communications: ")+DBe.getMessage()); + System.exit(1); + } catch (DBusExecutionException DEe) { + System.err.println($_("ERROR: Failure in DBus Communications: ")+DEe.getMessage()); + System.exit(1); + + } else if (null != config.datafile) try { + introspectdata = new InputStreamReader(new FileInputStream(config.datafile)); + } catch (FileNotFoundException FNFe) { + System.err.println($_("ERROR: Could not find introspection file: ")+FNFe.getMessage()); + System.exit(1); + } + try { + PrintStreamFactory factory = config.fileout ? new FileStreamFactory() : new ConsoleStreamFactory(); + CreateInterface createInterface = new CreateInterface(factory, config.builtin); + createInterface.createInterface(introspectdata); + } catch (DBusException DBe) { + System.err.println("ERROR: "+DBe.getMessage()); + System.exit(1); + } + } + /** Output the interface for the supplied xml reader + * @param introspectdata The introspect data reader + * @throws ParserConfigurationException If the xml parser could not be configured + * @throws SAXException If a problem occurs reading the xml data + * @throws IOException If an IO error occurs + * @throws DBusException If the dbus related error occurs + */ + public void createInterface(Reader introspectdata) throws ParserConfigurationException, SAXException, IOException, DBusException + { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(new InputSource(introspectdata)); + + Element root = document.getDocumentElement(); + checkNode(root, "node"); + parseRoot(root); + + } +} + + diff --git a/app/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java b/app/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java new file mode 100644 index 00000000..8595fe07 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java @@ -0,0 +1,878 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.bin; + +import static org.freedesktop.dbus.Gettext.$_; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.AbstractConnection; +import org.freedesktop.dbus.BusAddress; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.DirectConnection; +import org.freedesktop.dbus.Error; +import org.freedesktop.dbus.Marshalling; +import org.freedesktop.dbus.Message; +import org.freedesktop.dbus.MessageReader; +import org.freedesktop.dbus.MessageWriter; +import org.freedesktop.dbus.MethodCall; +import org.freedesktop.dbus.MethodReturn; +import org.freedesktop.dbus.Transport; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.FatalException; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.unix.UnixServerSocket; +import cx.ath.matthew.unix.UnixSocket; +import cx.ath.matthew.unix.UnixSocketAddress; + +/** + * A replacement DBusDaemon + */ +public class DBusDaemon extends Thread +{ + public static final int QUEUE_POLL_WAIT = 500; + static class Connstruct + { + public UnixSocket usock; + public Socket tsock; + public MessageReader min; + public MessageWriter mout; + public String unique; + public Connstruct(UnixSocket sock) + { + this.usock = sock; + min = new MessageReader(sock.getInputStream()); + mout = new MessageWriter(sock.getOutputStream()); + } + public Connstruct(Socket sock) throws IOException + { + this.tsock = sock; + min = new MessageReader(sock.getInputStream()); + mout = new MessageWriter(sock.getOutputStream()); + } + public String toString() + { + return null == unique ? ":?-?" : unique; + } + } + static class MagicMap + { + private Map> m; + private LinkedList q; + private String name; + public MagicMap(String name) + { + m = new HashMap>(); + q = new LinkedList(); + this.name = name; + } + public A head() + { + return q.getFirst(); + } + public void putFirst(A a, B b) + { + if (Debug.debug) Debug.print("<"+name+"> Queueing {"+a+" => "+b+"}"); + if (m.containsKey(a)) + m.get(a).add(b); + else { + LinkedList l = new LinkedList(); + l.add(b); + m.put(a, l); + } + q.addFirst(a); + } + public void putLast(A a, B b) + { + if (Debug.debug) Debug.print("<"+name+"> Queueing {"+a+" => "+b+"}"); + if (m.containsKey(a)) + m.get(a).add(b); + else { + LinkedList l = new LinkedList(); + l.add(b); + m.put(a, l); + } + q.addLast(a); + } + public List remove(A a) + { + if (Debug.debug) Debug.print("<"+name+"> Removing {"+a+"}"); + q.remove(a); + return m.remove(a); + } + public int size() + { + return q.size(); + } + } + public class DBusServer extends Thread implements DBus, DBus.Introspectable, DBus.Peer + { + public DBusServer() + { + setName("Server"); + } + public Connstruct c; + public Message m; + public boolean isRemote() { return false; } + public String Hello() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + synchronized (c) { + if (null != c.unique) + throw new org.freedesktop.DBus.Error.AccessDenied($_("Connection has already sent a Hello message")); + synchronized (unique_lock) { + c.unique = ":1."+(++next_unique); + } + } + synchronized (names) { + names.put(c.unique, c); + } + + if (Debug.debug) Debug.print(Debug.WARN, "Client "+c.unique+" registered"); + + try { + send(c, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameAcquired", "s", c.unique)); + DBusSignal s = new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", c.unique, "", c.unique); + send(null, s); + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return c.unique; + } + public String[] ListNames() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + String[] ns; + synchronized (names) { + Set nss = names.keySet(); + ns = nss.toArray(new String[0]); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return ns; + } + + public boolean NameHasOwner(String name) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + boolean rv; + synchronized (names) { + rv = names.containsKey(name); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return rv; + } + public String GetNameOwner(String name) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + Connstruct owner = names.get(name); + String o; + if (null == owner) + o = ""; + else + o = owner.unique; + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return o; + } + + public UInt32 GetConnectionUnixUser(String connection_name) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new UInt32(0); + } + public UInt32 StartServiceByName(String name, UInt32 flags) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new UInt32(0); + } + public UInt32 RequestName(String name, UInt32 flags) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + + boolean exists = false; + synchronized (names) { + if (!(exists = names.containsKey(name))) + names.put(name, c); + } + + int rv; + if (exists) { + rv = DBus.DBUS_REQUEST_NAME_REPLY_EXISTS; + } else { + if (Debug.debug) Debug.print(Debug.WARN, "Client "+c.unique+" acquired name "+name); + rv = DBus.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER; + try { + send(c, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameAcquired", "s", name)); + send(null, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", name, "", c.unique)); + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + } + } + + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new UInt32(rv); + } + + public UInt32 ReleaseName(String name) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + + boolean exists = false; + synchronized (names) { + if ((exists = (names.containsKey(name) && names.get(name).equals(c)))) + names.remove(name); + } + + int rv; + if (!exists) { + rv = DBus.DBUS_RELEASE_NAME_REPLY_NON_EXISTANT; + } else { + if (Debug.debug) Debug.print(Debug.WARN, "Client "+c.unique+" acquired name "+name); + rv = DBus.DBUS_RELEASE_NAME_REPLY_RELEASED; + try { + send(c, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameLost", "s", name)); + send(null, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", name, c.unique, "")); + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + } + } + + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new UInt32(rv); + } + public void AddMatch(String matchrule) throws Error.MatchRuleInvalid + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding match rule: "+matchrule); + synchronized (sigrecips) { + if (!sigrecips.contains(c)) + sigrecips.add(c); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return; + } + public void RemoveMatch(String matchrule) throws Error.MatchRuleInvalid + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Removing match rule: "+matchrule); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return; + } + public String[] ListQueuedOwners(String name) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new String[0]; + } + public UInt32 GetConnectionUnixProcessID(String connection_name) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new UInt32(0); + } + public Byte[] GetConnectionSELinuxSecurityContext(String a) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new Byte[0]; + } + public void ReloadConfig() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return; + } + @SuppressWarnings("unchecked") + private void handleMessage(Connstruct c, Message m) throws DBusException + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Handling message "+m+" from "+c.unique); + if (!(m instanceof MethodCall)) return; + Object[] args = m.getParameters(); + + Class[] cs = new Class[args.length]; + + for (int i = 0; i < cs.length; i++) + cs[i] = args[i].getClass(); + + java.lang.reflect.Method meth = null; + Object rv = null; + + try { + meth = DBusServer.class.getMethod(m.getName(), cs); + try { + this.c = c; + this.m = m; + rv = meth.invoke(dbus_server, args); + if (null == rv) { + send(c, new MethodReturn("org.freedesktop.DBus", (MethodCall) m, null), true); + } else { + String sig = Marshalling.getDBusType(meth.getGenericReturnType())[0]; + send(c, new MethodReturn("org.freedesktop.DBus", (MethodCall) m, sig, rv), true); + } + } catch (InvocationTargetException ITe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, ITe); + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, ITe.getCause()); + send(c, new org.freedesktop.dbus.Error("org.freedesktop.DBus", m, ITe.getCause())); + } catch (DBusExecutionException DBEe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBEe); + send(c, new org.freedesktop.dbus.Error("org.freedesktop.DBus", m, DBEe)); + } catch (Exception e) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, e); + send(c,new org.freedesktop.dbus.Error("org.freedesktop.DBus", c.unique, "org.freedesktop.DBus.Error.GeneralError", m.getSerial(), "s", $_("An error occurred while calling ")+m.getName())); + } + } catch (NoSuchMethodException NSMe) { + send(c,new org.freedesktop.dbus.Error("org.freedesktop.DBus", c.unique, "org.freedesktop.DBus.Error.UnknownMethod", m.getSerial(), "s", $_("This service does not support ")+m.getName())); + } + + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + public String Introspect() + { + return "\n"+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + ""; + } + public void Ping() {} + + public void run() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + while (_run) { + Message m; + List> wcs; + // block on outqueue + synchronized (localqueue) { + while (localqueue.size() == 0) try { + localqueue.wait(); + } catch (InterruptedException Ie) { } + m = localqueue.head(); + wcs = localqueue.remove(m); + } + if (null != wcs) try { + for (WeakReference wc: wcs) { + Connstruct c = wc.get(); + if (null != c) { + if (Debug.debug) Debug.print(Debug.VERBOSE, " Got message "+m+" from "+c); + handleMessage(c, m); + } + } + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + } + else if (Debug.debug) Debug.print(Debug.INFO, "Discarding "+m+" connection reaped"); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + } + public class Sender extends Thread + { + public Sender() + { + setName("Sender"); + } + public void run() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + while (_run) { + + if (Debug.debug) Debug.print(Debug.VERBOSE, "Acquiring lock on outqueue and blocking for data"); + Message m = null; + List> wcs = null; + // block on outqueue + synchronized (outqueue) { + while (outqueue.size() == 0) try { + outqueue.wait(); + } catch (InterruptedException Ie) { } + + m = outqueue.head(); + wcs = outqueue.remove(m); + } + if (null != wcs) { + for (WeakReference wc: wcs) { + Connstruct c = wc.get(); + if (null != c) { + if (Debug.debug) Debug.print(Debug.VERBOSE, " Got message "+m+" for "+c.unique); + if (Debug.debug) Debug.print(Debug.INFO, "Sending message "+m+" to "+c.unique); + try { + c.mout.writeMessage(m); + } + catch (IOException IOe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe); + removeConnection(c); + } + } + } + } + else if (Debug.debug) Debug.print(Debug.INFO, "Discarding "+m+" connection reaped"); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + } + public class Reader extends Thread + { + private Connstruct conn; + private WeakReference weakconn; + private boolean _lrun = true; + public Reader(Connstruct conn) + { + this.conn = conn; + weakconn = new WeakReference(conn); + setName("Reader"); + } + public void stopRunning() + { + _lrun = false; + } + public void run() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + while (_run && _lrun) { + + Message m = null; + try { + m = conn.min.readMessage(); + } catch (IOException IOe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe); + removeConnection(conn); + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + if (DBe instanceof FatalException) + removeConnection(conn); + } + + if (null != m) { + if (Debug.debug) Debug.print(Debug.INFO, "Read "+m+" from "+conn.unique); + synchronized (inqueue) { + inqueue.putLast(m, weakconn); + inqueue.notifyAll(); + } + } + } + conn = null; + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + } + + private Map conns = new HashMap(); + private HashMap names = new HashMap(); + private MagicMap> outqueue = new MagicMap>("out"); + private MagicMap> inqueue = new MagicMap>("in"); + private MagicMap> localqueue = new MagicMap>("local"); + private List sigrecips = new Vector(); + private boolean _run = true; + private int next_unique = 0; + private Object unique_lock = new Object(); + DBusServer dbus_server = new DBusServer(); + Sender sender = new Sender(); + + public DBusDaemon() + { + setName("Daemon"); + synchronized (names) { + names.put("org.freedesktop.DBus", null); + } + } + @SuppressWarnings("unchecked") + private void send(Connstruct c, Message m) + { + send (c, m, false); + } + private void send(Connstruct c, Message m, boolean head) + { + if (Debug.debug){ + Debug.print(Debug.DEBUG, "enter"); + if (null == c) + Debug.print(Debug.VERBOSE, "Queing message "+m+" for all connections"); + else + Debug.print(Debug.VERBOSE, "Queing message "+m+" for "+c.unique); + } + // send to all connections + if (null == c) { + synchronized (conns) { + synchronized (outqueue) { + for (Connstruct d: conns.keySet()) + if (head) + outqueue.putFirst(m, new WeakReference(d)); + else + outqueue.putLast(m, new WeakReference(d)); + outqueue.notifyAll(); + } + } + } else { + synchronized (outqueue) { + if (head) + outqueue.putFirst(m, new WeakReference(c)); + else + outqueue.putLast(m, new WeakReference(c)); + outqueue.notifyAll(); + } + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + @SuppressWarnings("unchecked") + private List findSignalMatches(DBusSignal sig) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + List l; + synchronized (sigrecips) { + l = new Vector(sigrecips); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return l; + } + @SuppressWarnings("unchecked") + public void run() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + while (_run) { + try { + Message m; + List> wcs; + synchronized (inqueue) { + while (0 == inqueue.size()) try { + inqueue.wait(); + } catch (InterruptedException Ie) {} + + m = inqueue.head(); + wcs = inqueue.remove(m); + } + if (null != wcs) { + for (WeakReference wc: wcs) { + Connstruct c = wc.get(); + if (null != c) { + if (Debug.debug) Debug.print(Debug.INFO, " Got message "+m+" from "+c.unique); + // check if they have hello'd + if (null == c.unique + && (!(m instanceof MethodCall) + || !"org.freedesktop.DBus".equals(m.getDestination()) + || !"Hello".equals(m.getName()))) { + send(c,new Error("org.freedesktop.DBus", null, "org.freedesktop.DBus.Error.AccessDenied", m.getSerial(), "s", $_("You must send a Hello message"))); + } else { + try { + if (null != c.unique) m.setSource(c.unique); + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + send(c,new Error("org.freedesktop.DBus", null, "org.freedesktop.DBus.Error.GeneralError", m.getSerial(), "s", $_("Sending message failed"))); + } + + if ("org.freedesktop.DBus".equals(m.getDestination())) { + synchronized (localqueue) { + localqueue.putLast(m, wc); + localqueue.notifyAll(); + } + } else { + if (m instanceof DBusSignal) { + List list = findSignalMatches((DBusSignal) m); + for (Connstruct d: list) + send(d, m); + } else { + Connstruct dest = names.get(m.getDestination()); + + if (null == dest) { + send(c, new Error("org.freedesktop.DBus", null, "org.freedesktop.DBus.Error.ServiceUnknown", m.getSerial(), "s", MessageFormat.format($_("The name `{0}' does not exist"), new Object[] { m.getDestination() }))); + } else + send(dest, m); + } + } + } + } + } + } + } + catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + } + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + private void removeConnection(Connstruct c) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + boolean exists; + synchronized(conns) { + if ((exists = conns.containsKey(c))) { + Reader r = conns.get(c); + r.stopRunning(); + conns.remove(c); + } + } + if (exists) { + try { + if (null != c.usock) c.usock.close(); + if (null != c.tsock) c.tsock.close(); + } catch (IOException IOe) {} + synchronized(names) { + List toRemove = new Vector(); + for (String name: names.keySet()) + if (names.get(name) == c) { + toRemove.add(name); + try { + send(null, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", name, c.unique, "")); + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + } + } + for (String name: toRemove) + names.remove(name); + } + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + public void addSock(UnixSocket us) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.WARN, "New Client"); + Connstruct c = new Connstruct(us); + Reader r = new Reader(c); + synchronized (conns) { + conns.put(c, r); + } + r.start(); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + public void addSock(Socket s) throws IOException + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.WARN, "New Client"); + Connstruct c = new Connstruct(s); + Reader r = new Reader(c); + synchronized (conns) { + conns.put(c, r); + } + r.start(); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + public static void syntax() + { + System.out.println("Syntax: DBusDaemon [--version] [-v] [--help] [-h] [--listen address] [-l address] [--print-address] [-r] [--pidfile file] [-p file] [--addressfile file] [-a file] [--unix] [-u] [--tcp] [-t] "); + System.exit(1); + } + public static void version() + { + System.out.println("D-Bus Java Version: "+System.getProperty("Version")); + System.exit(1); + } + public static void saveFile(String data, String file) throws IOException + { + PrintWriter w = new PrintWriter(new FileOutputStream(file)); + w.println(data); + w.close(); + } + public static void main(String args[]) throws Exception + { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.DEBUG, "enter"); + else if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + String addr = null; + String pidfile = null; + String addrfile = null; + boolean printaddress = false; + boolean unix = true; + boolean tcp = false; + + // parse options + try { + for (int i=0; i < args.length; i++) + if ("--help".equals(args[i]) || "-h".equals(args[i])) + syntax(); + else if ("--version".equals(args[i]) || "-v".equals(args[i])) + version(); + else if ("--listen".equals(args[i]) || "-l".equals(args[i])) + addr = args[++i]; + else if ("--pidfile".equals(args[i]) || "-p".equals(args[i])) + pidfile = args[++i]; + else if ("--addressfile".equals(args[i]) || "-a".equals(args[i])) + addrfile = args[++i]; + else if ("--print-address".equals(args[i]) || "-r".equals(args[i])) + printaddress = true; + else if ("--unix".equals(args[i]) || "-u".equals(args[i])) { + unix = true; + tcp = false; + } else if ("--tcp".equals(args[i]) || "-t".equals(args[i])) { + tcp = true; + unix = false; + } else syntax(); + } catch (ArrayIndexOutOfBoundsException AIOOBe) { + syntax(); + } + + // generate a random address if none specified + if (null == addr && unix) addr = DirectConnection.createDynamicSession(); + else if (null == addr && tcp) addr = DirectConnection.createDynamicTCPSession(); + + BusAddress address = new BusAddress(addr); + if (null == address.getParameter("guid")) { + addr += ",guid="+Transport.genGUID(); + address = new BusAddress(addr); + } + + // print address to stdout + if (printaddress) System.out.println(addr); + + // print address to file + if (null != addrfile) saveFile(addr, addrfile); + + // print PID to file + if (null != pidfile) saveFile(System.getProperty("Pid"), pidfile); + + // start the daemon + if (Debug.debug) Debug.print(Debug.WARN, "Binding to "+addr); + if ("unix".equals(address.getType())) + doUnix(address); + else if ("tcp".equals(address.getType())) + doTCP(address); + else throw new Exception("Unknown address type: "+address.getType()); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + private static void doUnix(BusAddress address) throws IOException + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + UnixServerSocket uss; + if (null != address. getParameter("abstract")) + uss = new UnixServerSocket(new UnixSocketAddress(address.getParameter("abstract"), true)); + else + uss = new UnixServerSocket(new UnixSocketAddress(address.getParameter("path"), false)); + DBusDaemon d = new DBusDaemon(); + d.start(); + d.sender.start(); + d.dbus_server.start(); + + // accept new connections + while (d._run) { + UnixSocket s = uss.accept(); + if ((new Transport.SASL()).auth(Transport.SASL.MODE_SERVER, Transport.SASL.AUTH_EXTERNAL, address.getParameter("guid"), s.getOutputStream(), s.getInputStream(), s)) { + // s.setBlocking(false); + d.addSock(s); + } else + s.close(); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + private static void doTCP(BusAddress address) throws IOException + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + ServerSocket ss = new ServerSocket(Integer.parseInt(address.getParameter("port")),10, InetAddress.getByName(address.getParameter("host"))); + DBusDaemon d = new DBusDaemon(); + d.start(); + d.sender.start(); + d.dbus_server.start(); + + // accept new connections + while (d._run) { + Socket s = ss.accept(); + boolean authOK=false; + try { + authOK = (new Transport.SASL()).auth(Transport.SASL.MODE_SERVER, Transport.SASL.AUTH_EXTERNAL, address.getParameter("guid"), s.getOutputStream(), s.getInputStream(), null); + } catch (Exception e) { + if (Debug.debug) Debug. print(Debug.DEBUG, e); + } + if (authOK) { + d.addSock(s); + } else + s.close(); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/IdentifierMangler.java b/app/src/main/java/org/freedesktop/dbus/bin/IdentifierMangler.java new file mode 100644 index 00000000..db596f41 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/IdentifierMangler.java @@ -0,0 +1,43 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.bin; + +import java.util.Arrays; + +/** + * Checks identifiers for keywords etc and mangles them if so. + */ +public class IdentifierMangler +{ + private static String[] keywords; + static { + keywords = new String[] { + "true","false","null", + "abstract","continue","for","new","switch", + "assert","default","goto","package","synchronized", + "boolean","do","if","private","this", + "break","double","implements","protected","throw", + "byte","else","import","public","throws", + "case","enum","instanceof","return","transient", + "catch","extends","int","short","try", + "char","final","interface","static","void", + "class","finally","long","strictfp","volatile", + "const","float","native","super","while" + }; + Arrays.sort(keywords); + } + public static String mangle(String name) + { + if (Arrays.binarySearch(keywords, name) >= 0) + name = "_"+name; + return name; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/IterableNodeList.java b/app/src/main/java/org/freedesktop/dbus/bin/IterableNodeList.java new file mode 100644 index 00000000..ddd5884b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/IterableNodeList.java @@ -0,0 +1,29 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.bin; + +import java.util.Iterator; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +class IterableNodeList implements Iterable +{ + private NodeList nl; + public IterableNodeList(NodeList nl) + { + this.nl = nl; + } + public Iterator iterator() + { + return new NodeListIterator(nl); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/ListDBus.java b/app/src/main/java/org/freedesktop/dbus/bin/ListDBus.java new file mode 100644 index 00000000..93d4ad59 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/ListDBus.java @@ -0,0 +1,74 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.bin; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +/** + * This class lists all the names currently connected on the bus + */ +public class ListDBus +{ + public static void syntax() + { + System.out.println("Syntax: ListDBus [--version] [-v] [--help] [-h] [--owners] [-o] [--uids] [-u] [--session] [-s] [--system] [-y]"); + System.exit(1); + } + public static void version() + { + System.out.println("Java D-Bus Version "+System.getProperty("Version")); + System.exit(1); + } + public static void main(String args[]) throws Exception + { + boolean owners = false; + boolean users = false; + int connection = DBusConnection.SESSION; + + for (String a: args) + if ("--help".equals(a)) syntax(); + else if ("-h".equals(a)) syntax(); + else if ("--version".equals(a)) version(); + else if ("-v".equals(a)) version(); + else if ("-u".equals(a)) users = true; + else if ("--uids".equals(a)) users = true; + else if ("-o".equals(a)) owners = true; + else if ("--owners".equals(a)) owners = true; + else if ("--session".equals(a)) connection = DBusConnection.SESSION; + else if ("-s".equals(a)) connection = DBusConnection.SESSION; + else if ("--system".equals(a)) connection = DBusConnection.SYSTEM; + else if ("-y".equals(a)) connection = DBusConnection.SYSTEM; + else syntax(); + + DBusConnection conn = DBusConnection.getConnection(connection); + DBus dbus = conn.getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); + String[] names = dbus.ListNames(); + for (String s: names) { + if (users) + try { + System.out.print(dbus.GetConnectionUnixUser(s)+"\t"); + } catch (DBusExecutionException DBEe) { + System.out.print("\t"); + } + System.out.print(s); + if (!s.startsWith(":") && owners) { + try { + System.out.print("\t"+dbus.GetNameOwner(s)); + } catch (DBusExecutionException DBEe) { + } + } + System.out.println(); + } + conn.disconnect(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/NodeListIterator.java b/app/src/main/java/org/freedesktop/dbus/bin/NodeListIterator.java new file mode 100644 index 00000000..ea2d23a1 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/NodeListIterator.java @@ -0,0 +1,38 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.bin; + +import java.util.Iterator; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +class NodeListIterator implements Iterator +{ + NodeList nl; + int i; + NodeListIterator(NodeList nl) + { + this.nl = nl; + i = 0; + } + public boolean hasNext() + { + return i < nl.getLength(); + } + public Node next() + { + Node n = nl.item(i); + i++; + return n; + } + public void remove() {}; +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/StructStruct.java b/app/src/main/java/org/freedesktop/dbus/bin/StructStruct.java new file mode 100644 index 00000000..f302f6dc --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/StructStruct.java @@ -0,0 +1,54 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.bin; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.HashMap; + +class StructStruct +{ + public static Map fillPackages(Map structs, String pack) + { + Map newmap = new HashMap(); + for (StructStruct ss: structs.keySet()) { + Type[] type = structs.get(ss); + if (null == ss.pack) ss.pack = pack; + newmap.put(ss, type); + } + return newmap; + } + public String name; + public String pack; + public StructStruct(String name) + { + this.name = name; + } + public StructStruct(String name, String pack) + { + this.name = name; + this.pack = pack; + } + public int hashCode() + { + return name.hashCode(); + } + public boolean equals(Object o) + { + if (!(o instanceof StructStruct)) return false; + if (!name.equals(((StructStruct) o).name)) return false; + return true; + } + public String toString() + { + return "<"+name+", "+pack+">"; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java new file mode 100644 index 00000000..15f46d0c --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java @@ -0,0 +1,26 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +/** + * An exception within DBus. + */ +@SuppressWarnings("serial") +public class DBusException extends Exception +{ + /** + * Create an exception with the specified message + */ + public DBusException(String message) + { + super(message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java new file mode 100644 index 00000000..7dd54fd0 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java @@ -0,0 +1,40 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +/** + * An exception while running a remote method within DBus. + */ +@SuppressWarnings("serial") +public class DBusExecutionException extends RuntimeException +{ + private String type; + /** + * Create an exception with the specified message + */ + public DBusExecutionException(String message) + { + super(message); + } + public void setType(String type) + { + this.type = type; + } + /** + * Get the DBus type of this exception. Use if this + * was an exception we don't have a class file for. + */ + public String getType() + { + if (null == type) return getClass().getName(); + else return type; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java new file mode 100644 index 00000000..6f8d4010 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java @@ -0,0 +1,20 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +@SuppressWarnings("serial") +public class FatalDBusException extends DBusException implements FatalException +{ + public FatalDBusException(String message) + { + super(message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java new file mode 100644 index 00000000..d69efa5a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java @@ -0,0 +1,15 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +public interface FatalException +{ +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java new file mode 100644 index 00000000..4113bca8 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java @@ -0,0 +1,20 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +@SuppressWarnings("serial") +public class InternalMessageException extends DBusExecutionException implements NonFatalException +{ + public InternalMessageException(String message) + { + super (message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java new file mode 100644 index 00000000..cf60d3bd --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java @@ -0,0 +1,20 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +@SuppressWarnings("serial") +public class MarshallingException extends DBusException implements NonFatalException +{ + public MarshallingException(String message) + { + super(message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java new file mode 100644 index 00000000..b7efcf7b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java @@ -0,0 +1,23 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +/** + * Thrown if a message is formatted incorrectly. + */ +@SuppressWarnings("serial") +public class MessageFormatException extends DBusException implements NonFatalException +{ + public MessageFormatException(String message) + { + super (message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java new file mode 100644 index 00000000..9f6e44e3 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java @@ -0,0 +1,22 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +import java.io.IOException; + +@SuppressWarnings("serial") +public class MessageProtocolVersionException extends IOException implements FatalException +{ + public MessageProtocolVersionException(String message) + { + super(message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java new file mode 100644 index 00000000..b36f30ee --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java @@ -0,0 +1,22 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +import java.io.IOException; + +@SuppressWarnings("serial") +public class MessageTypeException extends IOException implements NonFatalException +{ + public MessageTypeException(String message) + { + super(message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java new file mode 100644 index 00000000..dc565f2e --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java @@ -0,0 +1,15 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +public interface NonFatalException +{ +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java b/app/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java new file mode 100644 index 00000000..71b8d44a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java @@ -0,0 +1,23 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +/** + * Thrown if a DBus action is called when not connected to the Bus. + */ +@SuppressWarnings("serial") +public class NotConnected extends DBusExecutionException implements FatalException +{ + public NotConnected(String message) + { + super (message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java new file mode 100644 index 00000000..76fe2800 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java @@ -0,0 +1,21 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; +import static org.freedesktop.dbus.Gettext.$_; + +@SuppressWarnings("serial") +public class UnknownTypeCodeException extends DBusException implements NonFatalException +{ + public UnknownTypeCodeException(byte code) + { + super($_("Not a valid D-Bus type code: ") + code); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/ProfileStruct.java b/app/src/main/java/org/freedesktop/dbus/test/ProfileStruct.java new file mode 100644 index 00000000..9fa963b8 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/ProfileStruct.java @@ -0,0 +1,32 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.UInt32; + +public final class ProfileStruct extends Struct +{ + @Position(0) + public final String a; + @Position(1) + public final UInt32 b; + @Position(2) + public final long c; + + public ProfileStruct(String a, UInt32 b, long c) + { + this.a = a; + this.b = b; + this.c = c; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/Profiler.java b/app/src/main/java/org/freedesktop/dbus/test/Profiler.java new file mode 100644 index 00000000..efe9ab21 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/Profiler.java @@ -0,0 +1,40 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.DBus.Method.NoReply; +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.exceptions.DBusException; +import java.util.List; +import java.util.Map; + +public interface Profiler extends DBusInterface +{ + public class ProfileSignal extends DBusSignal + { + public ProfileSignal(String path) throws DBusException + { + super(path); + } + } + public void array(int[] v); + public void stringarray(String[] v); + public void map(Map m); + public void list(List l); + public void bytes(byte[] b); + public void struct(ProfileStruct ps); + public void string(String s); + public void NoReply(); + public void Pong(); +} + + diff --git a/app/src/main/java/org/freedesktop/dbus/test/ProfilerInstance.java b/app/src/main/java/org/freedesktop/dbus/test/ProfilerInstance.java new file mode 100644 index 00000000..b99d1b75 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/ProfilerInstance.java @@ -0,0 +1,28 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import java.util.List; +import java.util.Map; + +public class ProfilerInstance implements Profiler +{ + public boolean isRemote() { return false; } + public void array(int[] v) { return; } + public void stringarray(String[] v) { return; } + public void map(Map m) { return; } + public void list(List l) { return; } + public void bytes(byte[] b) { return; } + public void struct(ProfileStruct ps) { return; } + public void string(String s) { return; } + public void NoReply() { return; } + public void Pong() { return; } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestException.java b/app/src/main/java/org/freedesktop/dbus/test/TestException.java new file mode 100644 index 00000000..2314ec33 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestException.java @@ -0,0 +1,24 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.DBus.Description; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +@Description("A test exception to throw over DBus") +@SuppressWarnings("serial") +public class TestException extends DBusExecutionException +{ + public TestException(String message) + { + super (message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestNewInterface.java b/app/src/main/java/org/freedesktop/dbus/test/TestNewInterface.java new file mode 100644 index 00000000..4199403a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestNewInterface.java @@ -0,0 +1,26 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.DBus.Description; + +/** + * A sample remote interface which exports one method. + */ +public interface TestNewInterface extends DBusInterface +{ + /** + * A simple method with no parameters which returns a String + */ + @Description("Simple test method") + public String getName(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface.java b/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface.java new file mode 100644 index 00000000..a24ef43b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface.java @@ -0,0 +1,57 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.Path; +import org.freedesktop.dbus.UInt16; +import org.freedesktop.DBus.Description; +import org.freedesktop.DBus.Method; + +import java.lang.reflect.Type; + +import java.util.Map; +import java.util.List; +/** + * A sample remote interface which exports one method. + */ +public interface TestRemoteInterface extends DBusInterface +{ + /** + * A simple method with no parameters which returns a String + */ + @Description("Simple test method") + public String getName(); + public String getNameAndThrow(); + @Description("Test of nested maps") + public int frobnicate(List n, Map> m, T v); + @Description("Throws a TestException when called") + public void throwme() throws TestException; + @Description("Waits then doesn't return") + @Method.NoReply() + public void waitawhile(); + @Description("Interface-overloaded method") + public int overload(); + @Description("Testing Type Signatures") + public void sig(Type[] s); + @Description("Testing object paths as Path objects") + public void newpathtest(Path p); + @Description("Testing the float type") + public float testfloat(float[] f); + @Description("Testing structs of structs") + public int[][] teststructstruct(TestStruct3 in); + @Description("Regression test for #13291") + public void reg13291(byte[] as, byte[] bs); + /* test lots of things involving Path */ + public Path pathrv(Path a); + public List pathlistrv(List a); + public Map pathmaprv(Map a); +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface2.java b/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface2.java new file mode 100644 index 00000000..a44d6279 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface2.java @@ -0,0 +1,54 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusInterfaceName; +import org.freedesktop.dbus.DBusMemberName; +import org.freedesktop.dbus.Variant; +import org.freedesktop.DBus.Description; + +import java.util.List; + +@Description("An example remote interface") +@DBusInterfaceName("org.freedesktop.dbus.test.AlternateTestInterface") +public interface TestRemoteInterface2 extends DBusInterface +{ + @Description("Test multiple return values and implicit variant parameters.") + public TestTuple,Boolean> show(A in); + @Description("Test passing structs and explicit variants, returning implicit variants") + public T dostuff(TestStruct foo); + @Description("Test arrays, boxed arrays and lists.") + public List sampleArray(List l, Integer[] is, long[] ls); + @Description("Test passing objects as object paths.") + public DBusInterface getThis(DBusInterface t); + @Description("Test bools work") + @DBusMemberName("checkbool") + public boolean check(); + @Description("Test Serializable Object") + public TestSerializable testSerializable(byte b, TestSerializable s, int i); + @Description("Call another method on itself from within a call") + public String recursionTest(); + @Description("Parameter-overloaded method (string)") + public int overload(String s); + @Description("Parameter-overloaded method (byte)") + public int overload(byte b); + @Description("Parameter-overloaded method (void)") + public int overload(); + @Description("Nested List Check") + public List> checklist(List> lli); + @Description("Get new objects as object paths.") + public TestNewInterface getNew(); + @Description("Test Complex Variants") + public void complexv(Variant v); + @Description("Test Introspect on a different interface") + public String Introspect(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestSerializable.java b/app/src/main/java/org/freedesktop/dbus/test/TestSerializable.java new file mode 100644 index 00000000..80142990 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestSerializable.java @@ -0,0 +1,48 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import java.util.List; +import java.util.Vector; + +import org.freedesktop.dbus.DBusSerializable; +import org.freedesktop.dbus.exceptions.DBusException; + +public class TestSerializable implements DBusSerializable +{ + private int a; + private String b; + private Vector c; + public TestSerializable(int a, A b, Vector c) + { + this.a = a; + this.b = b.toString(); + this.c = c; + } + public TestSerializable() {} + public void deserialize(int a, String b, List c) + { + this.a = a; + this.b = b; + this.c = new Vector(c); + } + public Object[] serialize() throws DBusException + { + return new Object[] { a, b, c }; + } + public int getInt() { return a; } + public String getString() { return b; } + public Vector getVector() { return c; } + public String toString() + { + return "TestSerializable{"+a+","+b+","+c+"}"; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface.java b/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface.java new file mode 100644 index 00000000..406ce36c --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface.java @@ -0,0 +1,97 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.DBus.Description; +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusMemberName; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.Path; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; +import java.util.Map; + +/** + * A sample signal with two parameters + */ +@Description("Test interface containing signals") +public interface TestSignalInterface extends DBusInterface +{ + @Description("Test basic signal") + public static class TestSignal extends DBusSignal + { + public final String value; + public final UInt32 number; + /** + * Create a signal. + */ + public TestSignal(String path, String value, UInt32 number) throws DBusException + { + super(path, value, number); + this.value = value; + this.number = number; + } + } + public static class StringSignal extends DBusSignal + { + public final String aoeu; + public StringSignal(String path, String aoeu) throws DBusException + { + super(path, aoeu); + this.aoeu = aoeu; + } + } + public static class EmptySignal extends DBusSignal + { + public EmptySignal(String path) throws DBusException + { + super(path); + } + } + @Description("Test signal with arrays") + public static class TestArraySignal extends DBusSignal + { + public final List v; + public final Map m; + public TestArraySignal(String path, List v, Map m) throws DBusException + { + super(path, v, m); + this.v = v; + this.m = m; + } + } + @Description("Test signal sending an object path") + @DBusMemberName("TestSignalObject") + public static class TestObjectSignal extends DBusSignal + { + public final DBusInterface otherpath; + public TestObjectSignal(String path, DBusInterface otherpath) throws DBusException + { + super(path, otherpath); + this.otherpath = otherpath; + } + } + public static class TestPathSignal extends DBusSignal + { + public final Path otherpath; + public final List pathlist; + public final Map pathmap; + public TestPathSignal(String path, Path otherpath, List pathlist, Map pathmap) throws DBusException + { + super(path, otherpath, pathlist, pathmap); + this.otherpath = otherpath; + this.pathlist = pathlist; + this.pathmap = pathmap; + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface2.java b/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface2.java new file mode 100644 index 00000000..d5c9ac52 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface2.java @@ -0,0 +1,45 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.DBus.Description; +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusInterfaceName; +import org.freedesktop.dbus.DBusMemberName; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; + +/** + * A sample signal with two parameters + */ +@Description("Test interface containing signals") +@DBusInterfaceName("some.other.interface.Name") +public interface TestSignalInterface2 extends DBusInterface +{ + @Description("Test basic signal") + public static class TestRenamedSignal extends DBusSignal + { + public final String value; + public final UInt32 number; + /** + * Create a signal. + */ + public TestRenamedSignal(String path, String value, UInt32 number) throws DBusException + { + super(path, value, number); + this.value = value; + this.number = number; + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestStruct.java b/app/src/main/java/org/freedesktop/dbus/test/TestStruct.java new file mode 100644 index 00000000..ca5deacd --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestStruct.java @@ -0,0 +1,32 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.Variant; + +public final class TestStruct extends Struct +{ + @Position(0) + public final String a; + @Position(1) + public final UInt32 b; + @Position(2) + public final Variant c; + public TestStruct(String a, UInt32 b, Variant c) + { + this.a = a; + this.b = b; + this.c = c; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestStruct2.java b/app/src/main/java/org/freedesktop/dbus/test/TestStruct2.java new file mode 100644 index 00000000..5c8797b4 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestStruct2.java @@ -0,0 +1,31 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; + +public final class TestStruct2 extends Struct +{ + @Position(0) + public final List a; + @Position(1) + public final Variant b; + public TestStruct2(List a, Variant b) throws DBusException + { + this.a = a; + this.b = b; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestStruct3.java b/app/src/main/java/org/freedesktop/dbus/test/TestStruct3.java new file mode 100644 index 00000000..52edbe0a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestStruct3.java @@ -0,0 +1,30 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; + +public final class TestStruct3 extends Struct +{ + @Position(0) + public final TestStruct2 a; + @Position(1) + public final List> b; + public TestStruct3(TestStruct2 a, List> b) throws DBusException + { + this.a = a; + this.b = b; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestTuple.java b/app/src/main/java/org/freedesktop/dbus/test/TestTuple.java new file mode 100644 index 00000000..1cf507a6 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestTuple.java @@ -0,0 +1,30 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Tuple; + +public final class TestTuple extends Tuple +{ + @Position(0) + public final A a; + @Position(1) + public final B b; + @Position(2) + public final C c; + public TestTuple(A a, B b, C c) + { + this.a = a; + this.b = b; + this.c = c; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TwoPartInterface.java b/app/src/main/java/org/freedesktop/dbus/test/TwoPartInterface.java new file mode 100644 index 00000000..af214cff --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TwoPartInterface.java @@ -0,0 +1,29 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.exceptions.DBusException; + +public interface TwoPartInterface extends DBusInterface +{ + public TwoPartObject getNew(); + public class TwoPartSignal extends DBusSignal + { + public final TwoPartObject o; + public TwoPartSignal(String path, TwoPartObject o) throws DBusException + { + super (path, o); + this.o = o; + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TwoPartObject.java b/app/src/main/java/org/freedesktop/dbus/test/TwoPartObject.java new file mode 100644 index 00000000..3c7237b5 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TwoPartObject.java @@ -0,0 +1,18 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.DBusInterface; + +public interface TwoPartObject extends DBusInterface +{ + public String getName(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/cross_test_client.java b/app/src/main/java/org/freedesktop/dbus/test/cross_test_client.java new file mode 100644 index 00000000..ae8369b2 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/cross_test_client.java @@ -0,0 +1,513 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSigHandler; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.UInt16; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.UInt64; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.types.DBusMapType; + +import cx.ath.matthew.debug.Debug; + +public class cross_test_client implements DBus.Binding.TestClient, DBusSigHandler +{ + private DBusConnection conn; + private static Set passed = new TreeSet(); + private static Map> failed = new HashMap>(); + private static cross_test_client ctc; + static { + List l = new Vector(); + l.add("Signal never arrived"); + failed.put("org.freedesktop.DBus.Binding.TestSignals.Triggered", l); + l = new Vector(); + l.add("Method never called"); + failed.put("org.freedesktop.DBus.Binding.TestClient.Response", l); + } + public cross_test_client(DBusConnection conn) + { + this.conn = conn; + } + public boolean isRemote() { return false; } + public void handle(DBus.Binding.TestSignals.Triggered t) + { + failed.remove("org.freedesktop.DBus.Binding.TestSignals.Triggered"); + if (new UInt64(21389479283L).equals(t.a) && "/Test".equals(t.getPath())) + pass("org.freedesktop.DBus.Binding.TestSignals.Triggered"); + else if (!new UInt64(21389479283L).equals(t.a)) + fail("org.freedesktop.DBus.Binding.TestSignals.Triggered", "Incorrect signal content; expected 21389479283 got "+t.a); + else if (!"/Test".equals(t.getPath())) + fail("org.freedesktop.DBus.Binding.TestSignals.Triggered", "Incorrect signal source object; expected /Test got "+t.getPath()); + } + public void Response(UInt16 a, double b) + { + failed.remove("org.freedesktop.DBus.Binding.TestClient.Response"); + if (a.equals(new UInt16(15)) && (b == 12.5)) + pass("org.freedesktop.DBus.Binding.TestClient.Response"); + else + fail("org.freedesktop.DBus.Binding.TestClient.Response", "Incorrect parameters; expected 15, 12.5 got "+a+", "+b); + } + public static void pass(String test) + { + passed.add(test.replaceAll("[$]", ".")); + } + public static void fail(String test, String reason) + { + test = test.replaceAll("[$]", "."); + List reasons = failed.get(test); + if (null == reasons) { + reasons = new Vector(); + failed.put(test, reasons); + } + reasons.add(reason); + } + @SuppressWarnings("unchecked") + public static void test(Class iface, Object proxy, String method, Object rv, Object... parameters) + { + try { + Method[] ms = iface.getMethods(); + Method m = null; + for (Method t: ms) { + if (t.getName().equals(method)) + m = t; + } + Object o = m.invoke(proxy, parameters); + + String msg = "Incorrect return value; sent ( "; + if (null != parameters) + for (Object po: parameters) + if (null != po) + msg += collapseArray(po) + ","; + msg = msg.replaceAll(".$",");"); + msg += " expected "+collapseArray(rv)+" got "+collapseArray(o); + + if (null != rv && rv.getClass().isArray()) { + compareArray(iface.getName()+"."+method, rv,o); + } else if (rv instanceof Map) { + if (o instanceof Map) { + Map a = (Map) o; + Map b = (Map) rv; + if (a.keySet().size() != b.keySet().size()) { + fail(iface.getName()+"."+method, msg); + } else for (Object k: a.keySet()) + if (a.get(k) instanceof List) { + if (b.get(k) instanceof List) + if (setCompareLists((List) a.get(k), (List) b.get(k))) + ; + else + fail(iface.getName()+"."+method, msg); + else + fail(iface.getName()+"."+method, msg); + } else if (!a.get(k).equals(b.get(k))) { + fail(iface.getName()+"."+method, msg); + return; + } + pass(iface.getName()+"."+method); + } else + fail(iface.getName()+"."+method, msg); + } else { + if (o == rv || (o != null && o.equals(rv))) + pass(iface.getName()+"."+method); + else + fail(iface.getName()+"."+method, msg); + } + } catch (DBusExecutionException DBEe) { + DBEe.printStackTrace(); + fail(iface.getName()+"."+method, "Error occurred during execution: "+DBEe.getClass().getName()+" "+DBEe.getMessage()); + } catch (InvocationTargetException ITe) { + ITe.printStackTrace(); + fail(iface.getName()+"."+method, "Error occurred during execution: "+ITe.getCause().getClass().getName()+" "+ITe.getCause().getMessage()); + } catch (Exception e) { + e.printStackTrace(); + fail(iface.getName()+"."+method, "Error occurred during execution: "+e.getClass().getName()+" "+e.getMessage()); + } + } + @SuppressWarnings("unchecked") + public static String collapseArray(Object array) + { + if (null == array) return "null"; + if (array.getClass().isArray()) { + String s = "{ "; + for (int i = 0; i < Array.getLength(array); i++) + s += collapseArray(Array.get(array, i))+","; + s = s.replaceAll(".$"," }"); + return s; + } else if (array instanceof List) { + String s = "{ "; + for (Object o: (List) array) + s += collapseArray(o)+","; + s = s.replaceAll(".$"," }"); + return s; + } else if (array instanceof Map) { + String s = "{ "; + for (Object o: ((Map) array).keySet()) + s += collapseArray(o)+" => "+collapseArray(((Map) array).get(o))+","; + s = s.replaceAll(".$"," }"); + return s; + } else return array.toString(); + } + public static boolean setCompareLists(List a, List b) + { + if (a.size() != b.size()) return false; + for (Object v: a) + if (!b.contains(v)) return false; + return true; + } + @SuppressWarnings("unchecked") + public static List> PrimitizeRecurse(Object a, Type t) + { + List> vs = new Vector>(); + if (t instanceof ParameterizedType) { + Class c = (Class) ((ParameterizedType) t).getRawType(); + if (List.class.isAssignableFrom(c)) { + Object os; + if (a instanceof List) + os = ((List) a).toArray(); + else + os = a; + Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); + for (int i = 0; i < Array.getLength(os); i++) + vs.addAll(PrimitizeRecurse(Array.get(os, i), ts[0])); + } else if (Map.class.isAssignableFrom(c)) { + Object[] os = ((Map) a).keySet().toArray(); + Object[] ks = ((Map) a).values().toArray(); + Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); + for (int i = 0; i < ks.length; i++) + vs.addAll(PrimitizeRecurse(ks[i], ts[0])); + for (int i = 0; i < os.length; i++) + vs.addAll(PrimitizeRecurse(os[i], ts[1])); + } else if (Struct.class.isAssignableFrom(c)) { + Object[] os = ((Struct) a).getParameters(); + Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); + for (int i = 0; i < os.length; i++) + vs.addAll(PrimitizeRecurse(os[i], ts[i])); + + } else if (Variant.class.isAssignableFrom(c)) { + vs.addAll(PrimitizeRecurse(((Variant) a).getValue(), ((Variant) a).getType())); + } + } else if (Variant.class.isAssignableFrom((Class) t)) + vs.addAll(PrimitizeRecurse(((Variant) a).getValue(), ((Variant) a).getType())); + else if (t instanceof Class && ((Class) t).isArray()) { + Type t2 = ((Class) t).getComponentType(); + for (int i = 0; i < Array.getLength(a); i++) + vs.addAll(PrimitizeRecurse(Array.get(a, i), t2)); + } + else vs.add(new Variant(a)); + + return vs; + } + + @SuppressWarnings("unchecked") + public static List> Primitize(Variant a) + { + return PrimitizeRecurse(a.getValue(), a.getType()); + } + + @SuppressWarnings("unchecked") + public static void primitizeTest(DBus.Binding.Tests tests, Object input) + { + Variant in = new Variant(input); + List> vs = Primitize(in); + List> res; + + try { + + res = tests.Primitize(in); + if (setCompareLists(res, vs)) + pass("org.freedesktop.DBus.Binding.Tests.Primitize"); + else + fail("org.freedesktop.DBus.Binding.Tests.Primitize", "Wrong Return Value; expected "+collapseArray(vs)+" got "+collapseArray(res)); + + } catch (Exception e) { + if (Debug.debug) Debug.print(e); + fail("org.freedesktop.DBus.Binding.Tests.Primitize", "Exception occurred during test: ("+e.getClass().getName()+") "+e.getMessage()); + } + } + public static void doTests(DBus.Peer peer, DBus.Introspectable intro, DBus.Introspectable rootintro, DBus.Binding.Tests tests, DBus.Binding.SingleTests singletests) + { + Random r = new Random(); + int i; + test(DBus.Peer.class, peer, "Ping", null); + + try { if (intro.Introspect().startsWith("(new Integer(1)), new Variant(new Integer(1))); + test(DBus.Binding.Tests.class, tests, "Identity", new Variant("Hello"), new Variant("Hello")); + + test(DBus.Binding.Tests.class, tests, "IdentityBool", false, false); + test(DBus.Binding.Tests.class, tests, "IdentityBool", true, true); + + test(DBus.Binding.Tests.class, tests, "Invert", false, true); + test(DBus.Binding.Tests.class, tests, "Invert", true, false); + + test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) 0, (byte) 0); + test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) 1, (byte) 1); + test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) -1, (byte) -1); + test(DBus.Binding.Tests.class, tests, "IdentityByte", Byte.MAX_VALUE, Byte.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityByte", Byte.MIN_VALUE, Byte.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) i, (byte) i); + + test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) 0, (short) 0); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) 1, (short) 1); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) -1, (short) -1); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", Short.MAX_VALUE, Short.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", Short.MIN_VALUE, Short.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) i, (short) i); + + test(DBus.Binding.Tests.class, tests, "IdentityInt32", 0, 0); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", 1, 1); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", -1, -1); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", Integer.MAX_VALUE, Integer.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", Integer.MIN_VALUE, Integer.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", i, i); + + + test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) 0, (long) 0); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) 1, (long) 1); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) -1, (long) -1); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", Long.MAX_VALUE, Long.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", Long.MIN_VALUE, Long.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) i, (long) i); + + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(0), new UInt16(0)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(1), new UInt16(1)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(UInt16.MAX_VALUE), new UInt16(UInt16.MAX_VALUE)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(UInt16.MIN_VALUE), new UInt16(UInt16.MIN_VALUE)); + i = r.nextInt(); + i = i > 0 ? i : -i; + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(i%UInt16.MAX_VALUE), new UInt16(i%UInt16.MAX_VALUE)); + + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(0), new UInt32(0)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(1), new UInt32(1)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(UInt32.MAX_VALUE), new UInt32(UInt32.MAX_VALUE)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(UInt32.MIN_VALUE), new UInt32(UInt32.MIN_VALUE)); + i = r.nextInt(); + i = i > 0 ? i : -i; + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(i%UInt32.MAX_VALUE), new UInt32(i%UInt32.MAX_VALUE)); + + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(0), new UInt64(0)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(1), new UInt64(1)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MAX_LONG_VALUE), new UInt64(UInt64.MAX_LONG_VALUE)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MAX_BIG_VALUE), new UInt64(UInt64.MAX_BIG_VALUE)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MIN_VALUE), new UInt64(UInt64.MIN_VALUE)); + i = r.nextInt(); + i = i > 0 ? i : -i; + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(i%UInt64.MAX_LONG_VALUE), new UInt64(i%UInt64.MAX_LONG_VALUE)); + + test(DBus.Binding.Tests.class, tests, "IdentityDouble", 0.0, 0.0); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", 1.0, 1.0); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", -1.0, -1.0); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", Double.MAX_VALUE, Double.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", Double.MIN_VALUE, Double.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", (double) i, (double) i); + + test(DBus.Binding.Tests.class, tests, "IdentityString", "", ""); + test(DBus.Binding.Tests.class, tests, "IdentityString", "The Quick Brown Fox Jumped Over The Lazy Dog", "The Quick Brown Fox Jumped Over The Lazy Dog"); + test(DBus.Binding.Tests.class, tests, "IdentityString", "ひらがなゲーム - かなぶん", "ひらがなゲーム - かなぶん"); + + testArray(DBus.Binding.Tests.class, tests, "IdentityBoolArray", Boolean.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityByteArray", Byte.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityInt16Array", Short.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityInt32Array", Integer.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityInt64Array", Long.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityDoubleArray", Double.TYPE, null); + + testArray(DBus.Binding.Tests.class, tests, "IdentityArray", Variant.class, new Variant("aoeu")); + testArray(DBus.Binding.Tests.class, tests, "IdentityUInt16Array", UInt16.class, new UInt16(12)); + testArray(DBus.Binding.Tests.class, tests, "IdentityUInt32Array", UInt32.class, new UInt32(190)); + testArray(DBus.Binding.Tests.class, tests, "IdentityUInt64Array", UInt64.class, new UInt64(103948)); + testArray(DBus.Binding.Tests.class, tests, "IdentityStringArray", String.class, "asdf"); + + int[] is = new int[0]; + test(DBus.Binding.Tests.class, tests, "Sum", 0L, is); + r = new Random(); + int len = (r.nextInt() % 100) + 15; + len = (len<0 ? -len: len)+15; + is = new int[len]; + long result = 0; + for (i = 0; i < len; i++) { + is[i] = r.nextInt(); + result += is[i]; + } + test(DBus.Binding.Tests.class, tests, "Sum", result, is); + + byte[] bs = new byte[0]; + test(DBus.Binding.SingleTests.class, singletests, "Sum", new UInt32(0), bs); + len = (r.nextInt() % 100); + len = (len<0 ? -len: len)+15; + bs = new byte[len]; + int res = 0; + for (i = 0; i < len; i++) { + bs[i] = (byte) r.nextInt(); + res += (bs[i] < 0 ? bs[i]+256 : bs[i]); + } + test(DBus.Binding.SingleTests.class, singletests, "Sum", new UInt32(res % (UInt32.MAX_VALUE+1)), bs); + + test(DBus.Binding.Tests.class, tests, "DeStruct", new DBus.Binding.Triplet("hi", new UInt32(12), new Short((short) 99)), new DBus.Binding.TestStruct("hi", new UInt32(12), new Short((short) 99))); + + Map in = new HashMap(); + Map> out = new HashMap>(); + test(DBus.Binding.Tests.class, tests, "InvertMapping", out, in); + + in.put("hi", "there"); + in.put("to", "there"); + in.put("from", "here"); + in.put("in", "out"); + List l = new Vector(); + l.add("hi"); + l.add("to"); + out.put("there", l); + l = new Vector(); + l.add("from"); + out.put("here", l); + l = new Vector(); + l.add("in"); + out.put("out", l); + test(DBus.Binding.Tests.class, tests, "InvertMapping", out, in); + + primitizeTest(tests, new Integer(1)); + primitizeTest(tests, + new Variant>>>( + new Variant>>( + new Variant>( + new Variant("Hi"))))); + primitizeTest(tests, new Variant>(in, new DBusMapType(String.class,String.class))); + + test(DBus.Binding.Tests.class, tests, "Trigger", null, "/Test", new UInt64(21389479283L)); + + try { + ctc.conn.sendSignal(new DBus.Binding.TestClient.Trigger("/Test", new UInt16(15), 12.5)); + } catch (DBusException DBe) { + if (Debug.debug) Debug.print(DBe); + throw new DBusExecutionException(DBe.getMessage()); + } + + try { Thread.sleep(10000); } catch (InterruptedException Ie) {} + + test(DBus.Binding.Tests.class, tests, "Exit", null); + } + public static void testArray(Class iface, Object proxy, String method, Class arrayType, Object content) + { + Object array = Array.newInstance(arrayType, 0); + test(iface, proxy, method, array, array); + Random r = new Random(); + int l = (r.nextInt() % 100); + array = Array.newInstance(arrayType, (l < 0 ? -l : l) + 15); + if (null != content) + Arrays.fill((Object[]) array, content); + test(iface, proxy, method, array, array); + } + public static void compareArray(String test, Object a, Object b) + { + if (!a.getClass().equals(b.getClass())) { + fail(test, "Incorrect return type; expected "+a.getClass()+" got "+b.getClass()); + return; + } + boolean pass = false; + + if (a instanceof Object[]) + pass = Arrays.equals((Object[]) a, (Object[]) b); + else if (a instanceof byte[]) + pass = Arrays.equals((byte[]) a, (byte[]) b); + else if (a instanceof boolean[]) + pass = Arrays.equals((boolean[]) a, (boolean[]) b); + else if (a instanceof int[]) + pass = Arrays.equals((int[]) a, (int[]) b); + else if (a instanceof short[]) + pass = Arrays.equals((short[]) a, (short[]) b); + else if (a instanceof long[]) + pass = Arrays.equals((long[]) a, (long[]) b); + else if (a instanceof double[]) + pass = Arrays.equals((double[]) a, (double[]) b); + + if (pass) + pass(test); + else { + String s = "Incorrect return value; expected "; + s += collapseArray(a); + s += " got "; + s += collapseArray(b); + fail(test, s); + } + } + + public static void main(String[] args) + { try { + /* init */ + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + ctc = new cross_test_client(conn); + conn.exportObject("/Test", ctc); + conn.addSigHandler(DBus.Binding.TestSignals.Triggered.class, ctc); + DBus.Binding.Tests tests = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Binding.Tests.class); + DBus.Binding.SingleTests singletests = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Binding.SingleTests.class); + DBus.Peer peer = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Peer.class); + DBus.Introspectable intro = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Introspectable.class); + + DBus.Introspectable rootintro = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/", DBus.Introspectable.class); + + doTests(peer, intro, rootintro, tests, singletests); + + /* report results */ + for (String s: passed) + System.out.println(s+" pass"); + int i = 1; + for (String s: failed.keySet()) + for (String r: failed.get(s)) { + System.out.println(s+" fail "+i); + System.out.println("report "+i+": "+r); + i++; + } + + conn.disconnect(); + } catch (DBusException DBe) { + DBe.printStackTrace(); + System.exit(1); + }} +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/cross_test_server.java b/app/src/main/java/org/freedesktop/dbus/test/cross_test_server.java new file mode 100644 index 00000000..acfb2fa9 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/cross_test_server.java @@ -0,0 +1,344 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; +import java.util.Set; +import java.util.Vector; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.DBusSigHandler; +import org.freedesktop.dbus.UInt16; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.UInt64; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +public class cross_test_server implements DBus.Binding.Tests, DBus.Binding.SingleTests, DBusSigHandler +{ + private DBusConnection conn; + boolean run = true; + private Set done = new TreeSet(); + private Set notdone = new TreeSet(); + { + notdone.add("org.freedesktop.DBus.Binding.Tests.Identity"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityByte"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityBool"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityDouble"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityString"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityByteArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityStringArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Sum"); + notdone.add("org.freedesktop.DBus.Binding.SingleTests.Sum"); + notdone.add("org.freedesktop.DBus.Binding.Tests.InvertMapping"); + notdone.add("org.freedesktop.DBus.Binding.Tests.DeStruct"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Primitize"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Invert"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Trigger"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Exit"); + notdone.add("org.freedesktop.DBus.Binding.TestClient.Trigger"); + } + + public cross_test_server(DBusConnection conn) + { + this.conn = conn; + } + public boolean isRemote() { return false; } + @SuppressWarnings("unchecked") + @DBus.Description("Returns whatever it is passed") + public Variant Identity(Variant input) + { + done.add("org.freedesktop.DBus.Binding.Tests.Identity"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Identity"); + return new Variant(input.getValue()); + } + @DBus.Description("Returns whatever it is passed") + public byte IdentityByte(byte input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityByte"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityByte"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public boolean IdentityBool(boolean input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityBool"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityBool"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public short IdentityInt16(short input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt16"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public UInt16 IdentityUInt16(UInt16 input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt16"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public int IdentityInt32(int input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt32"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public UInt32 IdentityUInt32(UInt32 input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt32"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public long IdentityInt64(long input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt64"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public UInt64 IdentityUInt64(UInt64 input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt64"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public double IdentityDouble(double input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityDouble"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityDouble"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public String IdentityString(String input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityString"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityString"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public Variant[] IdentityArray(Variant[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityArray"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public byte[] IdentityByteArray(byte[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityByteArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityByteArray"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public boolean[] IdentityBoolArray(boolean[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public short[] IdentityInt16Array(short[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public UInt16[] IdentityUInt16Array(UInt16[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public int[] IdentityInt32Array(int[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public UInt32[] IdentityUInt32Array(UInt32[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public long[] IdentityInt64Array(long[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public UInt64[] IdentityUInt64Array(UInt64[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public double[] IdentityDoubleArray(double[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public String[] IdentityStringArray(String[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityStringArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityStringArray"); + return input; + } + @DBus.Description("Returns the sum of the values in the input list") + public long Sum(int[] a) + { + done.add("org.freedesktop.DBus.Binding.Tests.Sum"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Sum"); + long sum = 0; + for (int b: a) sum += b; + return sum; + } + @DBus.Description("Returns the sum of the values in the input list") + public UInt32 Sum(byte[] a) + { + done.add("org.freedesktop.DBus.Binding.SingleTests.Sum"); + notdone.remove("org.freedesktop.DBus.Binding.SingleTests.Sum"); + int sum = 0; + for (byte b: a) sum += (b < 0 ? b+256 : b); + return new UInt32(sum % (UInt32.MAX_VALUE+1)); + } + @DBus.Description("Given a map of A => B, should return a map of B => a list of all the As which mapped to B") + public Map> InvertMapping(Map a) + { + done.add("org.freedesktop.DBus.Binding.Tests.InvertMapping"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.InvertMapping"); + HashMap> m = new HashMap>(); + for (String s: a.keySet()) { + String b = a.get(s); + List l = m.get(b); + if (null == l) { + l = new Vector(); + m.put(b, l); + } + l.add(s); + } + return m; + } + @DBus.Description("This method returns the contents of a struct as separate values") + public DBus.Binding.Triplet DeStruct(DBus.Binding.TestStruct a) + { + done.add("org.freedesktop.DBus.Binding.Tests.DeStruct"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.DeStruct"); + return new DBus.Binding.Triplet(a.a, a.b, a.c); + } + @DBus.Description("Given any compound type as a variant, return all the primitive types recursively contained within as an array of variants") + @SuppressWarnings("unchecked") + public List> Primitize(Variant a) + { + done.add("org.freedesktop.DBus.Binding.Tests.Primitize"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Primitize"); + return cross_test_client.PrimitizeRecurse(a.getValue(), a.getType()); + } + @DBus.Description("inverts it's input") + public boolean Invert(boolean a) + { + done.add("org.freedesktop.DBus.Binding.Tests.Invert"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Invert"); + return !a; + } + @DBus.Description("triggers sending of a signal from the supplied object with the given parameter") + public void Trigger(String a, UInt64 b) + { + done.add("org.freedesktop.DBus.Binding.Tests.Trigger"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Trigger"); + try { + conn.sendSignal(new DBus.Binding.TestSignals.Triggered(a, b)); + } catch (DBusException DBe) { + throw new DBusExecutionException(DBe.getMessage()); + } + } + public void Exit() + { + done.add("org.freedesktop.DBus.Binding.Tests.Exit"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Exit"); + run = false; + synchronized (this) { + notifyAll(); + } + } + public void handle(DBus.Binding.TestClient.Trigger t) + { + done.add("org.freedesktop.DBus.Binding.TestClient.Trigger"); + notdone.remove("org.freedesktop.DBus.Binding.TestClient.Trigger"); + try { + DBus.Binding.TestClient cb = conn.getRemoteObject(t.getSource(), "/Test", DBus.Binding.TestClient.class); + cb.Response(t.a, t.b); + } catch (DBusException DBe) { + throw new DBusExecutionException(DBe.getMessage()); + } + } + + public static void main(String[] args) + { try { + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + conn.requestBusName("org.freedesktop.DBus.Binding.TestServer"); + cross_test_server cts = new cross_test_server(conn); + conn.addSigHandler(DBus.Binding.TestClient.Trigger.class, cts); + conn.exportObject("/Test", cts); + synchronized (cts) { + while (cts.run) { + try { + cts.wait(); + } catch (InterruptedException Ie) {} + } + } + for (String s: cts.done) + System.out.println(s+" ok"); + for (String s: cts.notdone) + System.out.println(s+" untested"); + conn.disconnect(); + System.exit(0); + } catch (DBusException DBe) { + DBe.printStackTrace(); + System.exit(1); + }} +} + diff --git a/app/src/main/java/org/freedesktop/dbus/test/profile.java b/app/src/main/java/org/freedesktop/dbus/test/profile.java new file mode 100644 index 00000000..f998fa2c --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/profile.java @@ -0,0 +1,381 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import java.util.Random; +import java.util.HashMap; +import java.util.Vector; + +import org.freedesktop.DBus.Peer; +import org.freedesktop.DBus.Introspectable; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.DBusSigHandler; +import org.freedesktop.dbus.UInt32; + +class ProfileHandler implements DBusSigHandler +{ + public int c = 0; + public void handle(Profiler.ProfileSignal s) + { + if (0 == (c++%profile.SIGNAL_INNER)) System.out.print("-"); + } +} + +/** + * Profiling tests. + */ +public class profile +{ + public static final int SIGNAL_INNER = 100; + public static final int SIGNAL_OUTER = 100; + public static final int PING_INNER = 100; + public static final int PING_OUTER = 100; + public static final int BYTES = 2000000; + public static final int INTROSPECTION_OUTER = 100; + public static final int INTROSPECTION_INNER = 10; + public static final int STRUCT_OUTER = 100; + public static final int STRUCT_INNER = 10; + public static final int LIST_OUTER = 100; + public static final int LIST_INNER = 10; + public static final int LIST_LENGTH = 100; + public static final int MAP_OUTER = 100; + public static final int MAP_INNER = 10; + public static final int MAP_LENGTH = 100; + public static final int ARRAY_OUTER = 100; + public static final int ARRAY_INNER = 10; + public static final int ARRAY_LENGTH = 1000; + public static final int STRING_ARRAY_OUTER = 10; + public static final int STRING_ARRAY_INNER = 1; + public static final int STRING_ARRAY_LENGTH = 20000; + + public static class Log + { + private long last; + private int[] deltas; + private int current = 0; + public Log(int size) + { + deltas = new int[size]; + } + public void start() + { + last = System.currentTimeMillis(); + } + public void stop() + { + deltas[current] = (int) (System.currentTimeMillis()-last); + current++; + } + public double mean() + { + if (0 == current) return 0; + long sum = 0; + for (int i = 0; i < current; i++) + sum+=deltas[i]; + return sum /= current; + } + public long min() + { + int m = Integer.MAX_VALUE; + for (int i = 0; i < current; i++) + if (deltas[i] < m) m = deltas[i]; + return m; + } + public long max() + { + int m = 0; + for (int i = 0; i < current; i++) + if (deltas[i] > m) m = deltas[i]; + return m; + } + public double stddev() + { + double mean = mean(); + double sum = 0; + for (int i=0; i < current; i++) + sum += (deltas[i]-mean)*(deltas[i]-mean); + return Math.sqrt(sum / (current-1)); + } + } + public static void main(String[] args) + { + try { + if (0==args.length) { + System.out.println("You must specify a profile type."); + System.out.println("Syntax: profile "); + System.exit(1); + } + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + conn.requestBusName("org.freedesktop.DBus.java.profiler"); + if ("pings".equals(args[0])) { + int count = PING_INNER*PING_OUTER; + System.out.print("Sending "+count+" pings..."); + Peer p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Peer.class); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < PING_OUTER; i++) { + for (int j = 0; j < PING_INNER; j++) { + l.start(); + p.Ping(); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + } else if ("strings".equals(args[0])) { + int count = STRING_ARRAY_INNER*STRING_ARRAY_OUTER; + System.out.print("Sending array of "+STRING_ARRAY_LENGTH+" strings "+count+" times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + String[] v = new String[STRING_ARRAY_LENGTH]; + Random r = new Random(); + for (int i = 0; i < STRING_ARRAY_LENGTH; i++) v[i] = ""+r.nextInt(); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < STRING_ARRAY_OUTER; i++) { + for (int j = 0; j < STRING_ARRAY_INNER; j++) { + l.start(); + p.stringarray(v); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + } else if ("arrays".equals(args[0])) { + int count = ARRAY_INNER*ARRAY_OUTER; + System.out.print("Sending array of "+ARRAY_LENGTH+" ints "+count+" times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + int[] v = new int[ARRAY_LENGTH]; + Random r = new Random(); + for (int i = 0; i < ARRAY_LENGTH; i++) v[i] = r.nextInt(); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < ARRAY_OUTER; i++) { + for (int j = 0; j < ARRAY_INNER; j++) { + l.start(); + p.array(v); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + } else if ("maps".equals(args[0])) { + int count = MAP_INNER*MAP_OUTER; + System.out.print("Sending map of "+MAP_LENGTH+" string=>strings "+count+" times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + HashMap m = new HashMap(); + for (int i = 0; i < MAP_LENGTH; i++) + m.put(""+i, "hello"); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < MAP_OUTER; i++) { + for (int j=0; j < MAP_INNER; j++) { + l.start(); + p.map(m); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + } else if ("lists".equals(args[0])) { + int count = LIST_OUTER*LIST_INNER; + System.out.print("Sending list of "+LIST_LENGTH+" strings "+count+" times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + Vector v = new Vector(); + for (int i = 0; i < LIST_LENGTH; i++) + v.add("hello "+i); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < LIST_OUTER; i++) { + for (int j=0; j < LIST_INNER; j++) { + l.start(); + p.list(v); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + } else if ("structs".equals(args[0])) { + int count = STRUCT_OUTER*STRUCT_INNER; + System.out.print("Sending a struct "+count+" times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + ProfileStruct ps = new ProfileStruct("hello", new UInt32(18), 500L); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < STRUCT_OUTER; i++) { + for (int j=0; j < STRUCT_INNER; j++) { + l.start(); + p.struct(ps); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + } else if ("introspect".equals(args[0])) { + int count = INTROSPECTION_OUTER*INTROSPECTION_INNER; + System.out.print("Recieving introspection data "+count+" times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Introspectable is = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Introspectable.class); + Log l = new Log(count); + long t = System.currentTimeMillis(); + String s = null; + for (int i = 0; i < INTROSPECTION_OUTER; i++) { + for (int j = 0; j < INTROSPECTION_INNER; j++) { + l.start(); + s = is.Introspect(); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + System.out.println("Introspect data: "+s); + } else if ("bytes".equals(args[0])) { + System.out.print("Sending "+BYTES+" bytes"); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + byte[] bs = new byte[BYTES]; + for (int i = 0; i < BYTES; i++) + bs[i] = (byte) i; + long t = System.currentTimeMillis(); + p.bytes(bs); + System.out.println(" done in "+(System.currentTimeMillis()-t)+"ms."); + } else if ("rate".equals(args[0])) { + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + Peer peer = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Peer.class); + conn.changeThreadCount((byte)1); + + long start = System.currentTimeMillis(); + int count = 0; + do { + p.Pong(); + count++; + } while(count < 10000); + long end = System.currentTimeMillis(); + System.out.println("No payload: "+((count*1000)/(end-start))+" RT/second"); + start = System.currentTimeMillis(); + count = 0; + do { + p.Pong(); + count++; + } while(count < 10000); + peer.Ping(); + end = System.currentTimeMillis(); + System.out.println("No payload, One way: "+((count*1000)/(end-start))+" /second"); + int len = 256; + while (len <= 32768) { + byte[] bs = new byte[len]; + count = 0; + start = System.currentTimeMillis(); + do { + p.bytes(bs); + count++; + } while(count < 1000); + end = System.currentTimeMillis(); + long ms = end-start; + double cps = (count*1000)/ms; + double rate = (len*cps)/(1024.0*1024.0); + System.out.println(len+" byte array) "+(count*len)+" bytes in "+ms+"ms (in "+count+" calls / "+(int)cps+" CPS): "+rate+"MB/s"); + len <<= 1; + } + len = 256; + while (len <= 32768) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) sb.append('a'); + String s = sb.toString(); + end = System.currentTimeMillis()+500; + count = 0; + do { + p.string(s); + count++; + } while(count < 1000); + long ms = end-start; + double cps = (count*1000)/ms; + double rate = (len*cps)/(1024.0*1024.0); + System.out.println(len+" string) "+(count*len)+" bytes in "+ms+"ms (in "+count+" calls / "+(int)cps+" CPS): "+rate+"MB/s"); + len <<= 1; + } + } else if ("signals".equals(args[0])) { + int count = SIGNAL_OUTER*SIGNAL_INNER; + System.out.print("Sending "+count+" signals"); + ProfileHandler ph = new ProfileHandler(); + conn.addSigHandler(Profiler.ProfileSignal.class, ph); + Log l = new Log(count); + Profiler.ProfileSignal ps = new Profiler.ProfileSignal("/"); + long t = System.currentTimeMillis(); + for (int i = 0; i < SIGNAL_OUTER; i++) { + for (int j = 0; j < SIGNAL_INNER; j++) { + l.start(); + conn.sendSignal(ps); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + while (ph.c < count) try { Thread.sleep(100); } + catch (InterruptedException Ie) {}; + } else { + conn.disconnect(); + System.out.println("Invalid profile ``"+args[0]+"''."); + System.out.println("Syntax: profile "); + System.exit(1); + } + conn.disconnect(); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/test.java b/app/src/main/java/org/freedesktop/dbus/test/test.java new file mode 100644 index 00000000..4ccd696e --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/test.java @@ -0,0 +1,965 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + +import java.text.Collator; + +import org.freedesktop.dbus.CallbackHandler; +import org.freedesktop.dbus.DBusAsyncReply; +import org.freedesktop.dbus.DBusCallInfo; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSigHandler; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.Marshalling; +import org.freedesktop.dbus.Path; +import org.freedesktop.dbus.UInt16; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.UInt64; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import org.freedesktop.DBus; +import org.freedesktop.DBus.Error.MatchRuleInvalid; +import org.freedesktop.DBus.Error.ServiceUnknown; +import org.freedesktop.DBus.Error.UnknownObject; +import org.freedesktop.DBus.Peer; +import org.freedesktop.DBus.Introspectable; +import org.freedesktop.DBus.Properties; + +class testnewclass implements TestNewInterface +{ + public boolean isRemote() { return false; } + public String getName() + { + return toString(); + } +} + +class testclass implements TestRemoteInterface, TestRemoteInterface2, TestSignalInterface, TestSignalInterface2, Properties +{ + private DBusConnection conn; + public testclass(DBusConnection conn) + { + this.conn = conn; + } + public String Introspect() + { + return "Not XML"; + } + public int[][] teststructstruct(TestStruct3 in) + { + List> lli = in.b; + int[][] out = new int[lli.size()][]; + for (int j = 0; j < out.length; j++) { + out[j] = new int[lli.get(j).size()]; + for (int k = 0; k < out[j].length; k++) + out[j][k] = lli.get(j).get(k); + } + return out; + } + public float testfloat(float[] f) + { + if (f.length < 4 || + f[0] != 17.093f || + f[1] != -23f || + f[2] != 0.0f || + f[3] != 31.42f) + test.fail("testfloat got incorrect array"); + return f[0]; + } + public void newpathtest(Path p) + { + if (!p.toString().equals("/new/path/test")) + test.fail("new path test got wrong path"); + } + public void waitawhile() + { + System.out.println("Sleeping."); + try { + Thread.sleep(1000); + } catch (InterruptedException Ie) {} + System.out.println("Done sleeping."); + } + public TestTuple, Boolean> show(A in) + { + System.out.println("Showing Stuff: "+in.getClass()+"("+in+")"); + if (!(in instanceof Integer) || ((Integer) in).intValue() != 234) + test.fail("show received the wrong arguments"); + DBusCallInfo info = DBusConnection.getCallInfo(); + List l = new Vector(); + l.add(1953); + return new TestTuple, Boolean>(info.getSource(), l, true); + } + @SuppressWarnings("unchecked") + public T dostuff(TestStruct foo) + { + System.out.println("Doing Stuff "+foo); + System.out.println(" -- ("+foo.a.getClass()+", "+foo.b.getClass()+", "+foo.c.getClass()+")"); + if (!(foo instanceof TestStruct) || + !(foo.a instanceof String) || + !(foo.b instanceof UInt32) || + !(foo.c instanceof Variant) || + !"bar".equals(foo.a) || + foo.b.intValue() != 52 || + !(foo.c.getValue() instanceof Boolean) || + ((Boolean) foo.c.getValue()).booleanValue() != true) + test.fail("dostuff received the wrong arguments"); + return (T) foo.c.getValue(); + } + /** Local classes MUST implement this to return false */ + public boolean isRemote() { return false; } + /** The method we are exporting to the Bus. */ + public List sampleArray(List ss, Integer[] is, long[] ls) + { + System.out.println("Got an array:"); + for (String s: ss) + System.out.println("--"+s); + if (ss.size()!= 5 || + !"hi".equals(ss.get(0)) || + !"hello".equals(ss.get(1)) || + !"hej".equals(ss.get(2)) || + !"hey".equals(ss.get(3)) || + !"aloha".equals(ss.get(4))) + test.fail("sampleArray, String array contents incorrect"); + System.out.println("Got an array:"); + for (Integer i: is) + System.out.println("--"+i); + if (is.length != 4 || + is[0].intValue() != 1 || + is[1].intValue() != 5 || + is[2].intValue() != 7 || + is[3].intValue() != 9) + test.fail("sampleArray, Integer array contents incorrect"); + System.out.println("Got an array:"); + for (long l: ls) + System.out.println("--"+l); + if (ls.length != 4 || + ls[0] != 2 || + ls[1] != 6 || + ls[2] != 8 || + ls[3] != 12) + test.fail("sampleArray, Integer array contents incorrect"); + Vector v = new Vector(); + v.add(-1); + v.add(-5); + v.add(-7); + v.add(-12); + v.add(-18); + return v; + } + public String getName() + { + return "This Is A UTF-8 Name: س !!"; + } + public String getNameAndThrow() throws TestException + { + throw new TestException("test"); + } + public boolean check() + { + System.out.println("Being checked"); + return false; + } + public int frobnicate(List n, Map> m, T v) + { + if (null == n) + test.fail("List was null"); + if (n.size() != 3) + test.fail("List was wrong size (expected 3, actual "+n.size()+")"); + if (n.get(0) != 2L || + n.get(1) != 5L || + n.get(2) != 71L) + test.fail("List has wrong contents"); + if (!(v instanceof Integer)) + test.fail("v not an Integer"); + if (((Integer) v) != 13) + test.fail("v is incorrect"); + if (null == m) + test.fail("Map was null"); + if (m.size() != 1) + test.fail("Map was wrong size"); + if (!m.keySet().contains("stuff")) + test.fail("Incorrect key"); + Map mus = m.get("stuff"); + if (null == mus) + test.fail("Sub-Map was null"); + if (mus.size() != 3) + test.fail("Sub-Map was wrong size"); + if (!(new Short((short)5).equals(mus.get(new UInt16(4))))) + test.fail("Sub-Map has wrong contents"); + if (!(new Short((short)6).equals(mus.get(new UInt16(5))))) + test.fail("Sub-Map has wrong contents"); + if (!(new Short((short)7).equals(mus.get(new UInt16(6))))) + test.fail("Sub-Map has wrong contents"); + return -5; + } + public DBusInterface getThis(DBusInterface t) + { + if (!t.equals(this)) + test.fail("Didn't get this properly"); + return this; + } + public void throwme() throws TestException + { + throw new TestException("test"); + } + public TestSerializable testSerializable(byte b, TestSerializable s, int i) + { + System.out.println("Recieving TestSerializable: "+s); + if ( b != 12 + || i != 13 + || !(s.getInt() == 1) + || !(s.getString().equals("woo")) + || !(s.getVector().size() == 3) + || !(s.getVector().get(0) == 1) + || !(s.getVector().get(1) == 2) + || !(s.getVector().get(2) == 3) ) + test.fail("Error in recieving custom synchronisation"); + return s; + } + public String recursionTest() + { + try { + TestRemoteInterface tri = conn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface.class); + return tri.getName(); + } catch (DBusException DBe) { + test.fail("Failed with error: "+DBe); + return ""; + } + } + public int overload(String s) + { + return 1; + } + public int overload(byte b) + { + return 2; + } + public int overload() + { + DBusCallInfo info = DBusConnection.getCallInfo(); + if ("org.freedesktop.dbus.test.AlternateTestInterface".equals(info.getInterface())) + return 3; + else if ("org.freedesktop.dbus.test.TestRemoteInterface".equals(info.getInterface())) + return 4; + else + return -1; + } + public List> checklist(List> lli) + { + return lli; + } + public TestNewInterface getNew() + { + testnewclass n = new testnewclass(); + try { + conn.exportObject("/new", n); + } catch (DBusException DBe) + { throw new DBusExecutionException(DBe.getMessage()); } + return n; + } + public void sig(Type[] s) + { + if (s.length != 2 + || !s[0].equals(Byte.class) + || ! (s[1] instanceof ParameterizedType) + || ! Map.class.equals(((ParameterizedType) s[1]).getRawType()) + || ((ParameterizedType) s[1]).getActualTypeArguments().length != 2 + || ! String.class.equals(((ParameterizedType) s[1]).getActualTypeArguments()[0]) + || ! Integer.class.equals(((ParameterizedType) s[1]).getActualTypeArguments()[1])) + test.fail("Didn't send types correctly"); + } + @SuppressWarnings("unchecked") + public void complexv(Variant v) + { + if (!"a{ss}".equals(v.getSig()) + || ! (v.getValue() instanceof Map) + || ((Map) v.getValue()).size() != 1 + || !"moo".equals(((Map) v.getValue()).get("cow"))) + test.fail("Didn't send variant correctly"); + } + public void reg13291(byte[] as, byte[] bs) + { + if (as.length != bs.length) test.fail("didn't receive identical byte arrays"); + for (int i = 0; i < as.length; i++) + if (as[i] != bs[i]) test.fail("didn't receive identical byte arrays"); + } + @SuppressWarnings("unchecked") + public A Get (String interface_name, String property_name) + { + return (A) new Path("/nonexistant/path"); + } + public void Set (String interface_name, String property_name, A value) {} + public Map GetAll (String interface_name) { return new HashMap(); } + public Path pathrv(Path a) { return a; } + public List pathlistrv(List a) { return a; } + public Map pathmaprv(Map a) { return a; } +} + +/** + * Typed signal handler for renamed signal + */ +class renamedsignalhandler implements DBusSigHandler +{ + /** Handling a signal */ + public void handle(TestSignalInterface2.TestRenamedSignal t) + { + if (false == test.done5) { + test.done5 = true; + } else { + test.fail("SignalHandler R has been run too many times"); + } + System.out.println("SignalHandler R Running"); + System.out.println("string("+t.value+") int("+t.number+")"); + if (!"Bar".equals(t.value) || !(new UInt32(42)).equals(t.number)) + test.fail("Incorrect TestRenamedSignal parameters"); + } +} + +/** + * Empty signal handler + */ +class emptysignalhandler implements DBusSigHandler +{ + /** Handling a signal */ + public void handle(TestSignalInterface.EmptySignal t) + { + if (false == test.done7) { + test.done7 = true; + } else { + test.fail("SignalHandler E has been run too many times"); + } + System.out.println("SignalHandler E Running"); + } +} +/** + * Disconnect handler + */ +class disconnecthandler implements DBusSigHandler +{ + private DBusConnection conn; + private renamedsignalhandler sh; + public disconnecthandler(DBusConnection conn, renamedsignalhandler sh) + { + this.conn = conn; + this.sh = sh; + } + /** Handling a signal */ + public void handle(DBus.Local.Disconnected t) + { + if (false == test.done6) { + test.done6 = true; + System.out.println("Handling disconnect, unregistering handler"); + try { + conn.removeSigHandler(TestSignalInterface2.TestRenamedSignal.class, sh); + } catch (DBusException DBe) { + DBe.printStackTrace(); + test.fail("Disconnect handler threw an exception: "+DBe); + } + } + } +} + + +/** + * Typed signal handler + */ +class pathsignalhandler implements DBusSigHandler +{ + /** Handling a signal */ + public void handle(TestSignalInterface.TestPathSignal t) + { + System.out.println("Path sighandler: "+t); + } +} + +/** + * Typed signal handler + */ +class signalhandler implements DBusSigHandler +{ + /** Handling a signal */ + public void handle(TestSignalInterface.TestSignal t) + { + if (false == test.done1) { + test.done1 = true; + } else { + test.fail("SignalHandler 1 has been run too many times"); + } + System.out.println("SignalHandler 1 Running"); + System.out.println("string("+t.value+") int("+t.number+")"); + if (!"Bar".equals(t.value) || !(new UInt32(42)).equals(t.number)) + test.fail("Incorrect TestSignal parameters"); + } +} + +/** + * Untyped signal handler + */ +class arraysignalhandler implements DBusSigHandler +{ + /** Handling a signal */ + public void handle(TestSignalInterface.TestArraySignal t) + { + try { + if (false == test.done2) { + test.done2 = true; + } else { + test.fail("SignalHandler 2 has been run too many times"); + } + System.out.println("SignalHandler 2 Running"); + if (t.v.size() != 1) test.fail("Incorrect TestArraySignal array length: should be 1, actually "+t.v.size()); + System.out.println("Got a test array signal with Parameters: "); + for (String str: t.v.get(0).a) + System.out.println("--"+str); + System.out.println(t.v.get(0).b.getType()); + System.out.println(t.v.get(0).b.getValue()); + if (!(t.v.get(0).b.getValue() instanceof UInt64) || + 567L != ((UInt64) t.v.get(0).b.getValue()).longValue() || + t.v.get(0).a.size() != 5 || + !"hi".equals(t.v.get(0).a.get(0)) || + !"hello".equals(t.v.get(0).a.get(1)) || + !"hej".equals(t.v.get(0).a.get(2)) || + !"hey".equals(t.v.get(0).a.get(3)) || + !"aloha".equals(t.v.get(0).a.get(4))) + test.fail("Incorrect TestArraySignal parameters"); + + if (t.m.keySet().size() != 2) test.fail("Incorrect TestArraySignal map size: should be 2, actually "+t.m.keySet().size()); + if (!(t.m.get(new UInt32(1)).b.getValue() instanceof UInt64) || + 678L != ((UInt64) t.m.get(new UInt32(1)).b.getValue()).longValue() || + !(t.m.get(new UInt32(42)).b.getValue() instanceof UInt64) || + 789L != ((UInt64) t.m.get(new UInt32(42)).b.getValue()).longValue()) + test.fail("Incorrect TestArraySignal parameters"); + + } catch (Exception e) { + e.printStackTrace(); + test.fail("SignalHandler 2 threw an exception: "+e); + } + } +} + +/** + * Object path signal handler + */ +class objectsignalhandler implements DBusSigHandler +{ + public void handle(TestSignalInterface.TestObjectSignal s) + { + if (false == test.done3) { + test.done3 = true; + } else { + test.fail("SignalHandler 3 has been run too many times"); + } + System.out.println(s.otherpath); + } +} + +/** + * handler which should never be called + */ +class badarraysignalhandler implements DBusSigHandler +{ + /** Handling a signal */ + public void handle(T s) + { + test.fail("This signal handler shouldn't be called"); + } +} + +/** + * Callback handler + */ +class callbackhandler implements CallbackHandler +{ + public void handle(String r) + { + System.out.println("Handling callback: "+r); + Collator col = Collator.getInstance(); + col.setDecomposition(Collator.FULL_DECOMPOSITION); + col.setStrength(Collator.PRIMARY); + if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!", r)) + test.fail("call with callback, wrong return value"); + if (test.done4) test.fail("Already ran callback handler"); + test.done4 = true; + } + public void handleError(DBusExecutionException e) + { + System.out.println("Handling error callback: "+e+" message = '"+e.getMessage()+"'"); + if (!(e instanceof TestException)) test.fail("Exception is of the wrong sort"); + Collator col = Collator.getInstance(); + col.setDecomposition(Collator.FULL_DECOMPOSITION); + col.setStrength(Collator.PRIMARY); + if (0 != col.compare("test", e.getMessage())) + test.fail("Exception has the wrong message"); + if (test.done8) test.fail("Already ran callback error handler"); + test.done8=true; + } +} + +/** + * This is a test program which sends and recieves a signal, implements, exports and calls a remote method. + */ +public class test +{ + public static boolean done1 = false; + public static boolean done2 = false; + public static boolean done3 = false; + public static boolean done4 = false; + public static boolean done5 = false; + public static boolean done6 = false; + public static boolean done7 = false; + public static boolean done8 = false; + public static void fail(String message) + { + System.out.println("Test Failed: "+message); + System.err.println("Test Failed: "+message); + if (null != serverconn) serverconn.disconnect(); + if (null != clientconn) clientconn.disconnect(); + System.exit(1); + } + static DBusConnection serverconn = null; + static DBusConnection clientconn = null; + @SuppressWarnings("unchecked") + public static void main(String[] args) + { try { + System.out.println("Creating Connection"); + serverconn = DBusConnection.getConnection(DBusConnection.SESSION); + clientconn = DBusConnection.getConnection(DBusConnection.SESSION); + serverconn.setWeakReferences(true); + clientconn.setWeakReferences(true); + + System.out.println("Registering Name"); + serverconn.requestBusName("foo.bar.Test"); + + /** This gets a remote object matching our bus name and exported object path. */ + Peer peer = clientconn.getRemoteObject("foo.bar.Test", "/Test", Peer.class); + DBus dbus = clientconn.getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); + + System.out.print("Listening for signals..."); + signalhandler sigh = new signalhandler(); + renamedsignalhandler rsh = new renamedsignalhandler(); + try { + /** This registers an instance of the test class as the signal handler for the TestSignal class. */ + clientconn.addSigHandler(TestSignalInterface.EmptySignal.class, new emptysignalhandler()); + clientconn.addSigHandler(TestSignalInterface.TestSignal.class, sigh); + clientconn.addSigHandler(TestSignalInterface2.TestRenamedSignal.class, rsh); + clientconn.addSigHandler(DBus.Local.Disconnected.class, new disconnecthandler(clientconn, rsh)); + String source = dbus.GetNameOwner("foo.bar.Test"); + clientconn.addSigHandler(TestSignalInterface.TestArraySignal.class, source, peer, new arraysignalhandler()); + clientconn.addSigHandler(TestSignalInterface.TestObjectSignal.class, new objectsignalhandler()); + clientconn.addSigHandler(TestSignalInterface.TestPathSignal.class, new pathsignalhandler()); + badarraysignalhandler bash = new badarraysignalhandler(); + clientconn.addSigHandler(TestSignalInterface.TestSignal.class, bash); + clientconn.removeSigHandler(TestSignalInterface.TestSignal.class, bash); + System.out.println("done"); + } catch (MatchRuleInvalid MRI) { + test.fail("Failed to add handlers: "+MRI.getMessage()); + } catch (DBusException DBe) { + test.fail("Failed to add handlers: "+DBe.getMessage()); + } + + System.out.println("Listening for Method Calls"); + testclass tclass = new testclass(serverconn); + testclass tclass2 = new testclass(serverconn); + /** This exports an instance of the test class as the object /Test. */ + serverconn.exportObject("/Test", tclass); + serverconn.exportObject("/BadTest", tclass); + serverconn.exportObject("/BadTest2", tclass2); + serverconn.addFallback("/FallbackTest", tclass); + + // explicitly unexport object + serverconn.unExportObject("/BadTest"); + // implicitly unexport object + tclass2 = null; + System.gc(); + System.runFinalization(); + System.gc(); + System.runFinalization(); + System.gc(); + System.runFinalization(); + + System.out.println("Sending Signal"); + /** This creates an instance of the Test Signal, with the given object path, signal name and parameters, and broadcasts in on the Bus. */ + serverconn.sendSignal(new TestSignalInterface.TestSignal("/foo/bar/Wibble", "Bar", new UInt32(42))); + serverconn.sendSignal(new TestSignalInterface.EmptySignal("/foo/bar/Wibble")); + serverconn.sendSignal(new TestSignalInterface2.TestRenamedSignal("/foo/bar/Wibble", "Bar", new UInt32(42))); + + System.out.println("These things are on the bus:"); + String[] names = dbus.ListNames(); + for (String name: names) + System.out.println("\t"+name); + + System.out.println("Getting our introspection data"); + /** This gets a remote object matching our bus name and exported object path. */ + Introspectable intro = clientconn.getRemoteObject("foo.bar.Test", "/", Introspectable.class); + /** Get introspection data */ + String data;/* = intro.Introspect(); + if (null == data || !data.startsWith(" peers = serverconn.new PeerSet(); + peers.add("org.freedesktop.DBus"); + clientconn.requestBusName("test.testclient"); + peers.add("test.testclient"); + clientconn.releaseBusName("test.testclient"); + + System.out.println("Pinging ourselves"); + /** Call ping. */ + for (int i = 0; i < 10; i++) { + long then = System.currentTimeMillis(); + peer.Ping(); + long now = System.currentTimeMillis(); + System.out.println("Ping returned in "+(now-then)+"ms."); + } + + System.out.println("Calling Method0/1"); + /** This gets a remote object matching our bus name and exported object path. */ + TestRemoteInterface tri = (TestRemoteInterface) clientconn.getPeerRemoteObject("foo.bar.Test", "/Test"); + System.out.println("Got Remote Object: "+tri); + /** Call the remote object and get a response. */ + String rname = tri.getName(); + System.out.println("Got Remote Name: "+rname); + + Path path = new Path("/nonexistantwooooooo"); + Path p = tri.pathrv(path); + System.out.println(path.toString()+" => "+p.toString()); + if (!path.equals(p)) fail("pathrv incorrect"); + List paths = new Vector(); + paths.add(path); + List ps = tri.pathlistrv(paths); + System.out.println(paths.toString()+" => "+ps.toString()); + if (!paths.equals(ps)) fail("pathlistrv incorrect"); + Map pathm = new HashMap(); + pathm.put(path, path); + Map pm = tri.pathmaprv(pathm); + System.out.println(pathm.toString()+" => "+pm.toString()); + System.out.println(pm.containsKey(path)+" "+pm.get(path)+" "+path.equals(pm.get(path))); + System.out.println(pm.containsKey(p)+" "+pm.get(p)+" "+p.equals(pm.get(p))); + for (Path q: pm.keySet()) { + System.out.println(q); + System.out.println(pm.get(q)); + } + if (!pm.containsKey(path) || !path.equals(pm.get(path))) fail("pathmaprv incorrect"); + + serverconn.sendSignal(new TestSignalInterface.TestPathSignal("/Test", path, paths, pathm)); + + Collator col = Collator.getInstance(); + col.setDecomposition(Collator.FULL_DECOMPOSITION); + col.setStrength(Collator.PRIMARY); + if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!", rname)) + fail("getName return value incorrect"); + System.out.println("sending it to sleep"); + tri.waitawhile(); + System.out.println("testing floats"); + if (17.093f != tri.testfloat(new float[] { 17.093f, -23f, 0.0f, 31.42f })) + fail("testfloat returned the wrong thing"); + System.out.println("Structs of Structs"); + List> lli = new Vector>(); + List li = new Vector(); + li.add(1); + li.add(2); + li.add(3); + lli.add(li); + lli.add(li); + lli.add(li); + TestStruct3 ts3 = new TestStruct3(new TestStruct2(new Vector(), new Variant(0)), lli); + int[][] out = tri.teststructstruct(ts3); + if (out.length != 3) fail("teststructstruct returned the wrong thing: "+Arrays.deepToString(out)); + for (int[] o: out) + if (o.length != 3 + ||o[0] != 1 + ||o[1] != 2 + ||o[2] != 3) fail("teststructstruct returned the wrong thing: "+Arrays.deepToString(out)); + + System.out.println("frobnicating"); + List ls = new Vector(); + ls.add(2L); + ls.add(5L); + ls.add(71L); + Map mus = new HashMap(); + mus.put(new UInt16(4), (short) 5); + mus.put(new UInt16(5), (short) 6); + mus.put(new UInt16(6), (short) 7); + Map> msmus = new HashMap>(); + msmus.put("stuff", mus); + int rint = tri.frobnicate(ls, msmus, 13); + if (-5 != rint) + fail("frobnicate return value incorrect"); + + System.out.println("Doing stuff asynchronously with callback"); + clientconn.callWithCallback(tri, "getName", new callbackhandler()); + System.out.println("Doing stuff asynchronously with callback, which throws an error"); + clientconn.callWithCallback(tri, "getNameAndThrow", new callbackhandler()); + + /** call something that throws */ + try { + System.out.println("Throwing stuff"); + tri.throwme(); + test.fail("Method Execution should have failed"); + } catch (TestException Te) { + System.out.println("Remote Method Failed with: "+Te.getClass().getName()+" "+Te.getMessage()); + if (!Te.getMessage().equals("test")) + test.fail("Error message was not correct"); + } + + /* Test type signatures */ + Vector ts = new Vector(); + Marshalling.getJavaType("ya{si}", ts, -1); + tri.sig(ts.toArray(new Type[0])); + + tri.newpathtest(new Path("/new/path/test")); + + /** Try and call an invalid remote object */ + try { + System.out.println("Calling Method2"); + tri = clientconn.getRemoteObject("foo.bar.NotATest", "/Moofle", TestRemoteInterface.class); + System.out.println("Got Remote Name: "+tri.getName()); + test.fail("Method Execution should have failed"); + } catch (ServiceUnknown SU) { + System.out.println("Remote Method Failed with: "+SU.getClass().getName()+" "+SU.getMessage()); + } + + /** Try and call an invalid remote object */ + try { + System.out.println("Calling Method3"); + tri = clientconn.getRemoteObject("foo.bar.Test", "/Moofle", TestRemoteInterface.class); + System.out.println("Got Remote Name: "+tri.getName()); + test.fail("Method Execution should have failed"); + } catch (UnknownObject UO) { + System.out.println("Remote Method Failed with: "+UO.getClass().getName()+" "+UO.getMessage()); + } + + /** Try and call an explicitly unexported object */ + try { + System.out.println("Calling Method4"); + tri = clientconn.getRemoteObject("foo.bar.Test", "/BadTest", TestRemoteInterface.class); + System.out.println("Got Remote Name: "+tri.getName()); + test.fail("Method Execution should have failed"); + } catch (UnknownObject UO) { + System.out.println("Remote Method Failed with: "+UO.getClass().getName()+" "+UO.getMessage()); + } + + /** Try and call an implicitly unexported object */ + try { + System.out.println("Calling Method5"); + tri = clientconn.getRemoteObject("foo.bar.Test", "/BadTest2", TestRemoteInterface.class); + System.out.println("Got Remote Name: "+tri.getName()); + test.fail("Method Execution should have failed"); + } catch (UnknownObject UO) { + System.out.println("Remote Method Failed with: "+UO.getClass().getName()+" "+UO.getMessage()); + } + + System.out.println("Calling Method6"); + tri = clientconn.getRemoteObject("foo.bar.Test", "/FallbackTest/0/1", TestRemoteInterface.class); + intro = clientconn.getRemoteObject("foo.bar.Test", "/FallbackTest/0/4", Introspectable.class); + System.out.println("Got Fallback Name: "+tri.getName()); + System.out.println("Fallback Introspection Data: \n"+intro.Introspect()); + + System.out.println("Testing Properties returning Paths"); + Properties prop = clientconn.getRemoteObject("foo.bar.Test", "/Test", Properties.class); + Path prv = (Path) prop.Get("foo.bar", "foo"); + System.out.println("Got path "+prv); + System.out.println("Calling Method7--9"); + /** This gets a remote object matching our bus name and exported object path. */ + TestRemoteInterface2 tri2 = clientconn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface2.class); + System.out.print("Calling the other introspect method: "); + String intro2 = tri2.Introspect(); + System.out.println(intro2); + if (0 != col.compare("Not XML", intro2)) + fail("Introspect return value incorrect"); + + /** Call the remote object and get a response. */ + TestTuple,Boolean> rv = tri2.show(234); + System.out.println("Show returned: "+rv); + if (!serverconn.getUniqueName().equals(rv.a) || + 1 != rv.b.size() || + 1953 != rv.b.get(0) || + true != rv.c.booleanValue()) + fail("show return value incorrect ("+rv.a+","+rv.b+","+rv.c+")"); + + System.out.println("Doing stuff asynchronously"); + DBusAsyncReply stuffreply = (DBusAsyncReply) clientconn.callMethodAsync(tri2, "dostuff", new TestStruct("bar", new UInt32(52), new Variant(new Boolean(true)))); + + System.out.println("Checking bools"); + if (tri2.check()) fail("bools are broken"); + + List l = new Vector(); + l.add("hi"); + l.add("hello"); + l.add("hej"); + l.add("hey"); + l.add("aloha"); + System.out.println("Sampling Arrays:"); + List is = tri2.sampleArray(l, new Integer[] { 1, 5, 7, 9 }, new long[] { 2, 6, 8, 12 }); + System.out.println("sampleArray returned an array:"); + for (Integer i: is) + System.out.println("--"+i); + if (is.size() != 5 || + is.get(0).intValue() != -1 || + is.get(1).intValue() != -5 || + is.get(2).intValue() != -7 || + is.get(3).intValue() != -12 || + is.get(4).intValue() != -18) + fail("sampleArray return value incorrect"); + + System.out.println("Get This"); + if (!tclass.equals(tri2.getThis(tri2))) + fail("Didn't get the correct this"); + + Boolean b = stuffreply.getReply(); + System.out.println("Do stuff replied "+b); + if (true != b.booleanValue()) + fail("dostuff return value incorrect"); + + System.out.print("Sending Array Signal..."); + /** This creates an instance of the Test Signal, with the given object path, signal name and parameters, and broadcasts in on the Bus. */ + List tsl = new Vector(); + tsl.add(new TestStruct2(l, new Variant(new UInt64(567)))); + Map tsm = new HashMap(); + tsm.put(new UInt32(1), new TestStruct2(l, new Variant(new UInt64(678)))); + tsm.put(new UInt32(42), new TestStruct2(l, new Variant(new UInt64(789)))); + serverconn.sendSignal(new TestSignalInterface.TestArraySignal("/Test", tsl, tsm)); + + System.out.println("done"); + + System.out.print("testing custom serialization..."); + Vector v = new Vector(); + v.add(1); + v.add(2); + v.add(3); + TestSerializable s = new TestSerializable(1, "woo", v); + s = tri2.testSerializable((byte) 12, s, 13); + System.out.print("returned: "+s); + if (s.getInt() != 1 || + ! s.getString().equals("woo") || + s.getVector().size() != 3 || + s.getVector().get(0) != 1 || + s.getVector().get(1) != 2 || + s.getVector().get(2) != 3) + fail("Didn't get back the same TestSerializable"); + + System.out.println("done"); + + System.out.print("testing complex variants..."); + Map m = new HashMap(); + m.put("cow", "moo"); + tri2.complexv(new Variant(m, "a{ss}")); + System.out.println("done"); + + System.out.print("testing recursion..."); + + if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!",tri2.recursionTest())) fail("recursion test failed"); + + System.out.println("done"); + + System.out.print("testing method overloading..."); + tri = clientconn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface.class); + if (1 != tri2.overload("foo")) test.fail("wrong overloaded method called"); + if (2 != tri2.overload((byte) 0)) test.fail("wrong overloaded method called"); + if (3 != tri2.overload()) test.fail("wrong overloaded method called"); + if (4 != tri.overload()) test.fail("wrong overloaded method called"); + System.out.println("done"); + + System.out.print("reg13291..."); + byte[] as = new byte[10]; + for (int i = 0; i < 10; i++) + as[i] = (byte) (100-i); + tri.reg13291(as, as); + System.out.println("done"); + + System.out.print("Testing nested lists..."); + lli = new Vector>(); + li = new Vector(); + li.add(1); + lli.add(li); + List> reti = tri2.checklist(lli); + if (reti.size() != 1 || + reti.get(0).size() != 1 || + reti.get(0).get(0) != 1) + test.fail("Failed to check nested lists"); + System.out.println("done"); + + System.out.print("Testing dynamic object creation..."); + TestNewInterface tni = tri2.getNew(); + System.out.print(tni.getName()+" "); + System.out.println("done"); + + /* send an object in a signal */ + serverconn.sendSignal(new TestSignalInterface.TestObjectSignal("/foo/bar/Wibble", tclass)); + + /** Pause while we wait for the DBus messages to go back and forth. */ + Thread.sleep(1000); + + // check that bus name set has been trimmed + if (peers.size() != 1) fail("peers hasn't been trimmed"); + if (!peers.contains("org.freedesktop.DBus")) fail ("peers contains the wrong name"); + + System.out.println("Checking for outstanding errors"); + DBusExecutionException DBEe = serverconn.getError(); + if (null != DBEe) throw DBEe; + DBEe = clientconn.getError(); + if (null != DBEe) throw DBEe; + + System.out.println("Disconnecting"); + /** Disconnect from the bus. */ + clientconn.disconnect(); + serverconn.disconnect(); + + System.out.println("Trying to do things after disconnection"); + + /** Remove sig handler */ + clientconn.removeSigHandler(TestSignalInterface.TestSignal.class, sigh); + + /** Call a method when disconnected */ + try { + System.out.println("getName() suceeded and returned: "+tri.getName()); + fail("Should not succeed when disconnected"); + } catch (NotConnected NC) { + System.out.println("getName() failed with exception "+NC); + } + clientconn = null; + serverconn = null; + + if (!done1) fail("Signal handler 1 failed to be run"); + if (!done2) fail("Signal handler 2 failed to be run"); + if (!done3) fail("Signal handler 3 failed to be run"); + if (!done4) fail("Callback handler failed to be run"); + if (!done5) fail("Signal handler R failed to be run"); + if (!done6) fail("Disconnect handler failed to be run"); + if (!done7) fail("Signal handler E failed to be run"); + if (!done8) fail("Error callback handler failed to be run"); + + } catch (Exception e) { + e.printStackTrace(); + fail("Unexpected Exception Occurred: "+e); + }} +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/test_low_level.java b/app/src/main/java/org/freedesktop/dbus/test/test_low_level.java new file mode 100644 index 00000000..e65956d8 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/test_low_level.java @@ -0,0 +1,56 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; +import cx.ath.matthew.debug.Debug; +import org.freedesktop.dbus.BusAddress; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.Message; +import org.freedesktop.dbus.MethodCall; +import org.freedesktop.dbus.Transport; + +public class test_low_level +{ + public static void main(String[] args) throws Exception + { + Debug.setHexDump(true); + String addr = System.getenv("DBUS_SESSION_BUS_ADDRESS"); + Debug.print(addr); + BusAddress address = new BusAddress(addr); + Debug.print(address); + + Transport conn = new Transport(address); + + Message m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello", (byte) 0, null); + conn.mout.writeMessage(m); + m = conn.min.readMessage(); + Debug.print(m.getClass()); + Debug.print(m); + m = conn.min.readMessage(); + Debug.print(m.getClass()); + Debug.print(m); + m = conn.min.readMessage(); + Debug.print(""+m); + m = new MethodCall("org.freedesktop.DBus", "/", null, "Hello", (byte) 0, null); + conn.mout.writeMessage(m); + m = conn.min.readMessage(); + Debug.print(m); + + m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "RequestName", (byte) 0, "su", "org.testname", 0); + conn.mout.writeMessage(m); + m = conn.min.readMessage(); + Debug.print(m); + m = new DBusSignal(null, "/foo", "org.foo", "Foo", null); + conn.mout.writeMessage(m); + m = conn.min.readMessage(); + Debug.print(m); + conn.disconnect(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/test_p2p_client.java b/app/src/main/java/org/freedesktop/dbus/test/test_p2p_client.java new file mode 100644 index 00000000..aafbca4f --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/test_p2p_client.java @@ -0,0 +1,42 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.InputStreamReader; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.DirectConnection; + +public class test_p2p_client +{ + public static void main(String[] args) throws Exception + { + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream("address"))); + String address = r.readLine(); + DirectConnection dc = new DirectConnection(address); + System.out.println("Connected"); + TestRemoteInterface tri = (TestRemoteInterface) dc.getRemoteObject("/Test"); + System.out.println(tri.getName()); + System.out.println(tri.testfloat(new float[] { 17.093f, -23f, 0.0f, 31.42f })); + + try { + tri.throwme(); + } catch (TestException Te) { + System.out.println("Caught TestException"); + } + ((DBus.Peer) tri).Ping(); + System.out.println(((DBus.Introspectable) tri).Introspect()); + dc.disconnect(); + System.out.println("Disconnected"); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/test_p2p_server.java b/app/src/main/java/org/freedesktop/dbus/test/test_p2p_server.java new file mode 100644 index 00000000..68406941 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/test_p2p_server.java @@ -0,0 +1,94 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import java.lang.reflect.Type; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.freedesktop.dbus.DirectConnection; +import org.freedesktop.dbus.Path; +import org.freedesktop.dbus.UInt16; + +public class test_p2p_server implements TestRemoteInterface +{ + public int[][] teststructstruct(TestStruct3 in) + { + List> lli = in.b; + int[][] out = new int[lli.size()][]; + for (int j = 0; j < out.length; j++) { + out[j] = new int[lli.get(j).size()]; + for (int k = 0; k < out[j].length; k++) + out[j][k] = lli.get(j).get(k); + } + return out; + } + public String getNameAndThrow() + { + return getName(); + } + public String getName() + { + System.out.println("getName called"); + return "Peer2Peer Server"; + } + public int frobnicate(List n, Map> m, T v) + { + return 3; + } + public void throwme() throws TestException + { + System.out.println("throwme called"); + throw new TestException("BOO"); + } + public void waitawhile() + { + return; + } + public int overload() + { + return 1; + } + public void sig(Type[] s) + { + } + public void newpathtest(Path p) + { + } + public void reg13291(byte[] as, byte[] bs) + { + } + public Path pathrv(Path a) { return a; } + public List pathlistrv(List a) { return a; } + public Map pathmaprv(Map a) { return a; } + public boolean isRemote() { return false; } + public float testfloat(float[] f) + { + System.out.println("got float: "+Arrays.toString(f)); + return f[0]; + } + + public static void main(String[] args) throws Exception + { + String address = DirectConnection.createDynamicSession(); + //String address = "tcp:host=localhost,port=12344,guid="+Transport.genGUID(); + PrintWriter w = new PrintWriter(new FileOutputStream("address")); + w.println(address); + w.flush(); + w.close(); + DirectConnection dc = new DirectConnection(address+",listen=true"); + System.out.println("Connected"); + dc.exportObject("/Test", new test_p2p_server()); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/two_part_test_client.java b/app/src/main/java/org/freedesktop/dbus/test/two_part_test_client.java new file mode 100644 index 00000000..f37e955b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/two_part_test_client.java @@ -0,0 +1,42 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.DBusConnection; + +public class two_part_test_client +{ + public static class two_part_test_object implements TwoPartObject + { + public boolean isRemote() { return false; } + public String getName() + { + System.out.println("client name"); + return toString(); + } + } + public static void main(String[] args) throws Exception + { + System.out.println("get conn"); + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + System.out.println("get remote"); + TwoPartInterface remote = conn.getRemoteObject("org.freedesktop.dbus.test.two_part_server", "/", TwoPartInterface.class); + System.out.println("get object"); + TwoPartObject o = remote.getNew(); + System.out.println("get name"); + System.out.println(o.getName()); + two_part_test_object tpto = new two_part_test_object(); + conn.exportObject("/TestObject", tpto); + conn.sendSignal(new TwoPartInterface.TwoPartSignal("/FromObject", tpto)); + try { Thread.sleep(1000); } catch (InterruptedException Ie) {} + conn.disconnect(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/two_part_test_server.java b/app/src/main/java/org/freedesktop/dbus/test/two_part_test_server.java new file mode 100644 index 00000000..7452303b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/two_part_test_server.java @@ -0,0 +1,55 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.test; + +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.DBusSigHandler; + +public class two_part_test_server implements TwoPartInterface, DBusSigHandler +{ + public class two_part_test_object implements TwoPartObject + { + public boolean isRemote() { return false; } + public String getName() + { + System.out.println("give name"); + return toString(); + } + } + private DBusConnection conn; + public two_part_test_server(DBusConnection conn) + { + this.conn = conn; + } + public boolean isRemote() { return false; } + public TwoPartObject getNew() + { + TwoPartObject o = new two_part_test_object(); + System.out.println("export new"); + try { conn.exportObject("/12345", o); } catch (Exception e) {} + System.out.println("give new"); + return o; + } + public void handle(TwoPartInterface.TwoPartSignal s) + { + System.out.println("Got: "+s.o); + } + public static void main(String[] args) throws Exception + { + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + conn.requestBusName("org.freedesktop.dbus.test.two_part_server"); + two_part_test_server server = new two_part_test_server(conn); + conn.exportObject("/", server); + conn.addSigHandler(TwoPartInterface.TwoPartSignal.class, server); + while (true) try { Thread.sleep(10000); } catch (InterruptedException Ie) {} + } +} + diff --git a/app/src/main/java/org/freedesktop/dbus/types/DBusListType.java b/app/src/main/java/org/freedesktop/dbus/types/DBusListType.java new file mode 100644 index 00000000..f7711dc8 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/types/DBusListType.java @@ -0,0 +1,44 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.types; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; + +/** + * The type of a list. + * Should be used whenever you need a Type variable for a list. + */ +public class DBusListType implements ParameterizedType +{ + private Type v; + /** + * Create a List type. + * @param v Type of the list contents. + */ + public DBusListType(Type v) + { + this.v = v; + } + public Type[] getActualTypeArguments() + { + return new Type[] { v }; + } + public Type getRawType() + { + return List.class; + } + public Type getOwnerType() + { + return null; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/types/DBusMapType.java b/app/src/main/java/org/freedesktop/dbus/types/DBusMapType.java new file mode 100644 index 00000000..825df7db --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/types/DBusMapType.java @@ -0,0 +1,47 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.types; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +/** + * The type of a map. + * Should be used whenever you need a Type variable for a map. + */ +public class DBusMapType implements ParameterizedType +{ + private Type k; + private Type v; + /** + * Create a map type. + * @param k The type of the keys. + * @param v The type of the values. + */ + public DBusMapType(Type k, Type v) + { + this.k = k; + this.v = v; + } + public Type[] getActualTypeArguments() + { + return new Type[] { k, v }; + } + public Type getRawType() + { + return Map.class; + } + public Type getOwnerType() + { + return null; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/types/DBusStructType.java b/app/src/main/java/org/freedesktop/dbus/types/DBusStructType.java new file mode 100644 index 00000000..86d7533f --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/types/DBusStructType.java @@ -0,0 +1,44 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.types; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import org.freedesktop.dbus.Struct; + +/** + * The type of a struct. + * Should be used whenever you need a Type variable for a struct. + */ +public class DBusStructType implements ParameterizedType +{ + private Type[] contents; + /** + * Create a struct type. + * @param contents The types contained in this struct. + */ + public DBusStructType(Type... contents) + { + this.contents = contents; + } + public Type[] getActualTypeArguments() + { + return contents; + } + public Type getRawType() + { + return Struct.class; + } + public Type getOwnerType() + { + return null; + } +} From 07ced7b4ee51abdcf700b1181ea34624d99eab78 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Fri, 3 May 2024 16:21:17 +0200 Subject: [PATCH 03/10] Use modern dbus-java fork, add experimental D-Bus MediaService --- app/build.gradle.kts | 22 +- app/src/main/cpp/build_depends.sh | 4 +- app/src/main/java/cx/ath/matthew/cgi/CGI.java | 565 -------- .../cx/ath/matthew/cgi/CGIErrorHandler.java | 41 - .../matthew/cgi/CGIHeaderSentException.java | 39 - .../cgi/CGIInvalidContentFormatException.java | 39 - .../java/cx/ath/matthew/cgi/CGITools.java | 48 - .../java/cx/ath/matthew/cgi/CheckBox.java | 46 - .../ath/matthew/cgi/DefaultErrorHandler.java | 67 - .../java/cx/ath/matthew/cgi/DisplayField.java | 46 - .../java/cx/ath/matthew/cgi/DropDown.java | 131 -- .../main/java/cx/ath/matthew/cgi/Field.java | 38 - .../java/cx/ath/matthew/cgi/HTMLForm.java | 141 -- .../java/cx/ath/matthew/cgi/HiddenField.java | 46 - .../cx/ath/matthew/cgi/MultipleDropDown.java | 115 -- .../java/cx/ath/matthew/cgi/NewTable.java | 42 - .../java/cx/ath/matthew/cgi/Password.java | 46 - .../main/java/cx/ath/matthew/cgi/Radio.java | 46 - .../java/cx/ath/matthew/cgi/SubmitButton.java | 54 - .../java/cx/ath/matthew/cgi/TextArea.java | 57 - .../java/cx/ath/matthew/cgi/TextField.java | 69 - .../main/java/cx/ath/matthew/cgi/testcgi.java | 78 -- .../main/java/cx/ath/matthew/debug/Debug.java | 617 --------- .../main/java/cx/ath/matthew/debug/Debug.jpp | 597 --------- .../java/cx/ath/matthew/io/DOMPrinter.java | 114 -- .../cx/ath/matthew/io/ExecInputStream.java | 142 --- .../cx/ath/matthew/io/ExecOutputStream.java | 137 -- .../java/cx/ath/matthew/io/InOutCopier.java | 115 -- .../cx/ath/matthew/io/TeeInputStream.java | 155 --- .../cx/ath/matthew/io/TeeOutputStream.java | 141 -- app/src/main/java/cx/ath/matthew/io/test.java | 48 - .../main/java/cx/ath/matthew/io/test2.java | 39 - .../main/java/cx/ath/matthew/io/test3.java | 45 - .../matthew/unix/NotConnectedException.java | 37 - .../cx/ath/matthew/unix/USInputStream.java | 84 -- .../cx/ath/matthew/unix/USOutputStream.java | 69 - .../cx/ath/matthew/unix/UnixIOException.java | 44 - .../cx/ath/matthew/unix/UnixServerSocket.java | 129 -- .../java/cx/ath/matthew/unix/UnixSocket.java | 320 ----- .../ath/matthew/unix/UnixSocketAddress.java | 85 -- .../main/java/cx/ath/matthew/unix/java-unix.h | 112 -- .../java/cx/ath/matthew/unix/testclient.java | 51 - .../java/cx/ath/matthew/unix/testserver.java | 55 - .../java/cx/ath/matthew/utils/Hexdump.java | 149 --- .../sync/connectivity/MediaService.java | 343 ----- .../connectivity/NotificationService.java | 143 --- .../sync/connectivity/SlirpService.java | 102 +- .../sync/dbus/DBusNotificationService.java | 169 --- ...ovider.java => IDBusConnectionCallback.kt} | 13 +- .../sync/dbus/IDBusConnectionProvider.kt | 25 + .../org/asteroidos/sync/dbus/MediaService.kt | 397 ++++++ .../sync/dbus/NotificationService.kt | 138 ++ .../asteroidos/sync/dbus/package-info.java | 19 + .../IMediaService.kt} | 14 +- .../asteroidos/sync/media/MediaSupervisor.kt | 126 ++ .../sync/services/SynchronizationService.java | 13 +- app/src/main/java/org/freedesktop/DBus.java | 490 ------- .../freedesktop/GetNotificationsStruct.java | 69 + .../GetServerInformationTuple.java | 56 + .../java/org/freedesktop/Notifications.java | 93 +- .../freedesktop/dbus/AbstractConnection.java | 1030 --------------- .../java/org/freedesktop/dbus/ArrayFrob.java | 173 --- .../java/org/freedesktop/dbus/BusAddress.java | 41 - .../org/freedesktop/dbus/CallbackHandler.java | 22 - .../java/org/freedesktop/dbus/Container.java | 88 -- .../org/freedesktop/dbus/DBusAsyncReply.java | 111 -- .../org/freedesktop/dbus/DBusCallInfo.java | 51 - .../org/freedesktop/dbus/DBusConnection.java | 780 ------------ .../org/freedesktop/dbus/DBusInterface.java | 31 - .../freedesktop/dbus/DBusInterfaceName.java | 27 - .../java/org/freedesktop/dbus/DBusMap.java | 152 --- .../org/freedesktop/dbus/DBusMatchRule.java | 143 --- .../org/freedesktop/dbus/DBusMemberName.java | 27 - .../freedesktop/dbus/DBusSerializable.java | 38 - .../org/freedesktop/dbus/DBusSigHandler.java | 25 - .../java/org/freedesktop/dbus/DBusSignal.java | 259 ---- .../freedesktop/dbus/DirectConnection.java | 251 ---- .../org/freedesktop/dbus/EfficientMap.java | 116 -- .../org/freedesktop/dbus/EfficientQueue.java | 107 -- .../main/java/org/freedesktop/dbus/Error.java | 142 --- .../org/freedesktop/dbus/ExportedObject.java | 166 --- .../java/org/freedesktop/dbus/Gettext.java | 33 - .../org/freedesktop/dbus/InternalSignal.java | 20 - .../org/freedesktop/dbus/Marshalling.java | 626 --------- .../java/org/freedesktop/dbus/Message.java | 1132 ----------------- .../org/freedesktop/dbus/MessageReader.java | 175 --- .../org/freedesktop/dbus/MessageWriter.java | 68 - .../java/org/freedesktop/dbus/MethodCall.java | 126 -- .../org/freedesktop/dbus/MethodReturn.java | 69 - .../org/freedesktop/dbus/MethodTuple.java | 38 - .../java/org/freedesktop/dbus/ObjectPath.java | 23 - .../java/org/freedesktop/dbus/ObjectTree.java | 163 --- .../main/java/org/freedesktop/dbus/Path.java | 40 - .../java/org/freedesktop/dbus/Position.java | 28 - .../dbus/RemoteInvocationHandler.java | 191 --- .../org/freedesktop/dbus/RemoteObject.java | 56 - .../org/freedesktop/dbus/SignalTuple.java | 51 - .../org/freedesktop/dbus/StrongReference.java | 43 - .../java/org/freedesktop/dbus/Struct.java | 23 - .../java/org/freedesktop/dbus/Transport.java | 827 ------------ .../main/java/org/freedesktop/dbus/Tuple.java | 24 - .../org/freedesktop/dbus/TypeSignature.java | 37 - .../java/org/freedesktop/dbus/UInt16.java | 79 -- .../java/org/freedesktop/dbus/UInt32.java | 83 -- .../java/org/freedesktop/dbus/UInt64.java | 151 --- .../java/org/freedesktop/dbus/Variant.java | 112 -- .../java/org/freedesktop/dbus/bin/Caller.java | 83 -- .../freedesktop/dbus/bin/CreateInterface.java | 711 ----------- .../org/freedesktop/dbus/bin/DBusDaemon.java | 878 ------------- .../dbus/bin/IdentifierMangler.java | 43 - .../dbus/bin/IterableNodeList.java | 29 - .../org/freedesktop/dbus/bin/ListDBus.java | 74 -- .../dbus/bin/NodeListIterator.java | 38 - .../freedesktop/dbus/bin/StructStruct.java | 54 - .../freedesktop/dbus/connections/SASL.java | 825 ++++++++++++ .../impl/AndroidDBusConnectionBuilder.java | 227 ++++ .../dbus/exceptions/DBusException.java | 26 - .../exceptions/DBusExecutionException.java | 40 - .../dbus/exceptions/FatalDBusException.java | 20 - .../dbus/exceptions/FatalException.java | 15 - .../exceptions/InternalMessageException.java | 20 - .../dbus/exceptions/MarshallingException.java | 20 - .../exceptions/MessageFormatException.java | 23 - .../MessageProtocolVersionException.java | 22 - .../dbus/exceptions/MessageTypeException.java | 22 - .../dbus/exceptions/NonFatalException.java | 15 - .../dbus/exceptions/NotConnected.java | 23 - .../exceptions/UnknownTypeCodeException.java | 21 - .../freedesktop/dbus/test/ProfileStruct.java | 32 - .../org/freedesktop/dbus/test/Profiler.java | 40 - .../dbus/test/ProfilerInstance.java | 28 - .../freedesktop/dbus/test/TestException.java | 24 - .../dbus/test/TestNewInterface.java | 26 - .../dbus/test/TestRemoteInterface.java | 57 - .../dbus/test/TestRemoteInterface2.java | 54 - .../dbus/test/TestSerializable.java | 48 - .../dbus/test/TestSignalInterface.java | 97 -- .../dbus/test/TestSignalInterface2.java | 45 - .../org/freedesktop/dbus/test/TestStruct.java | 32 - .../freedesktop/dbus/test/TestStruct2.java | 31 - .../freedesktop/dbus/test/TestStruct3.java | 30 - .../org/freedesktop/dbus/test/TestTuple.java | 30 - .../dbus/test/TwoPartInterface.java | 29 - .../freedesktop/dbus/test/TwoPartObject.java | 18 - .../dbus/test/cross_test_client.java | 513 -------- .../dbus/test/cross_test_server.java | 344 ----- .../org/freedesktop/dbus/test/profile.java | 381 ------ .../java/org/freedesktop/dbus/test/test.java | 965 -------------- .../freedesktop/dbus/test/test_low_level.java | 56 - .../dbus/test/test_p2p_client.java | 42 - .../dbus/test/test_p2p_server.java | 94 -- .../dbus/test/two_part_test_client.java | 42 - .../dbus/test/two_part_test_server.java | 55 - .../freedesktop/dbus/types/DBusListType.java | 44 - .../freedesktop/dbus/types/DBusMapType.java | 47 - .../dbus/types/DBusStructType.java | 44 - app/src/main/java/org/mpris/MediaPlayer2.java | 60 + .../java/org/mpris/mediaplayer2/Player.java | 112 ++ build.gradle.kts | 4 +- gradle.properties | 3 + gradle/wrapper/gradle-wrapper.properties | 2 +- 161 files changed, 2224 insertions(+), 19707 deletions(-) delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/CGI.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/CGIErrorHandler.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/CGIHeaderSentException.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/CGIInvalidContentFormatException.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/CGITools.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/CheckBox.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/DefaultErrorHandler.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/DisplayField.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/DropDown.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/Field.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/HTMLForm.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/HiddenField.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/MultipleDropDown.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/NewTable.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/Password.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/Radio.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/SubmitButton.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/TextArea.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/TextField.java delete mode 100644 app/src/main/java/cx/ath/matthew/cgi/testcgi.java delete mode 100644 app/src/main/java/cx/ath/matthew/debug/Debug.java delete mode 100644 app/src/main/java/cx/ath/matthew/debug/Debug.jpp delete mode 100644 app/src/main/java/cx/ath/matthew/io/DOMPrinter.java delete mode 100644 app/src/main/java/cx/ath/matthew/io/ExecInputStream.java delete mode 100644 app/src/main/java/cx/ath/matthew/io/ExecOutputStream.java delete mode 100644 app/src/main/java/cx/ath/matthew/io/InOutCopier.java delete mode 100644 app/src/main/java/cx/ath/matthew/io/TeeInputStream.java delete mode 100644 app/src/main/java/cx/ath/matthew/io/TeeOutputStream.java delete mode 100644 app/src/main/java/cx/ath/matthew/io/test.java delete mode 100644 app/src/main/java/cx/ath/matthew/io/test2.java delete mode 100644 app/src/main/java/cx/ath/matthew/io/test3.java delete mode 100644 app/src/main/java/cx/ath/matthew/unix/NotConnectedException.java delete mode 100644 app/src/main/java/cx/ath/matthew/unix/USInputStream.java delete mode 100644 app/src/main/java/cx/ath/matthew/unix/USOutputStream.java delete mode 100644 app/src/main/java/cx/ath/matthew/unix/UnixIOException.java delete mode 100644 app/src/main/java/cx/ath/matthew/unix/UnixServerSocket.java delete mode 100644 app/src/main/java/cx/ath/matthew/unix/UnixSocket.java delete mode 100644 app/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java delete mode 100644 app/src/main/java/cx/ath/matthew/unix/java-unix.h delete mode 100644 app/src/main/java/cx/ath/matthew/unix/testclient.java delete mode 100644 app/src/main/java/cx/ath/matthew/unix/testserver.java delete mode 100644 app/src/main/java/cx/ath/matthew/utils/Hexdump.java delete mode 100644 app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java delete mode 100644 app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java delete mode 100644 app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationService.java rename app/src/main/java/org/asteroidos/sync/dbus/{IDBusConnectionProvider.java => IDBusConnectionCallback.kt} (72%) create mode 100644 app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.kt create mode 100644 app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt create mode 100644 app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt create mode 100644 app/src/main/java/org/asteroidos/sync/dbus/package-info.java rename app/src/main/java/org/asteroidos/sync/{dbus/IDBusConnectionCallback.java => media/IMediaService.kt} (73%) create mode 100644 app/src/main/java/org/asteroidos/sync/media/MediaSupervisor.kt delete mode 100644 app/src/main/java/org/freedesktop/DBus.java create mode 100644 app/src/main/java/org/freedesktop/GetNotificationsStruct.java create mode 100644 app/src/main/java/org/freedesktop/GetServerInformationTuple.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/AbstractConnection.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/ArrayFrob.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/BusAddress.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/CallbackHandler.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/Container.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/DBusCallInfo.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/DBusConnection.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/DBusInterface.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/DBusMap.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/DBusMatchRule.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/DBusMemberName.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/DBusSerializable.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/DBusSigHandler.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/DBusSignal.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/DirectConnection.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/EfficientMap.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/EfficientQueue.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/Error.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/ExportedObject.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/Gettext.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/InternalSignal.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/Marshalling.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/Message.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/MessageReader.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/MessageWriter.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/MethodCall.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/MethodReturn.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/MethodTuple.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/ObjectPath.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/ObjectTree.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/Path.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/Position.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/RemoteObject.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/SignalTuple.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/StrongReference.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/Struct.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/Transport.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/Tuple.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/TypeSignature.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/UInt16.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/UInt32.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/UInt64.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/Variant.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/bin/Caller.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/bin/CreateInterface.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/bin/IdentifierMangler.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/bin/IterableNodeList.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/bin/ListDBus.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/bin/NodeListIterator.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/bin/StructStruct.java create mode 100644 app/src/main/java/org/freedesktop/dbus/connections/SASL.java create mode 100644 app/src/main/java/org/freedesktop/dbus/connections/impl/AndroidDBusConnectionBuilder.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/ProfileStruct.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/Profiler.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/ProfilerInstance.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestException.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestNewInterface.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface2.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestSerializable.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface2.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestStruct.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestStruct2.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestStruct3.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TestTuple.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TwoPartInterface.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/TwoPartObject.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/cross_test_client.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/cross_test_server.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/profile.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/test.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/test_low_level.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/test_p2p_client.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/test_p2p_server.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/two_part_test_client.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/test/two_part_test_server.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/types/DBusListType.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/types/DBusMapType.java delete mode 100644 app/src/main/java/org/freedesktop/dbus/types/DBusStructType.java create mode 100644 app/src/main/java/org/mpris/MediaPlayer2.java create mode 100644 app/src/main/java/org/mpris/mediaplayer2/Player.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3c8749cb..a63f3c36 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,19 +1,22 @@ plugins { id("com.android.application") + kotlin("android") } android { - compileSdk = 33 - buildToolsVersion = "30.0.3" + compileSdk = 34 defaultConfig { applicationId = "org.asteroidos.sync" minSdk = 24 - targetSdk = 33 + targetSdk = 34 versionCode = 29 versionName = "0.29" ndk.abiFilters.clear() ndk.abiFilters.add("arm64-v8a") + ndk.abiFilters.add("armeabi-v7a") + ndk.abiFilters.add("x86") + ndk.abiFilters.add("x86_64") externalNativeBuild { cmake { cppFlags += "" @@ -39,7 +42,7 @@ android { srcDir("src/main/lib/powerampapi/poweramp_api_lib/res/") } jniLibs { - srcDir("/work/android-root/lib") + srcDir("/tmp/android-root/lib") } } } @@ -48,6 +51,10 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + kotlinOptions { + jvmTarget = "17" + } + lint { checkReleaseBuilds = true disable += "MissingTranslation" @@ -80,4 +87,11 @@ dependencies { implementation("no.nordicsemi.android.support.v18:scanner:1.6.0") implementation("no.nordicsemi.android:ble:2.7.2") implementation("com.google.guava:guava:33.1.0-android") + implementation("com.github.hypfvieh:dbus-java-core:5.0.0") + implementation("com.github.hypfvieh:dbus-java-transport-tcp:5.0.0") + implementation("androidx.media3:media3-session:1.3.1") + implementation("androidx.media3:media3-common:1.3.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1-Beta") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.8.1-Beta") + } diff --git a/app/src/main/cpp/build_depends.sh b/app/src/main/cpp/build_depends.sh index 2ddd3e14..fec8465f 100755 --- a/app/src/main/cpp/build_depends.sh +++ b/app/src/main/cpp/build_depends.sh @@ -118,7 +118,7 @@ EOF. +++ meson.build 2024-04-27 11:44:38.569868768 +0200 @@ -2170 +2170 @@ - libiconv = dependency('iconv') -+ libiconv = [cc.find_library('iconv', required : true, dirs : ['/work/android-root/lib'])] ++ libiconv = [cc.find_library('iconv', required : true, dirs : ['${PREFIX}/lib/${ABI}'])] EOF. @@ -130,4 +130,4 @@ EOF. >&2 ninja -C ./_builddir/ install >&2 echo "All depends ready" -popd \ No newline at end of file +popd diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGI.java b/app/src/main/java/cx/ath/matthew/cgi/CGI.java deleted file mode 100644 index b63fffa8..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/CGI.java +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.cgi; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Map; -import java.util.Set; -import java.util.Vector; -import java.text.DateFormat; -import java.text.SimpleDateFormat; - -/** - * This is the main class you have to extend with your CGI program. - * You should implement the cgi() method. - * - * @author Matthew Johnson <src@matthew.ath.cx> - */ -abstract public class CGI -{ - private CGIErrorHandler errorhandler = new DefaultErrorHandler(); - private boolean headers_sent = false; - private HashMap headers = new HashMap(); - private Vector cookies = new Vector(); - private LinkedList pagedata = new LinkedList(); - private LinkedList rawdata = new LinkedList(); - - private native String getenv(String var); - /** MUST pass String.class and ALWAYS returns a String[] */ - private native Object[] getfullenv(Class c); - private native void setenv(String var, String value); - { - System.loadLibrary("cgi-java"); - } - - /** - * Called by CGIs to send a header to the output - * - * @param variable The header variable to set. - * @param value The value of the variable. - * - * @throws CGIHeaderSentException if the headers have already been sent. - * - * @see #flush - */ - public final void header(String variable, String value) throws CGIHeaderSentException - { - // only send headers once - if (headers_sent) throw new CGIHeaderSentException(); - - // buffer the variable (Map so that each header is only set once) - headers.put(variable.toLowerCase(), value); - } - - /** - * Sets a Cookie in the web browser, with extended attributes. - * Calls header() so must be called before sending any output. - * - * A parameter will not be sent if it is null. - * - * @param variable The cookie variable to set. - * @param value The value of the variable. - * @param path The path that the cookie will be returned for. - * @param domain The domain that the cookie will be returned for. - * @param expires The expiry date of the cookie. - * @param secure Will only send the cookie over HTTPS if this is true. - * - * @throws CGIHeaderSentException if the headers have already been sent. - * - * @see #flush - * @see #header - */ - public final void setcookie(String variable, String value, String path, String domain, Date expires, boolean secure) throws CGIHeaderSentException - { - if (headers_sent) throw new CGIHeaderSentException(); - - //Set-Cookie: NAME=VALUE; expires=DATE; - //path=PATH; domain=DOMAIN_NAME; secure - //Wdy, DD-Mon-YYYY HH:MM:SS GMT - DateFormat df = new SimpleDateFormat("E, dd-MMM-yyyy HH:mm:ss zzz"); - String cookie = variable+"="+value; - if (null != path) cookie += "; path="+path; - if (null != domain) cookie += "; domain="+domain; - if (null != expires) cookie += "; expires="+df.format(expires); - if (secure) cookie += "; secure"; - cookies.add("Set-Cookie: "+ cookie); - } - - /** - * Sets a Cookie in the web browser. - * Calls header() so must be called before sending any output. - * - * @param variable The cookie variable to set. - * @param value The value of the variable. - * - * @throws CGIHeaderSentException if the headers have already been sent. - * - * @see #flush - * @see #header - */ - public final void setcookie(String variable, String value) throws CGIHeaderSentException - { - if (headers_sent) throw new CGIHeaderSentException(); - - //Set-Cookie: NAME=VALUE; expires=DATE; - //path=PATH; domain=DOMAIN_NAME; secure - cookies.add("Set-Cookie: "+ variable+"="+value); - } - - /** - * Called by CGIs to send byte data to the output. - * The data is buffered until the CGI exits, or a call of flush. - * - * @param data The page data. - * @throws CGIInvalidContentFormatException if text data has already been sent. - * - * @see #flush - */ - public final void out(byte[] data) throws CGIInvalidContentFormatException - { - if (pagedata.size() > 0) throw new CGIInvalidContentFormatException(); - rawdata.add(data); - } - - /** - * Called by CGIs to send a string to the output. - * The data is buffered until the CGI exits, or a call of flush. - * - * @param data The page data. - * @throws CGIInvalidContentFormatException if raw data has already been sent. - * - * @see #flush - */ - public final void out(String data) throws CGIInvalidContentFormatException - { - if (rawdata.size() > 0) throw new CGIInvalidContentFormatException(); - pagedata.add(data); - } - - /** - * This will return an OutputStream that you can write data - * directly to. Calling this method will cause the output to be - * flushed and the Headers sent. At the moment this is not buffered - * and will be sent directly to the client. Subsequent calls - * to out() will appear after data written to the output stream. - * - * @see #out - * @return an OutputStream - */ - public final OutputStream getOutputStream() throws IOException - { - flush(); - return System.out; - } - - /** - * Flushes the output. - * Note that you cannot send a header after a flush. - * If you want to send both text and binary data in a page - * you may do so either side of a flush. - * - * @see #header - */ - public final void flush() throws IOException - { - if (!headers_sent) { - // don't send headers again - headers_sent = true; - // send headers - Iterator i = headers.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String value = (String) headers.get(key); - System.out.println(key + ": " + value); - } - // send cookies - i = cookies.iterator(); - while (i.hasNext()) { - System.out.println((String) i.next()); - } - System.out.println(); - } - - // send data - if (pagedata.size() >0) { - Iterator j = pagedata.iterator(); - while (j.hasNext()) { - System.out.println((String) j.next()); - } - pagedata.clear(); - } else if (rawdata.size() > 0) { - Iterator j = rawdata.iterator(); - while (j.hasNext()) { - System.out.write((byte[]) j.next()); - } - pagedata.clear(); - } - System.out.flush(); - } - - /** - * Sets a custom exception handler. - * Gets called when an exception is thrown. - * The default error handler prints the error nicely in HTML - * and then exits gracefully. - * - * @param handler The new exception handler - */ - protected final void setErrorHandler(CGIErrorHandler handler) - { - errorhandler = handler; - } - - /** - * Override this method in your CGI program. - * - * @param POST A Map of variable =$gt; value for the POST variables. - * @param GET A Map of variable =$gt; value for the GET variables. - * @param ENV A Map of variable =$gt; value for the Webserver environment variables. - * @param COOKIES A Map of variable =$gt; value for the browser-sent cookies. - * @param params An array of parameters passed to the CGI (GET with no variable assignments) - * - * @throws Exception You can throw anything, it will be caught by the error handler. - */ - abstract protected void cgi(Map POST, Map GET, Map ENV, Map COOKIES, String[] params) throws Exception; - - /** - * Reads variables from a String like a=b&c=d to a Map {a => b, c => d}. - * - * @param s String to read from. - * @param seperator seperator character between variables (eg &) - * @param values whether or not this string has values for variables - * - * @return a Map with values, a Vector without - */ - private Object readVariables(String s, char seperator, boolean values) - { - HashMap vars = new HashMap(); - Vector varv = new Vector(); - String temp = ""; - String variable = null; - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (c == seperator) { // new variable - if (null != temp) temp = temp.trim(); - if (values) { - if (variable == null) {variable = temp; temp = "";} - else variable.trim(); - if (!variable.equals("")) { - Object o = vars.get(variable); - if (o == null) - vars.put(variable.trim(), temp); - else if (o instanceof String) { - LinkedList l = new LinkedList(); - l.add(o); - l.add(temp); - vars.put(variable.trim(), l); - } else if (o instanceof LinkedList) - ((LinkedList) o).add(temp); - } - temp = ""; - } - else { - varv.add(temp); - temp = ""; - } - variable = null; - continue; - } - if (values && c == '=') { - variable = temp; - temp = ""; - continue; - } - switch (c) { - case '%': // escaped character - try { - char a = s.charAt(++i); - char b = s.charAt(++i); - int ch = 0; - switch (a) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - ch += 0x10 * (a - '0'); - break; - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - ch += 0x10 * (a - 'a' + 0xa); - break; - case 'A': - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': - ch += 0x10 * (a - 'A' + 0xA); - break; - } - switch (b) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - ch += (b - '0'); - break; - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - ch += (b - 'a' + 0xa); - break; - case 'A': - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': - ch += (b - 'A' + 0xA); - break; - } - temp += (char) ch; - } catch (StringIndexOutOfBoundsException SIOOBe) { - // this means someone has included an invalid escape sequence. - // Invalid URIs can just be thrown on the floor. - } - break; - // + is a space - case '+': - temp += ' '; - break; - default: - temp += c; - } - } - if (values) { - if (variable == null) {variable = temp; temp = "";} - else variable.trim(); - //out("DEBUG variable read: "+variable+"/"+temp); - if (!variable.equals("")) { - Object o = vars.get(variable); - if (o == null) - vars.put(variable.trim(), temp); - else if (o instanceof String) { - LinkedList l = new LinkedList(); - l.add(o); - l.add(temp); - vars.put(variable.trim(), l); - } else if (o instanceof LinkedList) - ((LinkedList) o).add(temp); - } - - return vars; - } - else { - varv.add(temp); - return varv; - } - } - - /** - * Sets up the POST variables - */ - private Map getPOST() - { - try { - String s = ""; - while(System.in.available() > 0) - s += (char) System.in.read(); - //out("DEBUG: POST STRING: "+s); - return (Map) readVariables(s, '&', true); - } catch (IOException IOe) { - try { - out("ERROR: IOException: "+IOe); - } catch (CGIInvalidContentFormatException CGIICFe) { - System.err.println("ERROR: IOException: "+IOe); - } - return new HashMap(); - } - } - - /** - * Sets up the COOKIEs - */ - private Map getCOOKIE() - { - String s = getenv("HTTP_COOKIE"); - if (null == s) - return new HashMap(); - else - return (Map) readVariables(s, ';', true); - } - - /** - * Sets up the GET variables - */ - private Map getGET() - { - String s = getenv("QUERY_STRING"); - if (null == s) - return new HashMap(); - else - return (Map) readVariables(s, '&', true); - } - - /** - * Sets up the ENV variables - */ - private Map getENV() - { - Map m = new HashMap(); - String[] env = (String[]) getfullenv(String.class); - for (int i = 0; i < env.length; i++){ - if (null == env[i]) continue; - String[] e = env[i].split("="); - if (1 == e.length) - m.put(e[0], ""); - else - m.put(e[0], e[1]); - } - -/* - m.put("SERVER_SOFTWARE", getenv("SERVER_SOFTWARE")); - m.put("SERVER_NAME", getenv("SERVER_NAME")); - m.put("GATEWAY_INTERFACE", getenv("GATEWAY_INTERFACE")); - m.put("SERVER_PROTOCOL", getenv("SERVER_PROTOCOL")); - m.put("SERVER_PORT", getenv("SERVER_PORT")); - m.put("REQUEST_METHOD", getenv("REQUEST_METHOD")); - m.put("PATH_INFO", getenv("PATH_INFO")); - m.put("PATH_TRANSLATED", getenv("PATH_TRANSLATED")); - m.put("SCRIPT_NAME", getenv("SCRIPT_NAME")); - m.put("QUERY_STRING", getenv("QUERY_STRING")); - m.put("REMOTE_HOST", getenv("REMOTE_HOST")); - m.put("REMOTE_ADDR", getenv("REMOTE_ADDR")); - m.put("AUTH_TYPE", getenv("AUTH_TYPE")); - m.put("REMOTE_USER", getenv("REMOTE_USER")); - m.put("REMOTE_IDENT", getenv("REMOTE_IDENT")); - m.put("CONTENT_TYPE", getenv("CONTENT_TYPE")); - m.put("CONTENT_LENGTH", getenv("CONTENT_LENGTH")); - m.put("HTTP_ACCEPT", getenv("HTTP_ACCEPT")); - m.put("HTTP_USER_AGENT", getenv("HTTP_USER_AGENT")); - m.put("HTTP_COOKIE", getenv("HTTP_COOKIE")); - m.put("HTTP_ACCEPT_CHARSET", getenv("HTTP_ACCEPT_CHARSET")); - m.put("HTTP_ACCEPT_ENCODING", getenv("HTTP_ACCEPT_ENCODING")); - m.put("HTTP_CACHE_CONTROL", getenv("HTTP_CACHE_CONTROL")); - m.put("HTTP_REFERER", getenv("HTTP_REFERER")); - m.put("HTTP_X_FORWARDED_FOR", getenv("HTTP_X_FORWARDED_FOR")); - m.put("HTTP_HOST", getenv("HTTP_HOST")); - m.put("REQUEST_URI", getenv("REQUEST_URI")); - m.put("DOCUMENT_ROOT", getenv("DOCUMENT_ROOT")); - m.put("PATH", getenv("PATH")); - m.put("SERVER_ADDR", getenv("SERVER_ADDR")); - m.put("SCRIPT_FILENAME", getenv("SCRIPT_FILENAME")); - m.put("HTTP_COOKIE2", getenv("HTTP_COOKIE2")); - m.put("HTTP_CONNECTION", getenv("HTTP_CONNECTION")); - m.put("LANG", getenv("LANG")); - m.put("REDIRECT_LANG", getenv("REDIRECT_LANG")); - */ - return m; - } - - /** - * Sets up the param variables - */ - private String[] getParams(String args) - { - Vector v = (Vector) readVariables(args, ',', false); - String[] params = new String[v.size()]; - Iterator i = v.iterator(); - for (int j = 0; j < params.length; j++) - params[j] = (String) i.next(); - return params; - } - - /** - * This method sets up all the CGI variables and calls the cgi() method, then writes out the page data. - */ - public final void doCGI(String[] args) - { - CGI cgiclass = null; - // wrap everything in a try, we need to handle all our own errors. - try { - // setup the CGI variables - Map POST = getPOST(); - Map GET = getGET(); - Map ENV = getENV(); - Map COOKIE = getCOOKIE(); - String[] params = new String[] {}; - if (args.length >= 1) - params = getParams(args[0]); - - // instantiate CGI class - /* Class c = Class.forName(args[0]); - cgiclass = (CGI) c.newInstance(); */ - - // set default headers - /*cgiclass.*/header("Content-type", "text/html"); - - // execute the CGI - /*cgiclass.*/cgi(POST, GET, ENV, COOKIE, params); - - // send the output / remaining output - /*cgiclass.*/flush(); - } - - // yes, we really want to do this. CGI programs can't send errors. Print nicely to the screen. - catch (Exception e) { - errorhandler.print(/*null == cgiclass ? false : cgiclass.*/headers_sent, e); - } - catch (Throwable t) { - t.printStackTrace(); // this is bad enough to produce stderr errors - errorhandler.print(/*null == cgiclass ? false : cgiclass.*/headers_sent, new Exception(t.toString())); - } - } -} - - diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGIErrorHandler.java b/app/src/main/java/cx/ath/matthew/cgi/CGIErrorHandler.java deleted file mode 100644 index e0798cb7..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/CGIErrorHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.cgi; - -/** - * Interface to handle exceptions in the CGI. - */ -public interface CGIErrorHandler -{ - /** - * This is called if an exception is not caught in the CGI. - * It should handle printing the error message nicely to the user, - * and then exit gracefully. - */ - public void print(boolean headers_sent, Exception e); -} diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGIHeaderSentException.java b/app/src/main/java/cx/ath/matthew/cgi/CGIHeaderSentException.java deleted file mode 100644 index 4df8cc70..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/CGIHeaderSentException.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.cgi; - -/** - * Thrown if the headers have already been sent and CGI.header is called. - */ -public class CGIHeaderSentException extends Exception -{ - public CGIHeaderSentException() - { - super("Headers already sent by CGI"); - } -} diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGIInvalidContentFormatException.java b/app/src/main/java/cx/ath/matthew/cgi/CGIInvalidContentFormatException.java deleted file mode 100644 index 281533d3..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/CGIInvalidContentFormatException.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.cgi; - -/** - * Thrown if both raw and text data are set in the same page. - */ -public class CGIInvalidContentFormatException extends Exception -{ - public CGIInvalidContentFormatException() - { - super("Cannot send both raw and text data"); - } -} diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGITools.java b/app/src/main/java/cx/ath/matthew/cgi/CGITools.java deleted file mode 100644 index 9a379459..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/CGITools.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.cgi; - -abstract class CGITools -{ - /** - * Escape a character in a string. - * @param in String to escape in. - * @param c Character to escape. - * @return in with c replaced with \c - */ - public static String escapeChar(String in, char c) - { - String out = ""; - for (int i = 0; i < in.length(); i++) { - if (in.charAt(i) == c) out += '\\'; - out += in.charAt(i); - } - return out; - } -} - diff --git a/app/src/main/java/cx/ath/matthew/cgi/CheckBox.java b/app/src/main/java/cx/ath/matthew/cgi/CheckBox.java deleted file mode 100644 index 350390d4..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/CheckBox.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - - -package cx.ath.matthew.cgi; - -public class CheckBox extends Field -{ - boolean checked; - public CheckBox(String name, String label, boolean checked) - { - this.name = name; - this.label = label; - this.checked = checked; - } - protected String print() - { - return ""; - } -} - - diff --git a/app/src/main/java/cx/ath/matthew/cgi/DefaultErrorHandler.java b/app/src/main/java/cx/ath/matthew/cgi/DefaultErrorHandler.java deleted file mode 100644 index f5b812fd..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/DefaultErrorHandler.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.cgi; - -/** - * Interface to handle exceptions in the CGI. - */ -public class DefaultErrorHandler implements CGIErrorHandler -{ - /** - * This is called if an exception is not caught in the CGI. - * It should handle printing the error message nicely to the user, - * and then exit gracefully. - */ - public void print(boolean headers_sent, Exception e) - { - if (!headers_sent) { - System.out.println("Content-type: text/html"); - System.out.println(""); - System.out.println(""); - System.out.println(""); - System.out.println("Exception in CGI"); - System.out.println(""); - } - System.out.println("
"); - System.out.println("

"+e.getClass().toString()+"

"); - System.out.println("

"); - System.out.println("Exception Message: "+e.getMessage()); - System.out.println("

"); - System.out.println("

"); - System.out.println("Stack Trace:"); - System.out.println("

"); - System.out.println("
");
-      e.printStackTrace(System.out);
-      System.out.println("
"); - System.out.println("
"); - if (!headers_sent) { - System.out.println(""); - } - System.exit(1); - } -} diff --git a/app/src/main/java/cx/ath/matthew/cgi/DisplayField.java b/app/src/main/java/cx/ath/matthew/cgi/DisplayField.java deleted file mode 100644 index ec62a7ff..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/DisplayField.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - - -package cx.ath.matthew.cgi; - -public class DisplayField extends Field -{ - String value; - public DisplayField(String label, String value) - { - this.name = ""; - this.label = label; - this.value = value; - } - protected String print() - { - return value; - } -} - - diff --git a/app/src/main/java/cx/ath/matthew/cgi/DropDown.java b/app/src/main/java/cx/ath/matthew/cgi/DropDown.java deleted file mode 100644 index bdc45346..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/DropDown.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - - -package cx.ath.matthew.cgi; - -import java.util.List; - -public class DropDown extends Field -{ - Object[] values; - Object defval; - boolean indexed = false; - /** - * Create a new DropDown list. - * - * @param name The HTML field name. - * @param label The label to display - * @param values The values for the drop down list - * @param defval If this parameter is set then this element will be selected by default. - * @param indexed If this is set to true, then indexes will be returned, rather than values. - */ - public DropDown(String name, String label, Object[] values, Object defval, boolean indexed) - { - this.name = name; - this.label = label; - this.values = values; - this.indexed = indexed; - this.defval = defval; - } - /** - * Create a new DropDown list. - * - * @param name The HTML field name. - * @param label The label to display - * @param values The values for the drop down list - * @param defval If this parameter is set then this element will be selected by default. - * @param indexed If this is set to true, then indexes will be returned, rather than values. - */ - public DropDown(String name, String label, Object[] values, int defval, boolean indexed) - { - this.name = name; - this.label = label; - this.values = values; - if (defval < 0) - this.defval = null; - else - this.defval = values[defval]; - this.indexed = indexed; - } - /** - * Create a new DropDown list. - * - * @param name The HTML field name. - * @param label The label to display - * @param values The values for the drop down list - * @param defval If this parameter is set then this element will be selected by default. - * @param indexed If this is set to true, then indexes will be returned, rather than values. - */ - public DropDown(String name, String label, List values, Object defval, boolean indexed) - { - this.name = name; - this.label = label; - this.values = (Object[]) values.toArray(new Object[] {}); - this.defval = defval; - this.indexed = indexed; - } - /** - * Create a new DropDown list. - * - * @param name The HTML field name. - * @param label The label to display - * @param values The values for the drop down list - * @param defval If this parameter is set then this element will be selected by default. - * @param indexed If this is set to true, then indexes will be returned, rather than values. - */ - public DropDown(String name, String label, List values, int defval, boolean indexed) - { - this.name = name; - this.label = label; - this.values = (Object[]) values.toArray(new Object[] {}); - if (defval < 0) - this.defval = null; - else - this.defval = values.get(defval); - this.indexed = indexed; - } - protected String print() - { - String s = ""; - s += "\n"; - return s; - } -} - - diff --git a/app/src/main/java/cx/ath/matthew/cgi/Field.java b/app/src/main/java/cx/ath/matthew/cgi/Field.java deleted file mode 100644 index d859952b..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/Field.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - - -package cx.ath.matthew.cgi; - -public abstract class Field -{ - protected String name; - protected String label; - protected abstract String print(); -} - - diff --git a/app/src/main/java/cx/ath/matthew/cgi/HTMLForm.java b/app/src/main/java/cx/ath/matthew/cgi/HTMLForm.java deleted file mode 100644 index 0a397399..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/HTMLForm.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - - -package cx.ath.matthew.cgi; - -import java.util.Iterator; -import java.util.Vector; - - -/** - * Class to manage drawing HTML forms - */ -public class HTMLForm -{ - private String target; - private String submitlabel; - private String tableclass; - private Vector fields; - private boolean post = true; - - /** - * @param target The module to submit to - */ - public HTMLForm(String target) - { - this(target, "Submit", null); - } - - /** - * @param target The module to submit to - * @param submitlabel The string to display on the submit button - */ - public HTMLForm(String target, String submitlabel) - { - this(target, submitlabel, null); - } - - /** - * @param target The module to submit to - * @param submitlabel The string to display on the submit button - * @param tableclass The class= parameter for the generated table - */ - public HTMLForm(String target, String submitlabel, String tableclass) - { - this.target = target; - this.submitlabel = submitlabel; - this.tableclass = tableclass; - fields = new Vector(); - } - - /** - * Add a field to be displayed in the form. - * - * @param field A Field subclass. - */ - public void addField(Field field) - { - fields.add(field); - } - - /** - * Set GET method rather than POST - * @param enable Enable/Disable GET - */ - public void setGET(boolean enable) - { - post = !enable; - } - - /** - * Shows the form. - * @param cgi The CGI instance that is handling output - */ - public void display(CGI cgi) - { - try { - cgi.out("
"); - if (null == tableclass) - cgi.out("
"); - else - cgi.out("
"); - - Iterator i = fields.iterator(); - while (i.hasNext()) { - Field f = (Field) i.next(); - if (f instanceof NewTable) { - cgi.out(f.print()); - } - if (!(f instanceof HiddenField) && !(f instanceof SubmitButton) && !(f instanceof NewTable)) { - cgi.out(" "); - cgi.out(" "); - cgi.out(" "); - cgi.out(" "); - } - } - cgi.out(" "); - cgi.out(" "); - cgi.out(" "); - cgi.out("
"+f.label+""+f.print()+"
"); - i = fields.iterator(); - while (i.hasNext()) { - Field f = (Field) i.next(); - if (f instanceof HiddenField || f instanceof SubmitButton) { - cgi.out(" "+f.print()); - } - } - cgi.out(" "); - cgi.out("
"); - cgi.out(""); - } catch (CGIInvalidContentFormatException CGIICFe) {} - } -} - - - diff --git a/app/src/main/java/cx/ath/matthew/cgi/HiddenField.java b/app/src/main/java/cx/ath/matthew/cgi/HiddenField.java deleted file mode 100644 index 523f85bc..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/HiddenField.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - - -package cx.ath.matthew.cgi; - -public class HiddenField extends Field -{ - String value; - public HiddenField(String name, String value) - { - this.name = name; - this.label = ""; - this.value = value; - } - protected String print() - { - return ""; - } -} - - diff --git a/app/src/main/java/cx/ath/matthew/cgi/MultipleDropDown.java b/app/src/main/java/cx/ath/matthew/cgi/MultipleDropDown.java deleted file mode 100644 index 39904bdf..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/MultipleDropDown.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -/* - * - * TODO To change the template for this generated file go to - * Window - Preferences - Java - Code Style - Code Templates - */ -package cx.ath.matthew.cgi; - -import java.util.List; - -/** - * @author Agent - * - * TODO To change the template for this generated type comment go to - * Window - Preferences - Java - Code Style - Code Templates - */ -public class MultipleDropDown extends DropDown { - - /** - * @param name - * @param label - * @param values - * @param defval - * @param indexed - */ - public MultipleDropDown(String name, String label, String[] values, - String defval, boolean indexed) { - super(name, label, values, defval, indexed); - // TODO Auto-generated constructor stub - } - - /** - * @param name - * @param label - * @param values - * @param defval - * @param indexed - */ - public MultipleDropDown(String name, String label, String[] values, - int defval, boolean indexed) { - super(name, label, values, defval, indexed); - // TODO Auto-generated constructor stub - } - - /** - * @param name - * @param label - * @param values - * @param defval - * @param indexed - */ - public MultipleDropDown(String name, String label, List values, - String defval, boolean indexed) { - super(name, label, values, defval, indexed); - // TODO Auto-generated constructor stub - } - - /** - * @param name - * @param label - * @param values - * @param defval - * @param indexed - */ - public MultipleDropDown(String name, String label, List values, int defval, - boolean indexed) { - super(name, label, values, defval, indexed); - // TODO Auto-generated constructor stub - } - - protected String print() - { - String s = ""; - s += "\n"; - return s; - } - -} diff --git a/app/src/main/java/cx/ath/matthew/cgi/NewTable.java b/app/src/main/java/cx/ath/matthew/cgi/NewTable.java deleted file mode 100644 index 1638b74f..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/NewTable.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.cgi; -public class NewTable extends Field { - - private String name; - private String cssClass; - - public NewTable (String name, String css) { - this.name = name; - this.cssClass = css; - } - - protected String print() { - return "\n"; - } -} diff --git a/app/src/main/java/cx/ath/matthew/cgi/Password.java b/app/src/main/java/cx/ath/matthew/cgi/Password.java deleted file mode 100644 index 046fc0c0..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/Password.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - - -package cx.ath.matthew.cgi; - -public class Password extends Field -{ - String defval; - public Password(String name, String label, String defval) - { - this.name = name; - this.label = label; - this.defval = defval; - } - protected String print() - { - return ""; - } -} - - diff --git a/app/src/main/java/cx/ath/matthew/cgi/Radio.java b/app/src/main/java/cx/ath/matthew/cgi/Radio.java deleted file mode 100644 index 3a0d80e5..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/Radio.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - - -package cx.ath.matthew.cgi; - -public class Radio extends Field -{ - boolean checked; - public Radio(String name, String label, boolean checked) - { - this.name = name; - this.label = label; - this.checked = checked; - } - protected String print() - { - return ""; - } -} - - diff --git a/app/src/main/java/cx/ath/matthew/cgi/SubmitButton.java b/app/src/main/java/cx/ath/matthew/cgi/SubmitButton.java deleted file mode 100644 index ff97e1ce..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/SubmitButton.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -/* - * - * TODO To change the template for this generated file go to - * Window - Preferences - Java - Code Style - Code Templates - */ -package cx.ath.matthew.cgi; - -/** - * @author Agent - * - * TODO To change the template for this generated type comment go to - * Window - Preferences - Java - Code Style - Code Templates - */ -public class SubmitButton extends Field { - - public SubmitButton(String name, String label) { - this.name = name; - this.label = label; - } - /* (non-Javadoc) - * @see cx.ath.matthew.cgi.Field#print() - */ - protected String print() { - return ""; - } - -} diff --git a/app/src/main/java/cx/ath/matthew/cgi/TextArea.java b/app/src/main/java/cx/ath/matthew/cgi/TextArea.java deleted file mode 100644 index 6a924c48..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/TextArea.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - - -package cx.ath.matthew.cgi; - -public class TextArea extends Field -{ - String defval; - int cols; - int rows; - public TextArea(String name, String label, String defval) - { - this(name, label, defval, 30, 4); - } - public TextArea(String name, String label, String defval, int cols, int rows) - { - this.name = name; - this.label = label; - if (null == defval) - this.defval = ""; - else - this.defval = defval; - this.cols = cols; - this.rows = rows; - } - protected String print() - { - return ""; - } -} - - diff --git a/app/src/main/java/cx/ath/matthew/cgi/TextField.java b/app/src/main/java/cx/ath/matthew/cgi/TextField.java deleted file mode 100644 index 124ce3ef..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/TextField.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - - -package cx.ath.matthew.cgi; - - -public class TextField extends Field -{ - String defval; - int length; - public TextField(String name, String label) - { - this.name = name; - this.label = label; - this.defval = ""; - this.length = 0; - } - public TextField(String name, String label, String defval) - { - this.name = name; - this.label = label; - if (null == defval) - this.defval = ""; - else - this.defval = defval; - this.length = 0; - } - public TextField(String name, String label, String defval, int length) - { - this.name = name; - this.label = label; - if (null == defval) - this.defval = ""; - else - this.defval = defval; - this.length = length; - } - protected String print() - { - return ""; - } -} - - diff --git a/app/src/main/java/cx/ath/matthew/cgi/testcgi.java b/app/src/main/java/cx/ath/matthew/cgi/testcgi.java deleted file mode 100644 index 563447d9..00000000 --- a/app/src/main/java/cx/ath/matthew/cgi/testcgi.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Java CGI Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.cgi; - -import java.util.Iterator; -import java.util.Map; - -class testcgi extends CGI -{ - protected void cgi(Map POST, Map GET, Map ENV, Map COOKIE, String[] params) throws Exception - { - header("Content-type", "text/plain"); - setcookie("testcgi", "You have visited us already"); - out("This is a test CGI program"); - out("These are the params:"); - for (int i=0; i < params.length; i++) - out("-- "+params[i]); - - out("These are the POST vars:"); - Iterator i = POST.keySet().iterator(); - while (i.hasNext()) { - String s = (String) i.next(); - out("-- "+s+" => "+POST.get(s)); - } - - out("These are the GET vars:"); - i = GET.keySet().iterator(); - while (i.hasNext()) { - String s = (String) i.next(); - out("-- "+s+" => "+GET.get(s)); - } - - out("These are the ENV vars:"); - i = ENV.keySet().iterator(); - while (i.hasNext()) { - String s = (String) i.next(); - out("-- "+s+" => "+ENV.get(s)); - } - - out("These are the COOKIEs:"); - i = COOKIE.keySet().iterator(); - while (i.hasNext()) { - String s = (String) i.next(); - out("-- "+s+" => "+COOKIE.get(s)); - } - } - - public static void main(String[] args) - { - CGI cgi = new testcgi(); - cgi.doCGI(args); - } -} diff --git a/app/src/main/java/cx/ath/matthew/debug/Debug.java b/app/src/main/java/cx/ath/matthew/debug/Debug.java deleted file mode 100644 index 22e8c8cc..00000000 --- a/app/src/main/java/cx/ath/matthew/debug/Debug.java +++ /dev/null @@ -1,617 +0,0 @@ -/* Copyright (C) 1991-2014 Free Software Foundation, Inc. - This file is part of the GNU C Library. - - The GNU C Library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - The GNU C Library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with the GNU C Library; if not, see - . */ -/* This header is separate from features.h so that the compiler can - include it implicitly at the start of every compilation. It must - not itself include or any other header that includes - because the implicit include comes before any feature - test macros that may be defined in a source file before it first - explicitly includes a system header. GCC knows the name of this - header in order to preinclude it. */ -/* glibc's intent is to support the IEC 559 math functionality, real - and complex. If the GCC (4.9 and later) predefined macros - specifying compiler intent are available, use them to determine - whether the overall intent is to support these features; otherwise, - presume an older compiler has intent to support these features and - define these macros by default. */ -/* wchar_t uses ISO/IEC 10646 (2nd ed., published 2011-03-15) / - Unicode 6.0. */ -/* We do not support C11 . */ -/* - * Java Debug Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ -package cx.ath.matthew.debug; -import cx.ath.matthew.utils.Hexdump; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.PrintStream; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Properties; -/** - Add debugging to your program, has support for large projects with multiple - classes and debug levels per class. Supports optional enabling of debug - per-level per-class and debug targets of files, Streams or stderr. - Also supports timing between debug outputs, printing of stack traces for Throwables - and files/line numbers on each message. -

- Debug now automatically figures out which class it was called from, so all - methods passing in the calling class are deprecated. -

-

- The defaults are to print all messages to stderr with class and method name. -

-

- Should be called like this: -

-
-   if (Debug.debug) Debug.print(Debug.INFO, "Debug Message");
-  
- */ -public class Debug -{ - /** - This interface can be used to provide custom printing filters - for certain classes. - */ - public static interface FilterCommand - { - /** - Called to print debug messages with a custom filter. - @param output The PrintStream to output to. - @param level The debug level of this message. - @param location The textual location of the message. - @param extra Extra information such as timing details. - @param message The debug message. - @param lines Other lines of a multiple-line debug message. - */ - public void filter(PrintStream output, int level, String location, String extra, String message, String[] lines); - } - /** Highest priority messages */ - public static final int CRIT = 1; - /** Error messages */ - public static final int ERR = 2; - /** Warnings */ - public static final int WARN = 3; - /** Information */ - public static final int INFO = 4; - /** Debug messages */ - public static final int DEBUG = 5; - /** Verbose debug messages */ - public static final int VERBOSE = 6; - /** Set this to false to disable compilation of Debug statements */ - public static final boolean debug = false; - /** The current output stream (defaults to System.err) */ - public static PrintStream debugout = System.err; - private static Properties prop = null; - private static boolean timing = false; - private static boolean ttrace = false; - private static boolean lines = false; - private static boolean hexdump = false; - private static long last = 0; - private static int balen = 36; - private static int bawidth = 80; - private static Class saveclass = null; - //TODO: 1.5 private static Map, FilterCommand> filterMap = new HashMap, FilterCommand>(); - private static Map filterMap = new HashMap(); - /** - Set properties to configure debugging. - Format of properties is class => level, e.g. -
-      cx.ath.matthew.io.TeeOutputStream = INFO
-      cx.ath.matthew.io.DOMPrinter = DEBUG
-     
- The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which - correspond to all messages up to that level. The special words YES, ALL and TRUE - cause all messages to be printed regardless of level. All other terms disable - messages for that class. CRIT and ERR messages are always printed if debugging is enabled - unless explicitly disabled. - The special class name ALL can be used to set the default level for all classes. - @param prop Properties object to use. - */ - public static void setProperties(Properties prop) - { - Debug.prop = prop; - } - /** - Read which class to debug on at which level from the given File. - Syntax the same as Java Properties files: -
-     <class> = <debuglevel>
-     
- E.G. -
-      cx.ath.matthew.io.TeeOutputStream = INFO
-      cx.ath.matthew.io.DOMPrinter = DEBUG
-     
- The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which - correspond to all messages up to that level. The special words YES, ALL and TRUE - cause all messages to be printed regardless of level. All other terms disable - messages for that class. CRIT and ERR messages are always printed if debugging is enabled - unless explicitly disabled. - The special class name ALL can be used to set the default level for all classes. - @param f File to read from. - */ - public static void loadConfig(File f) throws IOException - { - prop = new Properties(); - prop.load(new FileInputStream(f)); - } - /** @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. */ - //TODO: 1.5 @Deprecated() - public static boolean debugging(Class c, int loglevel) - { - if (debug) { - if (null == c) return true; - return debugging(c.getName(), loglevel); - } - return false; - } - public static boolean debugging(String s, int loglevel) - { - if (debug) { - try { - if (null == s) return true; - if (null == prop) return loglevel <= VERBOSE; - String d = prop.getProperty(s); - if (null == d || "".equals(d)) d = prop.getProperty("ALL"); - if (null == d) return loglevel <= ERR; - if ("".equals(d)) return loglevel <= ERR; - d = d.toLowerCase(); - if ("true".equals(d)) return true; - if ("yes".equals(d)) return true; - if ("all".equals(d)) return true; - if ("verbose".equals(d)) return loglevel <= VERBOSE; - if ("debug".equals(d)) return loglevel <= DEBUG; - if ("info".equals(d)) return loglevel <= INFO; - if ("warn".equals(d)) return loglevel <= WARN; - if ("err".equals(d)) return loglevel <= ERR; - if ("crit".equals(d)) return loglevel <= CRIT; - int i = Integer.parseInt(d); return i >= loglevel; - } catch (Exception e) { return false; } - } - return false; - } - /** - Output to the given Stream */ - public static void setOutput(PrintStream p) throws IOException - { - debugout = p; - } - /** - Output to the given file */ - public static void setOutput(String filename) throws IOException - { - debugout = new PrintStream(new FileOutputStream(filename, true)); - } - /** - Output to the default debug.log */ - public static void setOutput() throws IOException { - setOutput("./debug.log"); - } - /** - Log at DEBUG - @param d The object to log */ - public static void print(Object d) - { - if (debug) { - if (d instanceof String) - print(DEBUG, (String) d); - else if (d instanceof Throwable) - print(DEBUG, (Throwable) d); - else if (d instanceof byte[]) - print(DEBUG, (byte[]) d); - else if (d instanceof Map) - printMap(DEBUG, (Map) d); - else print(DEBUG, d); - } - } - /** - Log at DEBUG - @param o The object doing the logging - @param d The object to log - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Object o, Object d) - { - if (debug) { - if (o instanceof Class) - saveclass = (Class) o; - else - saveclass = o.getClass(); - print(d); - } - } - /** - Log an Object - @param o The object doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param d The object to log with d.toString() - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Object o, int loglevel, Object d) - { - if (debug) { - if (o instanceof Class) - saveclass = (Class) o; - else - saveclass = o.getClass(); - print(loglevel, d); - } - } - /** - Log a String - @param o The object doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param s The log message - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Object o, int loglevel, String s) - { - if (debug) { - if (o instanceof Class) - saveclass = (Class) o; - else - saveclass = o.getClass(); - print(loglevel, s); - } - } - /** - Log a Throwable - @param o The object doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param t The throwable to log with .toString and .printStackTrace - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Object o, int loglevel, Throwable t) - { - if (debug) { - if (o instanceof Class) - saveclass = (Class) o; - else - saveclass = o.getClass(); - print(loglevel, t); - } - } - /** - Log a Throwable - @param c The class doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param t The throwable to log with .toString and .printStackTrace - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Class c, int loglevel, Throwable t) - { - if (debug) { - saveclass = c; - print(loglevel, t); - } - } - /** - Log a Throwable - @param loglevel The level to log at (DEBUG, WARN, etc) - @param t The throwable to log with .toString and .printStackTrace - @see #setThrowableTraces to turn on stack traces. - */ - public static void print(int loglevel, Throwable t) - { - if (debug) { - String timestr = ""; - String[] data = getTraceElements(); - if (debugging(data[0], loglevel)) { - if (timing) { - long now = System.currentTimeMillis(); - timestr = "{" + (now-last) + "} "; - last = now; - } - String[] lines = null; - if (ttrace) { - StackTraceElement[] ste = t.getStackTrace(); - lines = new String[ste.length]; - for (int i = 0; i < ste.length; i++) - lines[i] = "\tat "+ste[i].toString(); - } - _print(t.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, t.toString(), lines); - } - } - } - /** - Log a byte array - @param loglevel The level to log at (DEBUG, WARN, etc) - @param b The byte array to print. - @see #setHexDump to enable hex dumping. - @see #setByteArrayCount to change how many bytes are printed. - @see #setByteArrayWidth to change the formatting width of hex. */ - public static void print(int loglevel, byte[] b) - { - if (debug) { - String timestr = ""; - String[] data = getTraceElements(); - if (debugging(data[0], loglevel)) { - if (timing) { - long now = System.currentTimeMillis(); - timestr = "{" + (now-last) + "} "; - last = now; - } - String[] lines = null; - if (hexdump) { - if (balen >= b.length) - lines = Hexdump.format(b, bawidth).split("\n"); - else { - byte[] buf = new byte[balen]; - System.arraycopy(b, 0, buf, 0, balen); - lines = Hexdump.format(buf, bawidth).split("\n"); - } - } - _print(b.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, b.length+" bytes", lines); - } - } - } - /** - Log a String - @param loglevel The level to log at (DEBUG, WARN, etc) - @param s The string to log with d.toString() - */ - public static void print(int loglevel, String s) - { - if (debug) - print(loglevel, (Object) s); - } - /** - Log an Object - @param c The class doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param d The object to log with d.toString() - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Class c, int loglevel, Object d) - { - if (debug) { - saveclass = c; - print(loglevel, d); - } - } - /** - Log a String - @param c The class doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param s The log message - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Class c, int loglevel, String s) - { - if (debug) { - saveclass = c; - print(loglevel, s); - } - } - private static String[] getTraceElements() - { - String[] data = new String[] { "", "", "" }; - try { - Method m = Thread.class.getMethod("getStackTrace", new Class[0]); - StackTraceElement[] stes = (StackTraceElement[]) m.invoke(Thread.currentThread(), new Object[0]); - for (StackTraceElement ste: stes) { - if (Debug.class.getName().equals(ste.getClassName())) continue; - if (Thread.class.getName().equals(ste.getClassName())) continue; - if (Method.class.getName().equals(ste.getClassName())) continue; - if (ste.getClassName().startsWith("sun.reflect")) continue; - data[0] = ste.getClassName(); - data[1] = ste.getMethodName(); - if (lines) - data[2] = " "+ste.getFileName()+":"+ste.getLineNumber(); - break; - } - } catch (NoSuchMethodException NSMe) { - if (null != saveclass) - data[0] = saveclass.getName(); - } catch (IllegalAccessException IAe) { - } catch (InvocationTargetException ITe) { - } - return data; - } - /** - Log an Object - @param loglevel The level to log at (DEBUG, WARN, etc) - @param o The object to log - */ - public static void print(int loglevel, Object o) - { - if (debug) { - String timestr = ""; - String[] data = getTraceElements(); - if (debugging(data[0], loglevel)) { - if (timing) { - long now = System.currentTimeMillis(); - timestr = "{" + (now-last) + "} "; - last = now; - } - _print(o.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, o.toString(), null); - } - } - } - /** - Log a Map - @param o The object doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param m The Map to print out - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void printMap(Object o, int loglevel, Map m) - { - if (debug) { - if (o instanceof Class) - saveclass = (Class) o; - else - saveclass = o.getClass(); - printMap(loglevel, m); - } - } - /** - Log a Map - @param c The class doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param m The Map to print out - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void printMap(Class c, int loglevel, Map m) - { - if (debug) { - saveclass = c; - printMap(loglevel, m); - } - } - /** - Log a Map at DEBUG log level - @param m The Map to print out - */ - public static void printMap(Map m) - { - printMap(DEBUG, m); - } - /** - Log a Map - @param loglevel The level to log at (DEBUG, WARN, etc) - @param m The Map to print out - */ - public static void printMap(int loglevel, Map m) - { - if (debug) { - String timestr = ""; - String[] data = getTraceElements(); - if (debugging(data[0], loglevel)) { - if (timing) { - long now = System.currentTimeMillis(); - timestr = "{" + (now-last) + "} "; - last = now; - } - Iterator i = m.keySet().iterator(); - String[] lines = new String[m.size()]; - int j = 0; - while (i.hasNext()) { - Object key = i.next(); - lines[j++] = "\t\t- "+key+" => "+m.get(key); - } - _print(m.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, "Map:", lines); - } - } - } - /** - Enable or disable stack traces in Debuging throwables. - */ - public static void setThrowableTraces(boolean ttrace) - { - Debug.ttrace = ttrace; - } - /** - Enable or disable timing in Debug messages. - */ - public static void setTiming(boolean timing) - { - Debug.timing = timing; - } - /** - Enable or disable line numbers. - */ - public static void setLineNos(boolean lines) - { - Debug.lines = lines; - } - /** - Enable or disable hexdumps. - */ - public static void setHexDump(boolean hexdump) - { - Debug.hexdump = hexdump; - } - /** - Set the size of hexdumps. - (Default: 36) - */ - public static void setByteArrayCount(int count) - { - Debug.balen = count; - } - /** - Set the formatted width of hexdumps. - (Default: 80 chars) - */ - public static void setByteArrayWidth(int width) - { - Debug.bawidth = width; - } - /** - Add a filter command for a specific type. - This command will be called with the output stream - and the text to be sent. It should perform any - changes necessary to the text and then print the - result to the output stream. - */ - public static void addFilterCommand(Class c, FilterCommand f) - //TODO 1.5: public static void addFilterCommand(Class c, FilterCommand f) - { - filterMap.put(c, f); - } - private static void _print(Class c, int level, String loc, String extra, String message, String[] lines) - { - //TODO 1.5: FilterCommand f = filterMap.get(c); - FilterCommand f = (FilterCommand) filterMap.get(c); - if (null == f) { - debugout.println("["+loc+"] " +extra + message); - if (null != lines) - for (String s: lines) - debugout.println(s); - } else - f.filter(debugout, level, loc, extra, message, lines); - } -} diff --git a/app/src/main/java/cx/ath/matthew/debug/Debug.jpp b/app/src/main/java/cx/ath/matthew/debug/Debug.jpp deleted file mode 100644 index 56fd4ac7..00000000 --- a/app/src/main/java/cx/ath/matthew/debug/Debug.jpp +++ /dev/null @@ -1,597 +0,0 @@ -/* - * Java Debug Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.debug; - -import cx.ath.matthew.utils.Hexdump; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.PrintStream; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Properties; - -/** - Add debugging to your program, has support for large projects with multiple - classes and debug levels per class. Supports optional enabling of debug - per-level per-class and debug targets of files, Streams or stderr. - Also supports timing between debug outputs, printing of stack traces for Throwables - and files/line numbers on each message. -

- Debug now automatically figures out which class it was called from, so all - methods passing in the calling class are deprecated. -

-

- The defaults are to print all messages to stderr with class and method name. -

-

- Should be called like this: -

-
-   if (Debug.debug) Debug.print(Debug.INFO, "Debug Message");
-  
- */ -public class Debug -{ - /** - This interface can be used to provide custom printing filters - for certain classes. - */ - public static interface FilterCommand - { - /** - Called to print debug messages with a custom filter. - @param output The PrintStream to output to. - @param level The debug level of this message. - @param location The textual location of the message. - @param extra Extra information such as timing details. - @param message The debug message. - @param lines Other lines of a multiple-line debug message. - */ - public void filter(PrintStream output, int level, String location, String extra, String message, String[] lines); - } - /** Highest priority messages */ - public static final int CRIT = 1; - /** Error messages */ - public static final int ERR = 2; - /** Warnings */ - public static final int WARN = 3; - /** Information */ - public static final int INFO = 4; - /** Debug messages */ - public static final int DEBUG = 5; - /** Verbose debug messages */ - public static final int VERBOSE = 6; - /** Set this to false to disable compilation of Debug statements */ - public static final boolean debug = DEBUGSETTING; - /** The current output stream (defaults to System.err) */ - public static PrintStream debugout = System.err; - private static Properties prop = null; - private static boolean timing = false; - private static boolean ttrace = false; - private static boolean lines = false; - private static boolean hexdump = false; - private static long last = 0; - private static int balen = 36; - private static int bawidth = 80; - private static Class saveclass = null; - //TODO: 1.5 private static Map, FilterCommand> filterMap = new HashMap, FilterCommand>(); - private static Map filterMap = new HashMap(); - /** - Set properties to configure debugging. - Format of properties is class => level, e.g. -
-      cx.ath.matthew.io.TeeOutputStream = INFO
-      cx.ath.matthew.io.DOMPrinter = DEBUG
-     
- The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which - correspond to all messages up to that level. The special words YES, ALL and TRUE - cause all messages to be printed regardless of level. All other terms disable - messages for that class. CRIT and ERR messages are always printed if debugging is enabled - unless explicitly disabled. - The special class name ALL can be used to set the default level for all classes. - @param prop Properties object to use. - */ - public static void setProperties(Properties prop) - { - Debug.prop = prop; - } - /** - Read which class to debug on at which level from the given File. - Syntax the same as Java Properties files: -
-     <class> = <debuglevel>
-     
- E.G. -
-      cx.ath.matthew.io.TeeOutputStream = INFO
-      cx.ath.matthew.io.DOMPrinter = DEBUG
-     
- The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which - correspond to all messages up to that level. The special words YES, ALL and TRUE - cause all messages to be printed regardless of level. All other terms disable - messages for that class. CRIT and ERR messages are always printed if debugging is enabled - unless explicitly disabled. - The special class name ALL can be used to set the default level for all classes. - @param f File to read from. - */ - public static void loadConfig(File f) throws IOException - { - prop = new Properties(); - prop.load(new FileInputStream(f)); - } - /** @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. */ - //TODO: 1.5 @Deprecated() - public static boolean debugging(Class c, int loglevel) - { - if (debug) { - if (null == c) return true; - return debugging(c.getName(), loglevel); - } - return false; - } - public static boolean debugging(String s, int loglevel) - { - if (debug) { - try { - if (null == s) return true; - if (null == prop) return loglevel <= DEBUG; - String d = prop.getProperty(s); - if (null == d || "".equals(d)) d = prop.getProperty("ALL"); - if (null == d) return loglevel <= ERR; - if ("".equals(d)) return loglevel <= ERR; - d = d.toLowerCase(); - if ("true".equals(d)) return true; - if ("yes".equals(d)) return true; - if ("all".equals(d)) return true; - if ("verbose".equals(d)) return loglevel <= VERBOSE; - if ("debug".equals(d)) return loglevel <= DEBUG; - if ("info".equals(d)) return loglevel <= INFO; - if ("warn".equals(d)) return loglevel <= WARN; - if ("err".equals(d)) return loglevel <= ERR; - if ("crit".equals(d)) return loglevel <= CRIT; - int i = Integer.parseInt(d); return i >= loglevel; - } catch (Exception e) { return false; } - } - return false; - } - - /** - Output to the given Stream */ - public static void setOutput(PrintStream p) throws IOException - { - debugout = p; - } - /** - Output to the given file */ - public static void setOutput(String filename) throws IOException - { - debugout = new PrintStream(new FileOutputStream(filename, true)); - } - - /** - Output to the default debug.log */ - public static void setOutput() throws IOException { - setOutput("./debug.log"); - } - /** - Log at DEBUG - @param d The object to log */ - public static void print(Object d) - { - if (debug) { - if (d instanceof String) - print(DEBUG, (String) d); - else if (d instanceof Throwable) - print(DEBUG, (Throwable) d); - else if (d instanceof byte[]) - print(DEBUG, (byte[]) d); - else if (d instanceof Map) - printMap(DEBUG, (Map) d); - else print(DEBUG, d); - } - } - /** - Log at DEBUG - @param o The object doing the logging - @param d The object to log - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Object o, Object d) - { - if (debug) { - if (o instanceof Class) - saveclass = (Class) o; - else - saveclass = o.getClass(); - print(d); - } - } - - /** - Log an Object - @param o The object doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param d The object to log with d.toString() - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Object o, int loglevel, Object d) - { - if (debug) { - if (o instanceof Class) - saveclass = (Class) o; - else - saveclass = o.getClass(); - print(loglevel, d); - } - } - /** - Log a String - @param o The object doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param s The log message - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Object o, int loglevel, String s) - { - if (debug) { - if (o instanceof Class) - saveclass = (Class) o; - else - saveclass = o.getClass(); - print(loglevel, s); - } - } - /** - Log a Throwable - @param o The object doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param t The throwable to log with .toString and .printStackTrace - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Object o, int loglevel, Throwable t) - { - if (debug) { - if (o instanceof Class) - saveclass = (Class) o; - else - saveclass = o.getClass(); - print(loglevel, t); - } - } - - /** - Log a Throwable - @param c The class doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param t The throwable to log with .toString and .printStackTrace - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Class c, int loglevel, Throwable t) - { - if (debug) { - saveclass = c; - print(loglevel, t); - } - } - /** - Log a Throwable - @param loglevel The level to log at (DEBUG, WARN, etc) - @param t The throwable to log with .toString and .printStackTrace - @see #setThrowableTraces to turn on stack traces. - */ - public static void print(int loglevel, Throwable t) - { - if (debug) { - String timestr = ""; - String[] data = getTraceElements(); - if (debugging(data[0], loglevel)) { - if (timing) { - long now = System.currentTimeMillis(); - timestr = "{" + (now-last) + "} "; - last = now; - } - String[] lines = null; - if (ttrace) { - StackTraceElement[] ste = t.getStackTrace(); - lines = new String[ste.length]; - for (int i = 0; i < ste.length; i++) - lines[i] = "\tat "+ste[i].toString(); - } - _print(t.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, t.toString(), lines); - } - } - } - - /** - Log a byte array - @param loglevel The level to log at (DEBUG, WARN, etc) - @param b The byte array to print. - @see #setHexDump to enable hex dumping. - @see #setByteArrayCount to change how many bytes are printed. - @see #setByteArrayWidth to change the formatting width of hex. */ - public static void print(int loglevel, byte[] b) - { - if (debug) { - String timestr = ""; - String[] data = getTraceElements(); - if (debugging(data[0], loglevel)) { - if (timing) { - long now = System.currentTimeMillis(); - timestr = "{" + (now-last) + "} "; - last = now; - } - String[] lines = null; - if (hexdump) { - if (balen >= b.length) - lines = Hexdump.format(b, bawidth).split("\n"); - else { - byte[] buf = new byte[balen]; - System.arraycopy(b, 0, buf, 0, balen); - lines = Hexdump.format(buf, bawidth).split("\n"); - } - } - _print(b.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, b.length+" bytes", lines); - } - } - } - /** - Log a String - @param loglevel The level to log at (DEBUG, WARN, etc) - @param s The string to log with d.toString() - */ - public static void print(int loglevel, String s) - { - if (debug) - print(loglevel, (Object) s); - } - /** - Log an Object - @param c The class doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param d The object to log with d.toString() - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Class c, int loglevel, Object d) - { - if (debug) { - saveclass = c; - print(loglevel, d); - } - } - /** - Log a String - @param c The class doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param s The log message - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void print(Class c, int loglevel, String s) - { - if (debug) { - saveclass = c; - print(loglevel, s); - } - } - private static String[] getTraceElements() - { - String[] data = new String[] { "", "", "" }; - try { - Method m = Thread.class.getMethod("getStackTrace", new Class[0]); - StackTraceElement[] stes = (StackTraceElement[]) m.invoke(Thread.currentThread(), new Object[0]); - for (StackTraceElement ste: stes) { - if (Debug.class.getName().equals(ste.getClassName())) continue; - if (Thread.class.getName().equals(ste.getClassName())) continue; - if (Method.class.getName().equals(ste.getClassName())) continue; - if (ste.getClassName().startsWith("sun.reflect")) continue; - data[0] = ste.getClassName(); - data[1] = ste.getMethodName(); - if (lines) - data[2] = " "+ste.getFileName()+":"+ste.getLineNumber(); - break; - } - } catch (NoSuchMethodException NSMe) { - if (null != saveclass) - data[0] = saveclass.getName(); - } catch (IllegalAccessException IAe) { - } catch (InvocationTargetException ITe) { - } - return data; - } - /** - Log an Object - @param loglevel The level to log at (DEBUG, WARN, etc) - @param o The object to log - */ - public static void print(int loglevel, Object o) - { - if (debug) { - String timestr = ""; - String[] data = getTraceElements(); - if (debugging(data[0], loglevel)) { - if (timing) { - long now = System.currentTimeMillis(); - timestr = "{" + (now-last) + "} "; - last = now; - } - _print(o.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, o.toString(), null); - } - } - } - - /** - Log a Map - @param o The object doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param m The Map to print out - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void printMap(Object o, int loglevel, Map m) - { - if (debug) { - if (o instanceof Class) - saveclass = (Class) o; - else - saveclass = o.getClass(); - printMap(loglevel, m); - } - } - /** - Log a Map - @param c The class doing the logging - @param loglevel The level to log at (DEBUG, WARN, etc) - @param m The Map to print out - @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. - */ - //TODO: 1.5 @Deprecated() - public static void printMap(Class c, int loglevel, Map m) - { - if (debug) { - saveclass = c; - printMap(loglevel, m); - } - } - /** - Log a Map at DEBUG log level - @param m The Map to print out - */ - public static void printMap(Map m) - { - printMap(DEBUG, m); - } - /** - Log a Map - @param loglevel The level to log at (DEBUG, WARN, etc) - @param m The Map to print out - */ - public static void printMap(int loglevel, Map m) - { - if (debug) { - String timestr = ""; - String[] data = getTraceElements(); - if (debugging(data[0], loglevel)) { - if (timing) { - long now = System.currentTimeMillis(); - timestr = "{" + (now-last) + "} "; - last = now; - } - Iterator i = m.keySet().iterator(); - String[] lines = new String[m.size()]; - int j = 0; - while (i.hasNext()) { - Object key = i.next(); - lines[j++] = "\t\t- "+key+" => "+m.get(key); - } - _print(m.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, "Map:", lines); - } - } - } - /** - Enable or disable stack traces in Debuging throwables. - */ - public static void setThrowableTraces(boolean ttrace) - { - Debug.ttrace = ttrace; - } - /** - Enable or disable timing in Debug messages. - */ - public static void setTiming(boolean timing) - { - Debug.timing = timing; - } - /** - Enable or disable line numbers. - */ - public static void setLineNos(boolean lines) - { - Debug.lines = lines; - } - /** - Enable or disable hexdumps. - */ - public static void setHexDump(boolean hexdump) - { - Debug.hexdump = hexdump; - } - /** - Set the size of hexdumps. - (Default: 36) - */ - public static void setByteArrayCount(int count) - { - Debug.balen = count; - } - /** - Set the formatted width of hexdumps. - (Default: 80 chars) - */ - public static void setByteArrayWidth(int width) - { - Debug.bawidth = width; - } - /** - Add a filter command for a specific type. - This command will be called with the output stream - and the text to be sent. It should perform any - changes necessary to the text and then print the - result to the output stream. - */ - public static void addFilterCommand(Class c, FilterCommand f) - //TODO 1.5: public static void addFilterCommand(Class c, FilterCommand f) - { - filterMap.put(c, f); - } - private static void _print(Class c, int level, String loc, String extra, String message, String[] lines) - { - //TODO 1.5: FilterCommand f = filterMap.get(c); - FilterCommand f = (FilterCommand) filterMap.get(c); - if (null == f) { - debugout.println("["+loc+"] " +extra + message); - if (null != lines) - for (String s: lines) - debugout.println(s); - } else - f.filter(debugout, level, loc, extra, message, lines); - } -} - - diff --git a/app/src/main/java/cx/ath/matthew/io/DOMPrinter.java b/app/src/main/java/cx/ath/matthew/io/DOMPrinter.java deleted file mode 100644 index e61fa779..00000000 --- a/app/src/main/java/cx/ath/matthew/io/DOMPrinter.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Java DOM Printing Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.io; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; - -import org.w3c.dom.Document; -import org.w3c.dom.DocumentType; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -/** - * Print a DOM tree to the given OutputStream - */ -public class DOMPrinter -{ - /** - * Print the given node and all its children. - * @param n The Node to print. - * @param os The Stream to print to. - */ - public static void printNode(Node n, OutputStream os) - { - PrintStream p = new PrintStream(os); - printNode(n, p); - } - /** - * Print the given node and all its children. - * @param n The Node to print. - * @param p The Stream to print to. - */ - public static void printNode(Node n, PrintStream p) - { - if (null != n.getNodeValue()) p.print(n.getNodeValue()); - else { - p.print("<"+n.getNodeName()); - if (n.hasAttributes()) { - NamedNodeMap nnm = n.getAttributes(); - for (int i = 0; i < nnm.getLength(); i++) { - Node attr = nnm.item(i); - p.print(" "+attr.getNodeName()+"='"+attr.getNodeValue()+"'"); - } - } - if (n.hasChildNodes()) { - p.print(">"); - NodeList nl = n.getChildNodes(); - for (int i = 0; i < nl.getLength(); i++) - printNode(nl.item(i), p); - p.print(""); - } else { - p.print("/>"); - } - } - } - /** - * Print the given document and all its children. - * @param d The Document to print. - * @param p The Stream to print to. - */ - public static void printDOM(Document d, PrintStream p) - { - DocumentType dt = d.getDoctype(); - if (null != dt) { - p.print(""); - } - Element e = d.getDocumentElement(); - printNode(e, p); - } - /** - * Print the given document and all its children. - * @param d The Document to print. - * @param os The Stream to print to. - */ - public static void printDOM(Document d, OutputStream os) - { - PrintStream p = new PrintStream(os); - printDOM(d, p); - } -} - diff --git a/app/src/main/java/cx/ath/matthew/io/ExecInputStream.java b/app/src/main/java/cx/ath/matthew/io/ExecInputStream.java deleted file mode 100644 index 517cded8..00000000 --- a/app/src/main/java/cx/ath/matthew/io/ExecInputStream.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Java Exec Pipe Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.io; - -import java.io.FilterInputStream; -import java.io.InputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * Class to pipe an InputStream through a command using stdin/stdout. - * E.g. - *
- *    Reader r = new InputStreamReader(new ExecInputStream(new FileInputStream("file"), "command"));
- * 
- */ -public class ExecInputStream extends FilterInputStream -{ - private Process proc; - private InputStream stdout; - private OutputStream stdin; - private InOutCopier copy; - - /** - * Create a new ExecInputStream on the given InputStream - * using the process to filter the stream. - * @param is Reads from this InputStream - * @param p Filters data through stdin/out on this Process - */ - public ExecInputStream(InputStream is, Process p) throws IOException - { - super(is); - proc = p; - stdin = p.getOutputStream(); - stdout = p.getInputStream(); - copy = new InOutCopier(in, stdin); - copy.start(); - } - /** - * Create a new ExecInputStream on the given InputStream - * using the process to filter the stream. - * @param is Reads from this InputStream - * @param cmd Creates a Process from this string to filter data through stdin/out - */ - public ExecInputStream(InputStream is, String cmd) throws IOException - { this(is, Runtime.getRuntime().exec(cmd)); } - /** - * Create a new ExecInputStream on the given InputStream - * using the process to filter the stream. - * @param is Reads from this InputStream - * @param cmd Creates a Process from this string array (command, arg, ...) to filter data through stdin/out - */ - public ExecInputStream(InputStream is, String[] cmd) throws IOException - { this(is, Runtime.getRuntime().exec(cmd)); } - /** - * Create a new ExecInputStream on the given InputStream - * using the process to filter the stream. - * @param is Reads from this InputStream - * @param cmd Creates a Process from this string to filter data through stdin/out - * @param env Setup the environment for the command - */ - public ExecInputStream(InputStream is, String cmd, String[] env) throws IOException - { this(is, Runtime.getRuntime().exec(cmd, env)); } - /** - * Create a new ExecInputStream on the given InputStream - * using the process to filter the stream. - * @param is Reads from this InputStream - * @param cmd Creates a Process from this string array (command, arg, ...) to filter data through stdin/out - * @param env Setup the environment for the command - */ - public ExecInputStream(InputStream is, String[] cmd, String[] env) throws IOException - { this(is, Runtime.getRuntime().exec(cmd, env)); } - - public void close() throws IOException - { - try { - proc.waitFor(); - } catch (InterruptedException Ie) {} - //copy.close(); - try { - copy.join(); - } catch (InterruptedException Ie) {} - stdin.close(); - in.close(); - stdout.close(); - } - public void flush() throws IOException - { - copy.flush(); - } - public int available() throws IOException - { return stdout.available(); } - public int read() throws IOException - { return stdout.read(); } - public int read(byte[] b) throws IOException - { return stdout.read(b); } - public int read(byte[] b, int off, int len) throws IOException - { return stdout.read(b, off, len); } - public long skip(long n) throws IOException - { return stdout.skip(n); } - public void mark(int readlimit) - {} - public boolean markSupported() - { return false; } - public void reset() - {} - - public void finalize() - { - try { - close(); - } catch (Exception e) {} - } -} - - - diff --git a/app/src/main/java/cx/ath/matthew/io/ExecOutputStream.java b/app/src/main/java/cx/ath/matthew/io/ExecOutputStream.java deleted file mode 100644 index 9ee92d6c..00000000 --- a/app/src/main/java/cx/ath/matthew/io/ExecOutputStream.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Java Exec Pipe Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.io; - -import java.io.FilterOutputStream; -import java.io.InputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * Class to pipe an OutputStream through a command using stdin/stdout. - * E.g. - *
- *    Writer w = new OutputStreamWriter(new ExecOutputStream(new FileOutputStream("file"), "command"));
- * 
- */ -public class ExecOutputStream extends FilterOutputStream -{ - private Process proc; - private InputStream stdout; - private OutputStream stdin; - private InOutCopier copy; - - /** - * Create a new ExecOutputStream on the given OutputStream - * using the process to filter the stream. - * @param os Writes to this OutputStream - * @param p Filters data through stdin/out on this Process - */ - public ExecOutputStream(OutputStream os, Process p) throws IOException - { - super(os); - proc = p; - stdin = p.getOutputStream(); - stdout = p.getInputStream(); - copy = new InOutCopier(stdout, out); - copy.start(); - } - /** - * Create a new ExecOutputStream on the given OutputStream - * using the process to filter the stream. - * @param os Writes to this OutputStream - * @param cmd Creates a Process from this string to filter data through stdin/out - */ - public ExecOutputStream(OutputStream os, String cmd) throws IOException - { this(os, Runtime.getRuntime().exec(cmd)); } - /** - * Create a new ExecOutputStream on the given OutputStream - * using the process to filter the stream. - * @param os Writes to this OutputStream - * @param cmd Creates a Process from this string array (command, arg, ...) to filter data through stdin/out - */ - public ExecOutputStream(OutputStream os, String[] cmd) throws IOException - { this(os, Runtime.getRuntime().exec(cmd)); } - /** - * Create a new ExecOutputStream on the given OutputStream - * using the process to filter the stream. - * @param os Writes to this OutputStream - * @param cmd Creates a Process from this string to filter data through stdin/out - * @param env Setup the environment for the command - */ - public ExecOutputStream(OutputStream os, String cmd, String[] env) throws IOException - { this(os, Runtime.getRuntime().exec(cmd, env)); } - /** - * Create a new ExecOutputStream on the given OutputStream - * using the process to filter the stream. - * @param os Writes to this OutputStream - * @param cmd Creates a Process from this string array (command, arg, ...) to filter data through stdin/out - * @param env Setup the environment for the command - */ - public ExecOutputStream(OutputStream os, String[] cmd, String[] env) throws IOException - { this(os, Runtime.getRuntime().exec(cmd, env)); } - - public void close() throws IOException - { - stdin.close(); - try { - proc.waitFor(); - } catch (InterruptedException Ie) {} - //copy.close(); - try { - copy.join(); - } catch (InterruptedException Ie) {} - stdout.close(); - out.close(); - } - public void flush() throws IOException - { - stdin.flush(); - copy.flush(); - out.flush(); - } - public void write(byte[] b) throws IOException - { - stdin.write(b); - } - public void write(byte[] b, int off, int len) throws IOException - { - stdin.write(b, off, len); - } - public void write(int b) throws IOException - { - stdin.write(b); - } - public void finalize() - { - try { - close(); - } catch (Exception e) {} - } -} - diff --git a/app/src/main/java/cx/ath/matthew/io/InOutCopier.java b/app/src/main/java/cx/ath/matthew/io/InOutCopier.java deleted file mode 100644 index 49688539..00000000 --- a/app/src/main/java/cx/ath/matthew/io/InOutCopier.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Java Exec Pipe Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.io; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Copies from an input stream to an output stream using a Thread. - * example: - * - *
- * InputStream a = getInputStream();
- * OutputStream b = getOutputStream();
- * InOutCopier copier = new InOutCopier(a, b);
- * copier.start();
- * <do stuff that writes to the inputstream>
- * 
- */ -public class InOutCopier extends Thread -{ - private static final int BUFSIZE=1024; - private static final int POLLTIME=100; - private BufferedInputStream is; - private OutputStream os; - private boolean enable; - /** - * Create a copier from an inputstream to an outputstream - * @param is The stream to copy from - * @param os the stream to copy to - */ - public InOutCopier(InputStream is, OutputStream os) throws IOException - { - this.is = new BufferedInputStream(is); - this.os = os; - this.enable = true; - } - /** - * Force close the stream without waiting for EOF on the source - */ - public void close() - { - enable = false; - interrupt(); - } - /** - * Flush the outputstream - */ - public void flush() throws IOException - { - os.flush(); - } - /** Start the thread and wait to make sure its really started */ - public synchronized void start() - { - super.start(); - try { - wait(); - } catch (InterruptedException Ie) {} - } - /** - * Copies from the inputstream to the outputstream - * until EOF on the inputstream or explicitly closed - * @see #close() - */ - public void run() - { - byte[] buf = new byte[BUFSIZE]; - synchronized (this) { - notifyAll(); - } - while (enable) - try { - int n = is.read(buf); - if (0 > n) - break; - if (0 < n) { - os.write(buf, 0, (n> BUFSIZE? BUFSIZE:n)); - os.flush(); - } - } catch (IOException IOe) { - break; - } - try { os.close(); } catch (IOException IOe) {} - } -} - diff --git a/app/src/main/java/cx/ath/matthew/io/TeeInputStream.java b/app/src/main/java/cx/ath/matthew/io/TeeInputStream.java deleted file mode 100644 index f769b114..00000000 --- a/app/src/main/java/cx/ath/matthew/io/TeeInputStream.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Java Tee Stream Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.io; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.FilterInputStream; -import java.io.InputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * Class to copy a stream to a file or another stream as it is being sent through a stream pipe - * E.g. - *
- *    Reader r = new InputStreamReader(new TeeInputStream(new FileInputStream("file"), new File("otherfile")));
- * 
- */ -public class TeeInputStream extends FilterInputStream -{ - private InputStream in; - private OutputStream fos; - /** - * Create a new TeeInputStream on the given InputStream - * and copy the stream to the given File. - * @param is Reads from this InputStream - * @param tos Write to this OutputStream - */ - public TeeInputStream(InputStream is, OutputStream tos) throws IOException - { - super(is); - this.in = is; - this.fos = tos; - } - /** - * Create a new TeeInputStream on the given InputStream - * and copy the stream to the given File. - * @param is Reads from this InputStream - * @param f Write to this File - * @param append Append to file not overwrite - */ - public TeeInputStream(InputStream is, File f, boolean append) throws IOException - { - super(is); - this.in = is; - this.fos = new FileOutputStream(f, append); - } - /** - * Create a new TeeInputStream on the given InputStream - * and copy the stream to the given File. - * @param is Reads from this InputStream - * @param f Write to this File - */ - public TeeInputStream(InputStream is, File f) throws IOException - { - super(is); - this.in = is; - this.fos = new FileOutputStream(f); - } - /** - * Create a new TeeInputStream on the given InputStream - * and copy the stream to the given File. - * @param is Reads from this InputStream - * @param f Write to this File - * @param append Append to file not overwrite - */ - public TeeInputStream(InputStream is, String f, boolean append) throws IOException - { - this(is, new File(f), append); - } - /** - * Create a new TeeInputStream on the given InputStream - * and copy the stream to the given File. - * @param is Reads from this InputStream - * @param f Write to this File - */ - public TeeInputStream(InputStream is, String f) throws IOException - { - this(is, new File(f)); - } - public void close() throws IOException - { - in.close(); - fos.close(); - } - public void flush() throws IOException - { - fos.flush(); - } - public int available() throws IOException - { - return in.available(); - } - public int read() throws IOException - { - int i = in.read(); - if (-1 != i) fos.write(i); - return i; - } - public int read(byte[] b) throws IOException - { - int c = in.read(b); - if (-1 != c) fos.write(b, 0, c); - return c; - } - public int read(byte[] b, int off, int len) throws IOException - { - int c = in.read(b, off, len); - if (-1 != c) fos.write(b, off, c); - return c; - } - public long skip(long n) throws IOException - { return in.skip(n); } - public void mark(int readlimit) - {} - public boolean markSupported() - { return false; } - public void reset() throws IOException - { in.reset(); } - - public void finalize() - { - try { - close(); - } catch (Exception e) {} - } -} - - - diff --git a/app/src/main/java/cx/ath/matthew/io/TeeOutputStream.java b/app/src/main/java/cx/ath/matthew/io/TeeOutputStream.java deleted file mode 100644 index 30509230..00000000 --- a/app/src/main/java/cx/ath/matthew/io/TeeOutputStream.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Java Tee Stream Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.io; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.FilterOutputStream; -import java.io.OutputStream; -import java.io.IOException; - -/** - * Class to copy a stream to another stream or file as it is being sent through a stream pipe - * E.g. - *
- *    PrintWriter r = new PrintWriter(new TeeOutputStream(new FileOutputStream("file"), new File("otherfile")));
- * 
- */ -public class TeeOutputStream extends FilterOutputStream -{ - private File f; - private OutputStream out; - private OutputStream fos; - /** - * Create a new TeeOutputStream on the given OutputStream - * and copy the stream to the other OuputStream. - * @param os Writes to this OutputStream - * @param tos Write to this OutputStream - */ - public TeeOutputStream(OutputStream os, OutputStream tos) throws IOException - { - super(os); - this.out = os; - this.fos = tos; - } - /** - * Create a new TeeOutputStream on the given OutputStream - * and copy the stream to the given File. - * @param os Writes to this OutputStream - * @param f Write to this File - * @param append Append to file not overwrite - */ - public TeeOutputStream(OutputStream os, File f, boolean append) throws IOException - { - super(os); - this.out = os; - this.fos = new FileOutputStream(f, append); - } - /** - * Create a new TeeOutputStream on the given OutputStream - * and copy the stream to the given File. - * @param os Writes to this OutputStream - * @param f Write to this File - */ - public TeeOutputStream(OutputStream os, File f) throws IOException - { - super(os); - this.out = os; - this.fos = new FileOutputStream(f); - } - /** - * Create a new TeeOutputStream on the given OutputStream - * and copy the stream to the given File. - * @param os Writes to this OutputStream - * @param f Write to this File - * @param append Append to file not overwrite - */ - public TeeOutputStream(OutputStream os, String f, boolean append) throws IOException - { - this(os, new File(f), append); - } - /** - * Create a new TeeOutputStream on the given OutputStream - * and copy the stream to the given File. - * @param os Writes to this OutputStream - * @param f Write to this File - */ - public TeeOutputStream(OutputStream os, String f) throws IOException - { - this(os, new File(f)); - } - public void close() throws IOException - { - out.close(); - fos.close(); - } - public void flush() throws IOException - { - fos.flush(); - out.flush(); - } - public void write(int b) throws IOException - { - fos.write(b); - out.write(b); - } - public void write(byte[] b) throws IOException - { - fos.write(b); - out.write(b); - } - public void write(byte[] b, int off, int len) throws IOException - { - fos.write(b, off, len); - out.write(b, off, len); - } - - public void finalize() - { - try { - close(); - } catch (Exception e) {} - } -} - - - diff --git a/app/src/main/java/cx/ath/matthew/io/test.java b/app/src/main/java/cx/ath/matthew/io/test.java deleted file mode 100644 index 0f3c9e43..00000000 --- a/app/src/main/java/cx/ath/matthew/io/test.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Java IO Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.io; -import java.io.PrintWriter; -import java.io.OutputStreamWriter; -class test -{ - public static void main(String[] args) throws Exception - { - PrintWriter out = new PrintWriter(new OutputStreamWriter(new ExecOutputStream(System.out, "xsltproc mcr.xsl -")));///java cx.ath.matthew.io.findeof"))); - - out.println(""); - out.println(" "); - out.println(" "); - out.println(" TEST"); - out.println(" "); - out.println("hello, he is helping tie up helen's lemmings"); - out.println("we are being followed and we break out"); - out.println(" "); - out.println(" "); - out.close(); - } -} diff --git a/app/src/main/java/cx/ath/matthew/io/test2.java b/app/src/main/java/cx/ath/matthew/io/test2.java deleted file mode 100644 index bd7a047d..00000000 --- a/app/src/main/java/cx/ath/matthew/io/test2.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Java IO Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.io; -import java.io.BufferedReader; -import java.io.InputStreamReader; -class test2 -{ - public static void main(String[] args) throws Exception - { - BufferedReader in = new BufferedReader(new InputStreamReader(new ExecInputStream(System.in, "xsltproc mcr.xsl -"))); - String s; - while (null != (s = in.readLine())) System.out.println(s); - } -} diff --git a/app/src/main/java/cx/ath/matthew/io/test3.java b/app/src/main/java/cx/ath/matthew/io/test3.java deleted file mode 100644 index 5d40214b..00000000 --- a/app/src/main/java/cx/ath/matthew/io/test3.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Java IO Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.io; -import java.io.PrintWriter; -import java.io.BufferedReader; -import java.io.InputStreamReader; -class test3 -{ - public static void main(String[] args) throws Exception - { - String file = args[0]; - PrintWriter p = new PrintWriter(new TeeOutputStream(System.out, file)); - BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); - String s; - while (null != (s = r.readLine())) - p.println(s); - p.close(); - r.close(); - } -} diff --git a/app/src/main/java/cx/ath/matthew/unix/NotConnectedException.java b/app/src/main/java/cx/ath/matthew/unix/NotConnectedException.java deleted file mode 100644 index 7f5c4e75..00000000 --- a/app/src/main/java/cx/ath/matthew/unix/NotConnectedException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Java Unix Sockets Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ -package cx.ath.matthew.unix; - -import java.net.SocketException; - -public class NotConnectedException extends SocketException -{ - public NotConnectedException() - { - super("The Socket is Not Connected"); - } -} diff --git a/app/src/main/java/cx/ath/matthew/unix/USInputStream.java b/app/src/main/java/cx/ath/matthew/unix/USInputStream.java deleted file mode 100644 index 95a95e6d..00000000 --- a/app/src/main/java/cx/ath/matthew/unix/USInputStream.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Java Unix Sockets Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ -package cx.ath.matthew.unix; - -import java.io.InputStream; -import java.io.IOException; - -public class USInputStream extends InputStream -{ - public static final int MSG_DONTWAIT = 0x40; - private native int native_recv(int sock, byte[] b, int off, int len, int flags, int timeout) throws IOException; - private int sock; - boolean closed = false; - private byte[] onebuf = new byte[1]; - private UnixSocket us; - private boolean blocking = true; - private int flags = 0; - private int timeout = 0; - public USInputStream(int sock, UnixSocket us) - { - this.sock = sock; - this.us = us; - } - public void close() throws IOException - { - closed = true; - us.close(); - } - public boolean markSupported() { return false; } - public int read() throws IOException - { - int rv = 0; - while (0 >= rv) rv = read(onebuf); - if (-1 == rv) return -1; - return 0 > onebuf[0] ? -onebuf[0] : onebuf[0]; - } - public int read(byte[] b, int off, int len) throws IOException - { - if (closed) throw new NotConnectedException(); - int count = native_recv(sock, b, off, len, flags, timeout); - /* Yes, I really want to do this. Recv returns 0 for 'connection shut down'. - * read() returns -1 for 'end of stream. - * Recv returns -1 for 'EAGAIN' (all other errors cause an exception to be raised) - * whereas read() returns 0 for '0 bytes read', so yes, I really want to swap them here. - */ - if (0 == count) return -1; - else if (-1 == count) return 0; - else return count; - } - public boolean isClosed() { return closed; } - public UnixSocket getSocket() { return us; } - public void setBlocking(boolean enable) - { - flags = enable ? 0 : MSG_DONTWAIT; - } - public void setSoTimeout(int timeout) - { - this.timeout = timeout; - } -} diff --git a/app/src/main/java/cx/ath/matthew/unix/USOutputStream.java b/app/src/main/java/cx/ath/matthew/unix/USOutputStream.java deleted file mode 100644 index 7e06289e..00000000 --- a/app/src/main/java/cx/ath/matthew/unix/USOutputStream.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Java Unix Sockets Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ -package cx.ath.matthew.unix; - -import java.io.IOException; -import java.io.OutputStream; - -public class USOutputStream extends OutputStream -{ - private native int native_send(int sock, byte[] b, int off, int len) throws IOException; - private native int native_send(int sock, byte[][] b) throws IOException; - private int sock; - boolean closed = false; - private byte[] onebuf = new byte[1]; - private UnixSocket us; - public USOutputStream(int sock, UnixSocket us) - { - this.sock = sock; - this.us = us; - } - public void close() throws IOException - { - closed = true; - us.close(); - } - public void flush() {} // no-op, we do not buffer - public void write(byte[][] b) throws IOException - { - if (closed) throw new NotConnectedException(); - native_send(sock, b); - } - public void write(byte[] b, int off, int len) throws IOException - { - if (closed) throw new NotConnectedException(); - native_send(sock, b, off, len); - } - public void write(int b) throws IOException - { - onebuf[0] = (byte) (b % 0x7F); - if (1 == (b % 0x80)) onebuf[0] = (byte) -onebuf[0]; - write(onebuf); - } - public boolean isClosed() { return closed; } - public UnixSocket getSocket() { return us; } -} diff --git a/app/src/main/java/cx/ath/matthew/unix/UnixIOException.java b/app/src/main/java/cx/ath/matthew/unix/UnixIOException.java deleted file mode 100644 index 61cdc127..00000000 --- a/app/src/main/java/cx/ath/matthew/unix/UnixIOException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Java Unix Sockets Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ -package cx.ath.matthew.unix; - -import java.io.IOException; - -/** - * An IO Exception which occurred during UNIX Socket IO - */ -public class UnixIOException extends IOException -{ - private int no; - private String message; - public UnixIOException(int no, String message) - { - super(message); - this.message = message; - this.no = no; - } -} diff --git a/app/src/main/java/cx/ath/matthew/unix/UnixServerSocket.java b/app/src/main/java/cx/ath/matthew/unix/UnixServerSocket.java deleted file mode 100644 index 082978c0..00000000 --- a/app/src/main/java/cx/ath/matthew/unix/UnixServerSocket.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Java Unix Sockets Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ -package cx.ath.matthew.unix; - -import java.io.IOException; - -/** - * Represents a listening UNIX Socket. - */ -public class UnixServerSocket -{ - static { System.loadLibrary("unix-java"); } - private native int native_bind(String address, boolean abs) throws IOException; - private native void native_close(int sock) throws IOException; - private native int native_accept(int sock) throws IOException; - private UnixSocketAddress address = null; - private boolean bound = false; - private boolean closed = false; - private int sock; - /** - * Create an un-bound server socket. - */ - public UnixServerSocket() - { - } - /** - * Create a server socket bound to the given address. - * @param address Path to the socket. - */ - public UnixServerSocket(UnixSocketAddress address) throws IOException - { - bind(address); - } - /** - * Create a server socket bound to the given address. - * @param address Path to the socket. - */ - public UnixServerSocket(String address) throws IOException - { - this(new UnixSocketAddress(address)); - } - /** - * Accepts a connection on the ServerSocket. - * @return A UnixSocket connected to the accepted connection. - */ - public UnixSocket accept() throws IOException - { - int client_sock = native_accept(sock); - return new UnixSocket(client_sock, address); - } - /** - * Closes the ServerSocket. - */ - public synchronized void close() throws IOException - { - native_close(sock); - sock = 0; - closed = true; - bound = false; - } - /** - * Binds a server socket to the given address. - * @param address Path to the socket. - */ - public void bind(UnixSocketAddress address) throws IOException - { - if (bound) close(); - sock = native_bind(address.path, address.abs); - bound = true; - closed = false; - this.address = address; - } - /** - * Binds a server socket to the given address. - * @param address Path to the socket. - */ - public void bind(String address) throws IOException - { - bind(new UnixSocketAddress(address)); - } - /** - * Return the address this socket is bound to. - * @return The UnixSocketAddress if bound or null if unbound. - */ - public UnixSocketAddress getAddress() - { - return address; - } - /** - * Check the status of the socket. - * @return True if closed. - */ - public boolean isClosed() - { - return closed; - } - /** - * Check the status of the socket. - * @return True if bound. - */ - public boolean isBound() - { - return bound; - } -} diff --git a/app/src/main/java/cx/ath/matthew/unix/UnixSocket.java b/app/src/main/java/cx/ath/matthew/unix/UnixSocket.java deleted file mode 100644 index f6526143..00000000 --- a/app/src/main/java/cx/ath/matthew/unix/UnixSocket.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Java Unix Sockets Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ -package cx.ath.matthew.unix; - -import java.io.InputStream; -import java.io.IOException; -import java.io.OutputStream; - -import cx.ath.matthew.debug.Debug; - -/** - * Represents a UnixSocket. - */ -public class UnixSocket -{ - static { System.loadLibrary("unix-java"); } - private native void native_set_pass_cred(int sock, boolean passcred) throws IOException; - private native int native_connect(String address, boolean abs) throws IOException; - private native void native_close(int sock) throws IOException; - private native int native_getPID(int sock); - private native int native_getUID(int sock); - private native int native_getGID(int sock); - private native void native_send_creds(int sock, byte data) throws IOException; - private native byte native_recv_creds(int sock, int[] creds) throws IOException; - - private UnixSocketAddress address = null; - private USOutputStream os = null; - private USInputStream is = null; - private boolean closed = false; - private boolean connected = false; - private boolean passcred = false; - private int sock = 0; - private boolean blocking = true; - private int uid = -1; - private int pid = -1; - private int gid = -1; - UnixSocket(int sock, UnixSocketAddress address) - { - this.sock = sock; - this.address = address; - this.connected = true; - this.os = new USOutputStream(sock, this); - this.is = new USInputStream(sock, this); - } - /** - * Create an unconnected socket. - */ - public UnixSocket() - { - } - /** - * Create a socket connected to the given address. - * @param address The Unix Socket address to connect to - */ - public UnixSocket(UnixSocketAddress address) throws IOException - { - connect(address); - } - /** - * Create a socket connected to the given address. - * @param address The Unix Socket address to connect to - */ - public UnixSocket(String address) throws IOException - { - this(new UnixSocketAddress(address)); - } - /** - * Connect the socket to this address. - * @param address The Unix Socket address to connect to - */ - public void connect(UnixSocketAddress address) throws IOException - { - if (connected) close(); - this.sock = native_connect(address.path, address.abs); - this.os = new USOutputStream(this.sock, this); - this.is = new USInputStream(this.sock, this); - this.address = address; - this.connected = true; - this.closed = false; - this.is.setBlocking(blocking); - } - /** - * Connect the socket to this address. - * @param address The Unix Socket address to connect to - */ - public void connect(String address) throws IOException - { - connect(new UnixSocketAddress(address)); - } - public void finalize() - { - try { - close(); - } catch (IOException IOe) {} - } - /** - * Closes the connection. - */ - public synchronized void close() throws IOException - { - if (Debug.debug) Debug.print(Debug.INFO, "Closing socket"); - native_close(sock); - sock = 0; - this.closed = true; - this.connected = false; - os = null; - is = null; - } - /** - * Returns an InputStream for reading from the socket. - * @return An InputStream connected to this socket. - */ - public InputStream getInputStream() - { - return is; - } - /** - * Returns an OutputStream for writing to the socket. - * @return An OutputStream connected to this socket. - */ - public OutputStream getOutputStream() - { - return os; - } - /** - * Returns the address this socket is connected to. - * Returns null if the socket is unconnected. - * @return The UnixSocketAddress the socket is connected to - */ - public UnixSocketAddress getAddress() - { - return address; - } - /** - * Send a single byte of data with credentials. - * (Works on BSDs) - * @param data The byte of data to send. - */ - public void sendCredentialByte(byte data) throws IOException - { - if (!connected) throw new NotConnectedException(); - native_send_creds(sock, data); - } - /** - * Receive a single byte of data, with credentials. - * (Works on BSDs) - * @see getPeerUID - * @see getPeerPID - * @see getPeerGID - * @param data The byte of data to send. - */ - public byte recvCredentialByte() throws IOException - { - if (!connected) throw new NotConnectedException(); - int[] creds = new int[] { -1, -1, -1 }; - byte data = native_recv_creds(sock, creds); - pid = creds[0]; - uid = creds[1]; - gid = creds[2]; - return data; - } - /** - * Get the credential passing status. - * (only effective on linux) - * @return The current status of credential passing. - * @see setPassCred - */ - public boolean getPassCred() - { - return passcred; - } - /** - * Return the uid of the remote process. - * Some data must have been received on the socket to do this. - * Either setPassCred must be called on Linux first, or recvCredentialByte - * on BSD. - * @return the UID or -1 if it is not available - */ - public int getPeerUID() - { - if (-1 == uid) - uid = native_getUID(sock); - return uid; - } - /** - * Return the gid of the remote process. - * Some data must have been received on the socket to do this. - * Either setPassCred must be called on Linux first, or recvCredentialByte - * on BSD. - * @return the GID or -1 if it is not available - */ - public int getPeerGID() - { - if (-1 == gid) - gid = native_getGID(sock); - return gid; - } - /** - * Return the pid of the remote process. - * Some data must have been received on the socket to do this. - * Either setPassCred must be called on Linux first, or recvCredentialByte - * on BSD. - * @return the PID or -1 if it is not available - */ - public int getPeerPID() - { - if (-1 == pid) - pid = native_getPID(sock); - return pid; - } - /** - * Set the credential passing status. - * (Only does anything on linux, for other OS, you need - * to use send/recv credentials) - * @param enable Set to true for credentials to be passed. - */ - public void setPassCred(boolean enable) throws IOException - { - native_set_pass_cred(sock, enable); - passcred = enable; - } - /** - * Get the blocking mode. - * @return true if reads are blocking. - * @see setBlocking - */ - public boolean getBlocking() - { - return blocking; - } - /** - * Set the blocking mode. - * @param enable Set to false for non-blocking reads. - */ - public void setBlocking(boolean enable) - { - blocking = enable; - if (null != is) is.setBlocking(enable); - } - - /** - * Check the socket status. - * @return true if closed. - */ - public boolean isClosed() - { - return closed; - } - /** - * Check the socket status. - * @return true if connected. - */ - public boolean isConnected() - { - return connected; - } - /** - * Check the socket status. - * @return true if the input stream has been shutdown - */ - public boolean isInputShutdown() - { - return is.isClosed(); - } - /** - * Check the socket status. - * @return true if the output stream has been shutdown - */ - public boolean isOutputShutdown() - { - return os.isClosed(); - } - /** - * Shuts down the input stream. - * Subsequent reads on the associated InputStream will fail. - */ - public void shutdownInput() - { - is.closed = true; - } - /** - * Shuts down the output stream. - * Subsequent writes to the associated OutputStream will fail. - */ - public void shutdownOutput() - { - os.closed = true; - } - /** - * Set timeout of read requests. - */ - public void setSoTimeout(int timeout) - { - is.setSoTimeout(timeout); - } -} diff --git a/app/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java b/app/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java deleted file mode 100644 index 319cd604..00000000 --- a/app/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Java Unix Sockets Library - * - * Copyright (c) Matthew Johnson 2004 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ -package cx.ath.matthew.unix; - -/** - * Represents an address for a Unix Socket - */ -public class UnixSocketAddress -{ - String path; - boolean abs; - /** - * Create the address. - * @param path The path to the Unix Socket. - * @param abs True if this should be an abstract socket. - */ - public UnixSocketAddress(String path, boolean abs) - { - this.path = path; - this.abs = abs; - } - /** - * Create the address. - * @param path The path to the Unix Socket. - */ - public UnixSocketAddress(String path) - { - this.path = path; - this.abs = false; - } - /** - * Return the path. - */ - public String getPath() - { - return path; - } - /** - * Returns true if this an address for an abstract socket. - */ - public boolean isAbstract() - { - return abs; - } - /** - * Return the Address as a String. - */ - public String toString() - { - return "unix"+(abs?":abstract":"")+":path="+path; - } - public boolean equals(Object o) - { - if (!(o instanceof UnixSocketAddress)) return false; - return ((UnixSocketAddress) o).path.equals(this.path); - } - public int hashCode() - { - return path.hashCode(); - } -} diff --git a/app/src/main/java/cx/ath/matthew/unix/java-unix.h b/app/src/main/java/cx/ath/matthew/unix/java-unix.h deleted file mode 100644 index f0d1ebe3..00000000 --- a/app/src/main/java/cx/ath/matthew/unix/java-unix.h +++ /dev/null @@ -1,112 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class cx_ath_matthew_unix_UnixServerSocket */ - -#ifndef _Included_cx_ath_matthew_unix_UnixServerSocket -#define _Included_cx_ath_matthew_unix_UnixServerSocket -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: cx_ath_matthew_unix_UnixServerSocket - * Method: native_bind - * Signature: (Ljava/lang/String;Z)I - */ -JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_UnixServerSocket_native_1bind - (JNIEnv *, jobject, jstring, jboolean); - -/* - * Class: cx_ath_matthew_unix_UnixServerSocket - * Method: native_close - * Signature: (I)V - */ -JNIEXPORT void JNICALL Java_cx_ath_matthew_unix_UnixServerSocket_native_1close - (JNIEnv *, jobject, jint); - -/* - * Class: cx_ath_matthew_unix_UnixServerSocket - * Method: native_accept - * Signature: (I)I - */ -JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_UnixServerSocket_native_1accept - (JNIEnv *, jobject, jint); - -#ifdef __cplusplus -} -#endif -#endif -/* Header for class cx_ath_matthew_unix_UnixSocket */ - -#ifndef _Included_cx_ath_matthew_unix_UnixSocket -#define _Included_cx_ath_matthew_unix_UnixSocket -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: cx_ath_matthew_unix_UnixSocket - * Method: native_set_pass_cred - * Signature: (IZ)V - */ -JNIEXPORT void JNICALL Java_cx_ath_matthew_unix_UnixSocket_native_1set_1pass_1cred - (JNIEnv *, jobject, jint, jboolean); - -/* - * Class: cx_ath_matthew_unix_UnixSocket - * Method: native_connect - * Signature: (Ljava/lang/String;Z)I - */ -JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_UnixSocket_native_1connect - (JNIEnv *, jobject, jstring, jboolean); - -/* - * Class: cx_ath_matthew_unix_UnixSocket - * Method: native_close - * Signature: (I)V - */ -JNIEXPORT void JNICALL Java_cx_ath_matthew_unix_UnixSocket_native_1close - (JNIEnv *, jobject, jint); - -#ifdef __cplusplus -} -#endif -#endif -/* Header for class cx_ath_matthew_unix_USInputStream */ - -#ifndef _Included_cx_ath_matthew_unix_USInputStream -#define _Included_cx_ath_matthew_unix_USInputStream -#ifdef __cplusplus -extern "C" { -#endif -#undef cx_ath_matthew_unix_USInputStream_SKIP_BUFFER_SIZE -#define cx_ath_matthew_unix_USInputStream_SKIP_BUFFER_SIZE 2048L -/* - * Class: cx_ath_matthew_unix_USInputStream - * Method: native_recv - * Signature: (I[BII)I - */ -JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_USInputStream_native_1recv - (JNIEnv *, jobject, jint, jbyteArray, jint, jint); - -#ifdef __cplusplus -} -#endif -#endif -/* Header for class cx_ath_matthew_unix_USOutputStream */ - -#ifndef _Included_cx_ath_matthew_unix_USOutputStream -#define _Included_cx_ath_matthew_unix_USOutputStream -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: cx_ath_matthew_unix_USOutputStream - * Method: native_send - * Signature: (I[BII)I - */ -JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_USOutputStream_native_1send - (JNIEnv *, jobject, jint, jbyteArray, jint, jint); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/app/src/main/java/cx/ath/matthew/unix/testclient.java b/app/src/main/java/cx/ath/matthew/unix/testclient.java deleted file mode 100644 index f390b4a9..00000000 --- a/app/src/main/java/cx/ath/matthew/unix/testclient.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Java Unix Sockets Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.unix; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; - -public class testclient -{ - public static void main(String args[]) throws IOException - { - UnixSocket s = new UnixSocket(new UnixSocketAddress("testsock", true)); - OutputStream os = s.getOutputStream(); - PrintWriter o = new PrintWriter(os); - BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); - String l; - while (null != (l = r.readLine())) { - byte[] buf = (l+"\n").getBytes(); - os.write(buf, 0, buf.length); - } - s.close(); - } -} diff --git a/app/src/main/java/cx/ath/matthew/unix/testserver.java b/app/src/main/java/cx/ath/matthew/unix/testserver.java deleted file mode 100644 index 5e1d4167..00000000 --- a/app/src/main/java/cx/ath/matthew/unix/testserver.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Java Unix Sockets Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.unix; - -import java.io.BufferedReader; -import java.io.File; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.IOException; - -public class testserver -{ - public static void main(String args[]) throws IOException - { - UnixServerSocket ss = new UnixServerSocket(new UnixSocketAddress("testsock", true)); - UnixSocket s = ss.accept(); - BufferedReader r = new BufferedReader(new InputStreamReader(s.getInputStream())); - String l; - while (null != (l = r.readLine())) - System.out.println(l);/* - InputStream is = s.getInputStream(); - int r; - do { - r = is.read(); - System.out.print((char)r); - } while (-1 != r);*/ - s.close(); - ss.close(); - } -} diff --git a/app/src/main/java/cx/ath/matthew/utils/Hexdump.java b/app/src/main/java/cx/ath/matthew/utils/Hexdump.java deleted file mode 100644 index 02cf5c65..00000000 --- a/app/src/main/java/cx/ath/matthew/utils/Hexdump.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Java Hexdump Library - * - * Copyright (c) Matthew Johnson 2005 - * - * 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. - * - * To Contact the author, please email src@matthew.ath.cx - * - */ - -package cx.ath.matthew.utils; - -import java.io.PrintStream; - -public class Hexdump -{ - public static final char[] hexchars = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - public static String toHex(byte[] buf) - { - return toHex(buf, 0, buf.length); - } - public static String toHex(byte[] buf, int ofs, int len) - { - StringBuffer sb = new StringBuffer(); - int j = ofs+len; - for (int i = ofs; i < j; i++) { - if (i < buf.length) { - sb.append(hexchars[(buf[i] & 0xF0) >> 4]); - sb.append(hexchars[buf[i] & 0x0F]); - sb.append(' '); - } else { - sb.append(' '); - sb.append(' '); - sb.append(' '); - } - } - return sb.toString(); - } - - public static String toAscii(byte[] buf) - { - return toAscii(buf, 0, buf.length); - } - public static String toAscii(byte[] buf, int ofs, int len) - { - StringBuffer sb = new StringBuffer(); - int j = ofs+len; - for (int i = ofs; i < j ; i++) { - if (i < buf.length) { - if (20 <= buf[i] && 126 >= buf[i]) - sb.append((char) buf[i]); - else - sb.append('.'); - } else - sb.append(' '); - } - return sb.toString(); - } - public static String format(byte[] buf) - { - return format(buf, 80); - } - public static String format(byte[] buf, int width) - { - int bs = (width - 8) / 4; - int i = 0; - StringBuffer sb = new StringBuffer(); - do { - for (int j = 0; j < 6; j++) { - sb.append(hexchars[(i << (j*4) & 0xF00000) >> 20]); - } - sb.append('\t'); - sb.append(toHex(buf, i, bs)); - sb.append(' '); - sb.append(toAscii(buf, i, bs)); - sb.append('\n'); - i += bs; - } while (i < buf.length); - return sb.toString(); - } - public static void print(byte[] buf) - { - print(buf, System.err); - } - public static void print(byte[] buf, int width) - { - print(buf, width, System.err); - } - public static void print(byte[] buf, int width, PrintStream out) - { - out.print(format(buf, width)); - } - public static void print(byte[] buf, PrintStream out) - { - out.print(format(buf)); - } - /** - * Returns a string which can be written to a Java source file as part - * of a static initializer for a byte array. - * Returns data in the format 0xAB, 0xCD, .... - * use like: - * javafile.print("byte[] data = {") - * javafile.print(Hexdump.toByteArray(data)); - * javafile.println("};"); - */ - public static String toByteArray(byte[] buf) - { - return toByteArray(buf, 0, buf.length); - } - /** - * Returns a string which can be written to a Java source file as part - * of a static initializer for a byte array. - * Returns data in the format 0xAB, 0xCD, .... - * use like: - * javafile.print("byte[] data = {") - * javafile.print(Hexdump.toByteArray(data)); - * javafile.println("};"); - */ - public static String toByteArray(byte[] buf, int ofs, int len) - { - StringBuffer sb = new StringBuffer(); - for (int i = ofs; i < len && i < buf.length; i++) { - sb.append('0'); - sb.append('x'); - sb.append(hexchars[(buf[i] & 0xF0) >> 4]); - sb.append(hexchars[buf[i] & 0x0F]); - if ((i+1) < len && (i+1) < buf.length) - sb.append(','); - } - return sb.toString(); - } -} diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java b/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java deleted file mode 100644 index 5eda949e..00000000 --- a/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * AsteroidOSSync - * Copyright (c) 2023 AsteroidOS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.asteroidos.sync.connectivity; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.ContentObserver; -import android.media.AudioManager; -import android.media.MediaMetadata; -import android.media.session.MediaController; -import android.media.session.MediaSession; -import android.media.session.MediaSessionManager; -import android.media.session.PlaybackState; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import android.view.KeyEvent; - -import androidx.annotation.NonNull; - -import com.maxmpz.poweramp.player.PowerampAPI; -import com.maxmpz.poweramp.player.PowerampAPIHelper; - -import org.asteroidos.sync.asteroid.IAsteroidDevice; -import org.asteroidos.sync.services.NLService; -import org.asteroidos.sync.utils.AsteroidUUIDS; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.UUID; - -public class MediaService implements IConnectivityService, MediaSessionManager.OnActiveSessionsChangedListener { - - public static final String TAG = MediaService.class.toString(); - - private static final byte MEDIA_COMMAND_PREVIOUS = 0x0; - private static final byte MEDIA_COMMAND_NEXT = 0x1; - private static final byte MEDIA_COMMAND_PLAY = 0x2; - private static final byte MEDIA_COMMAND_PAUSE = 0x3; - private static final byte MEDIA_COMMAND_VOLUME = 0x4; - - public static final String PREFS_NAME = "MediaPreferences"; - public static final String PREFS_MEDIA_CONTROLLER_PACKAGE = "media_controller_package"; - public static final String PREFS_MEDIA_CONTROLLER_PACKAGE_DEFAULT = "default"; - - private final Context mCtx; - private final IAsteroidDevice mDevice; - private SharedPreferences mSettings; - - private MediaController mMediaController = null; - private MediaSessionManager mMediaSessionManager; - - private int mVolume; - - public MediaService(Context ctx, IAsteroidDevice device) { - mDevice = device; - mCtx = ctx; - device.registerCallback(AsteroidUUIDS.MEDIA_COMMANDS_CHAR, (data) -> { - if (data == null) return; - if (mMediaController != null) { - boolean isPoweramp = mSettings.getString(PREFS_MEDIA_CONTROLLER_PACKAGE, PREFS_MEDIA_CONTROLLER_PACKAGE_DEFAULT) - .equals(PowerampAPI.PACKAGE_NAME); - - switch (data[0]) { - case MEDIA_COMMAND_PREVIOUS: - if(isPoweramp) { - PowerampAPIHelper.startPAService(mCtx, new Intent(PowerampAPI.ACTION_API_COMMAND) - .putExtra(PowerampAPI.COMMAND, PowerampAPI.Commands.PREVIOUS)); - } else { - mMediaController.getTransportControls().skipToPrevious(); - } - break; - case MEDIA_COMMAND_NEXT: - if(isPoweramp) { - PowerampAPIHelper.startPAService(mCtx, new Intent(PowerampAPI.ACTION_API_COMMAND) - .putExtra(PowerampAPI.COMMAND, PowerampAPI.Commands.NEXT)); - } else { - mMediaController.getTransportControls().skipToNext(); - } - break; - case MEDIA_COMMAND_PLAY: - if(isPoweramp) { - PowerampAPIHelper.startPAService(mCtx, new Intent(PowerampAPI.ACTION_API_COMMAND) - .putExtra(PowerampAPI.COMMAND, PowerampAPI.Commands.RESUME)); - } else { - mMediaController.getTransportControls().play(); - } - break; - case MEDIA_COMMAND_PAUSE: - if (isPoweramp) { - PowerampAPIHelper.startPAService(mCtx, new Intent(PowerampAPI.ACTION_API_COMMAND) - .putExtra(PowerampAPI.COMMAND, PowerampAPI.Commands.PAUSE)); - } else { - mMediaController.getTransportControls().pause(); - } - break; - case MEDIA_COMMAND_VOLUME: - if (mMediaController.getPlaybackInfo() != null) { - if (data[1] != mVolume) { - int delta = Math.abs(mVolume - data[1]); - int deviceDelta = 100 / mMediaController.getPlaybackInfo().getMaxVolume(); - // Change in volume is smaller than the device volume step (i.e. volume won't change) - // Increase or decrease the volume by one step anyway to improve UX. - if (delta < deviceDelta) { - if (data[1] > mVolume) { - mMediaController.adjustVolume(AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI); - } else if (data[1] < mVolume) { - mMediaController.adjustVolume(AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI); - } - } else { - // Convert volume range (0-100) to Android device range(0-?). - int volume = (int) (mMediaController.getPlaybackInfo().getMaxVolume() * (data[1] / 100.0)); - mMediaController.setVolumeTo(volume, AudioManager.FLAG_SHOW_UI); - } - // Set theoretical volume. - mVolume = data[1]; - } - } - break; - } - - } else if (data[0] != MEDIA_COMMAND_VOLUME){ - Log.d(TAG, "No active media session, starting playback..."); - - try { - Runtime runtime = Runtime.getRuntime(); - runtime.exec("input keyevent " + KeyEvent.KEYCODE_MEDIA_PLAY); - } catch (IOException e) { - e.printStackTrace(); - } - } - }); - - mSettings = mCtx.getSharedPreferences(PREFS_NAME, 0); - } - - @Override - public void sync() { - if (mMediaSessionManager == null) { - mCtx.getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mVolumeChangeObserver); - try { - mMediaSessionManager = (MediaSessionManager) mCtx.getSystemService(Context.MEDIA_SESSION_SERVICE); - List controllers = mMediaSessionManager.getActiveSessions(new ComponentName(mCtx, NLService.class)); - Handler handler = new Handler(Looper.getMainLooper()); - handler.post(() -> { - onActiveSessionsChanged(controllers); - if (mMediaSessionManager != null) { - mMediaSessionManager.addOnActiveSessionsChangedListener(this, new ComponentName(mCtx, NLService.class)); - } else { - Log.e("MediaSessionManager", "missing MediaSessionManager"); - } - }); - } catch (SecurityException e) { - Log.w(TAG, "No Notification Access"); - } - } - } - - @Override - public final void unsync() { - if (mMediaSessionManager != null) { - mCtx.getContentResolver().unregisterContentObserver(mVolumeChangeObserver); - - mMediaSessionManager.removeOnActiveSessionsChangedListener(this); - mMediaSessionManager = null; - } - if (mMediaController != null) { - try { - mMediaController.unregisterCallback(mMediaCallback); - } catch (IllegalArgumentException ignored) { - } - mMediaController = null; - } - } - - private void sendVolume(int volume) { - // Set real volume. - mVolume = volume; - - byte[] data = new byte[1]; - data[0] = (byte) mVolume; - mDevice.send(AsteroidUUIDS.MEDIA_VOLUME_CHAR, data, MediaService.this); - } - - private final ContentObserver mVolumeChangeObserver = new ContentObserver(new Handler()) { - // The last value of volume send to the watch. - private int reportedVolume; - - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - if (mMediaController != null && mMediaController.getPlaybackInfo() != null) { - int vol = (100 * mMediaController.getPlaybackInfo().getCurrentVolume()) / mMediaController.getPlaybackInfo().getMaxVolume(); - - if (reportedVolume != vol) { - reportedVolume = vol; - sendVolume(reportedVolume); - } - } - } - }; - - /** - * Callback for the MediaController. - */ - private final MediaController.Callback mMediaCallback = new MediaController.Callback() { - - @Override - public void onAudioInfoChanged(MediaController.PlaybackInfo playbackInfo) { - super.onAudioInfoChanged(playbackInfo); - } - - /** - * Helper method to safely get a text value from a {@link MediaMetadata} as a byte array - * (UTF-8 encoded). - * - *

If the field is null, a zero length byte array will be returned.

- * - * @param metadata the MediaMetadata (assumed to be non-null) - * @param fieldName the field name - * @return the field value as a byte array - */ - private byte[] getTextAsBytes(MediaMetadata metadata, String fieldName) { - byte[] result; - - CharSequence text = metadata.getText(fieldName); - - if (text != null) { - result = text.toString().getBytes(StandardCharsets.UTF_8); - } else { - result = new byte[]{0}; - } - - return result; - } - - @Override - public void onMetadataChanged(MediaMetadata metadata) { - super.onMetadataChanged(metadata); - - if (metadata != null) { - mDevice.send(AsteroidUUIDS.MEDIA_ARTIST_CHAR, - getTextAsBytes(metadata, MediaMetadata.METADATA_KEY_ARTIST), - MediaService.this); - - mDevice.send(AsteroidUUIDS.MEDIA_ALBUM_CHAR, - getTextAsBytes(metadata, MediaMetadata.METADATA_KEY_ALBUM), - MediaService.this); - - mDevice.send(AsteroidUUIDS.MEDIA_TITLE_CHAR, - getTextAsBytes(metadata, MediaMetadata.METADATA_KEY_TITLE), - MediaService.this); - - mVolume = (100 * mMediaController.getPlaybackInfo().getCurrentVolume()) / mMediaController.getPlaybackInfo().getMaxVolume(); - sendVolume(mVolume); - } - } - - @Override - public void onPlaybackStateChanged(@NonNull PlaybackState state) { - super.onPlaybackStateChanged(state); - byte[] data = new byte[1]; - data[0] = (byte)(state.getState() == PlaybackState.STATE_PLAYING ? 1 : 0); - mDevice.send(AsteroidUUIDS.MEDIA_PLAYING_CHAR, data, MediaService.this); - } - - @Override - public void onQueueChanged(List queue) { - super.onQueueChanged(queue); - } - - @Override - public void onQueueTitleChanged(CharSequence title) { - super.onQueueTitleChanged(title); - } - }; - @Override - public void onActiveSessionsChanged(List controllers) { - if (controllers.size() > 0) { - if (mMediaController != null && !controllers.get(0).getSessionToken().equals(mMediaController.getSessionToken())) { - // Detach current controller - mMediaController.unregisterCallback(mMediaCallback); - Log.d(TAG, "MediaController removed"); - mMediaController = null; - } - - if(mMediaController == null) { - // Attach new controller - mMediaController = controllers.get(0); - mMediaController.registerCallback(mMediaCallback); - mMediaCallback.onMetadataChanged(mMediaController.getMetadata()); - if (mMediaController.getPlaybackState() != null) - mMediaCallback.onPlaybackStateChanged(mMediaController.getPlaybackState()); - Log.d(TAG, "MediaController set: " + mMediaController.getPackageName()); - SharedPreferences.Editor editor = mSettings.edit(); - editor.putString(PREFS_MEDIA_CONTROLLER_PACKAGE, mMediaController.getPackageName()); - editor.apply(); - } - } else { - byte[] data = new byte[]{0}; - mDevice.send(AsteroidUUIDS.MEDIA_ARTIST_CHAR, data, MediaService.this); - mDevice.send(AsteroidUUIDS.MEDIA_ALBUM_CHAR, data, MediaService.this); - mDevice.send(AsteroidUUIDS.MEDIA_TITLE_CHAR, data, MediaService.this); - } - } - - @Override - public HashMap getCharacteristicUUIDs() { - HashMap chars = new HashMap<>(); - chars.put(AsteroidUUIDS.MEDIA_TITLE_CHAR, Direction.TO_WATCH); - chars.put(AsteroidUUIDS.MEDIA_ALBUM_CHAR, Direction.TO_WATCH); - chars.put(AsteroidUUIDS.MEDIA_ARTIST_CHAR, Direction.TO_WATCH); - chars.put(AsteroidUUIDS.MEDIA_PLAYING_CHAR, Direction.TO_WATCH); - chars.put(AsteroidUUIDS.MEDIA_COMMANDS_CHAR, Direction.FROM_WATCH); - chars.put(AsteroidUUIDS.MEDIA_VOLUME_CHAR, Direction.TO_WATCH); - return chars; - } - - @Override - public final UUID getServiceUUID() { - return AsteroidUUIDS.MEDIA_SERVICE_UUID; - } -} diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java b/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java deleted file mode 100644 index 7d716424..00000000 --- a/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * AsteroidOSSync - * Copyright (c) 2024 AsteroidOS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.asteroidos.sync.connectivity; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; - -import org.asteroidos.sync.NotificationPreferences; -import org.asteroidos.sync.asteroid.IAsteroidDevice; -import org.asteroidos.sync.dataobjects.Notification; -import org.asteroidos.sync.services.INotificationHandler; -import org.asteroidos.sync.utils.AsteroidUUIDS; - -import java.util.HashMap; -import java.util.Objects; -import java.util.UUID; - -public class NotificationService implements IConnectivityService, INotificationHandler { - - public static final String TAG = NotificationService.class.toString(); - private final Context mCtx; - private final IAsteroidDevice mDevice; - private NotificationReceiver mNReceiver; - - public NotificationService(Context ctx, IAsteroidDevice device) { - this.mDevice = device; - this.mCtx = ctx; - } - - @Override - public void sync() { - if (mNReceiver == null) { - IntentFilter filter = new IntentFilter(); - filter.addAction("org.asteroidos.sync.NOTIFICATION_LISTENER"); - mNReceiver = new NotificationReceiver(); - mCtx.registerReceiver(mNReceiver, filter); - - Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE"); - i.putExtra("command", "refresh"); - mCtx.sendBroadcast(i); - } - } - - @Override - public void unsync() { - if (mNReceiver != null) { - try { - mCtx.unregisterReceiver(mNReceiver); - } catch (IllegalArgumentException ignored) { - } - mNReceiver = null; - } - } - - @Override - public final HashMap getCharacteristicUUIDs() { - HashMap chars = new HashMap<>(); - chars.put(AsteroidUUIDS.NOTIFICATION_UPDATE_CHAR, Direction.TO_WATCH); - chars.put(AsteroidUUIDS.NOTIFICATION_FEEDBACK_CHAR, Direction.FROM_WATCH); - return chars; - } - - @Override - public final UUID getServiceUUID() { - return AsteroidUUIDS.NOTIFICATION_SERVICE_UUID; - } - - @Override - public void postNotification(Context context, Intent intent) { - String event = intent.getStringExtra("event"); - if (Objects.equals(event, "posted")) { - String packageName = intent.getStringExtra("packageName"); - NotificationPreferences.putPackageToSeen(context, packageName); - NotificationPreferences.NotificationOption notificationOption = - NotificationPreferences.getNotificationPreferenceForApp(context, packageName); - if (notificationOption == NotificationPreferences.NotificationOption.NO_NOTIFICATIONS) - return; - - int id = intent.getIntExtra("id", 0); - String appName = intent.getStringExtra("appName"); - String appIcon = intent.getStringExtra("appIcon"); - String summary = intent.getStringExtra("summary"); - String body = intent.getStringExtra("body"); - String vibration; - if (notificationOption == NotificationPreferences.NotificationOption.SILENT_NOTIFICATION) - vibration = "none"; - else if (notificationOption == null - || notificationOption == NotificationPreferences.NotificationOption.NORMAL_VIBRATION - || notificationOption == NotificationPreferences.NotificationOption.DEFAULT) - vibration = "normal"; - else if (notificationOption == NotificationPreferences.NotificationOption.STRONG_VIBRATION) - vibration = "strong"; - else if(notificationOption == NotificationPreferences.NotificationOption.RINGTONE_VIBRATION) - vibration = "ringtone"; - else - throw new IllegalArgumentException("Not all options handled"); - - if(intent.hasExtra("vibration")) - vibration = intent.getStringExtra("vibration"); - - Notification notification = new Notification( - Notification.MsgType.POSTED, - packageName, - id, - appName, - appIcon, - summary, - body, - vibration); - - mDevice.send(AsteroidUUIDS.NOTIFICATION_UPDATE_CHAR, notification.toBytes(), NotificationService.this); - } else if (Objects.equals(event, "removed")) { - int id = intent.getIntExtra("id", 0); - - mDevice.send(AsteroidUUIDS.NOTIFICATION_UPDATE_CHAR, new Notification(Notification.MsgType.REMOVED, id).toBytes(), NotificationService.this); - } - } - - class NotificationReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - postNotification(context, intent); - } - } -} diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java index 6a3487e7..5b168a4e 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java @@ -21,25 +21,36 @@ import android.content.Context; import android.os.Handler; import android.os.HandlerThread; +import android.os.Message; import android.system.Os; import android.system.OsConstants; import android.system.StructPollfd; import android.util.Log; +import androidx.annotation.NonNull; + import org.asteroidos.sync.asteroid.IAsteroidDevice; import org.asteroidos.sync.dbus.IDBusConnectionCallback; import org.asteroidos.sync.dbus.IDBusConnectionProvider; import org.asteroidos.sync.utils.AsteroidUUIDS; -import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.connections.SASL; +import org.freedesktop.dbus.connections.impl.AndroidDBusConnectionBuilder; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder; import org.freedesktop.dbus.exceptions.DBusException; import java.io.FileDescriptor; +import java.io.IOException; +import java.lang.reflect.Field; import java.nio.ByteBuffer; -import java.text.ParseException; import java.util.HashMap; import java.util.UUID; import java.util.function.Consumer; +import kotlin.ParameterName; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; + public class SlirpService implements IConnectivityService, IDBusConnectionProvider { private final IAsteroidDevice mDevice; @@ -58,29 +69,9 @@ public class SlirpService implements IConnectivityService, IDBusConnectionProvid private final ByteBuffer tx = ByteBuffer.allocateDirect(1500); - private final Consumer dBusConnectionRunnable = dBusConnectionCallback -> { - DBusConnection connection = null; - try { - synchronized (DBusConnection.class) { - connection = DBusConnection.getConnection("tcp:host=127.0.0.1,bind=*,port=55556,family=ipv4"); - try { - Log.i("SlirpService", "D-Bus connection acquired: " + connection.getAddress().toString()); - } catch (ParseException e) { - Log.i("SlirpService", "D-Bus connection acquired"); - } - } - } catch (Throwable e) { - Log.e("SlirpService", "Failed to connect to D-Bus", e); - } - if (connection != null) { - try { - dBusConnectionCallback.handleConnection(connection); - } catch (Throwable e) { - Log.e("SlirpService", "An error occurred in a D-Bus callback", e); - } - } - }; + private final Consumer dBusConnectionRunnable; + private DBusConnection dBusConnection = null; public SlirpService(Context ctx, IAsteroidDevice device) { mDevice = device; @@ -88,7 +79,46 @@ public SlirpService(Context ctx, IAsteroidDevice device) { dBusHandlerThread = new HandlerThread("D-Bus Connection"); dBusHandlerThread.start(); - dBusHandler = new Handler(dBusHandlerThread.getLooper()); + dBusHandler = new Handler(dBusHandlerThread.getLooper()) { + @Override + public void handleMessage(@NonNull Message msg) { + switch (msg.arg1) { + case 0 -> { + if (dBusConnection == null) + break; + + dBusConnection.disconnect(); + dBusConnection = null; + } + case 1 -> { + if (dBusConnection != null) + break; + + try { + dBusConnection = AndroidDBusConnectionBuilder + .forAddress((String) msg.obj).build(); + } catch (DBusException e) { + Log.e("SlirpService", "Failed to establish a D-Bus connection", e); + } + } + } + } + }; + + dBusConnectionRunnable = dBusConnectionCallback -> { + final Message message = new Message(); + message.arg1 = 1; + message.obj = "tcp:host=127.0.0.1,bind=*,port=55556,family=ipv4"; + dBusHandler.sendMessage(message); + + try { + dBusConnectionCallback.handleConnection(dBusConnection); + } catch (DBusException e) { + Log.e("SlirpService", "D-Bus error", e); + } catch (Throwable e) { + Log.w("SlirpService", "Runtime error in D-Bus callback", e); + } + }; slirpThread = new Thread(() -> { FileDescriptor fd = getVdeFd(); @@ -107,7 +137,7 @@ public SlirpService(Context ctx, IAsteroidDevice device) { long read = vdeRecv(rx, 0, mtu - 3); assert read <= (mtu - 3); if (read > 0) { - Log.d("SlirpService", "Received " + read + " bytes"); +// Log.d("SlirpService", "Received " + read + " bytes"); byte[] data = new byte[(int) read]; rx.get(data); mDevice.send(AsteroidUUIDS.SLIRP_OUTGOING_CHAR, data, SlirpService.this); @@ -132,6 +162,7 @@ public SlirpService(Context ctx, IAsteroidDevice device) { tx.clear(); tx.put(data); vdeSend(tx, 0, data.length); +// Log.d("SlirpService", "Sent " + data.length + " bytes"); } }); @@ -189,15 +220,6 @@ public UUID getServiceUUID() { return AsteroidUUIDS.SLIRP_SERVICE_UUID; } - public void acquireDBusConnection(IDBusConnectionCallback dBusConnectionCallback) { - dBusHandler.post(() -> dBusConnectionRunnable.accept(dBusConnectionCallback)); - } - - @Override - public void acquireDBusConnectionLater(IDBusConnectionCallback dBusConnectionCallback, long delay) { - dBusHandler.postDelayed(() -> dBusConnectionRunnable.accept(dBusConnectionCallback), delay); - } - @SuppressWarnings({"unused", "FieldMayBeFinal"}) // used internally by the JNI part private long mySlirp = 0; @@ -214,4 +236,14 @@ public void acquireDBusConnectionLater(IDBusConnectionCallback dBusConnectionCal private native long vdeSend(ByteBuffer buffer, long offset, long count); private native FileDescriptor getVdeFd(); + + @Override + public void acquireDBusConnection(@NonNull Function1 dBusConnectionConsumer) { + dBusHandler.post(() -> dBusConnectionRunnable.accept(dBusConnectionConsumer::invoke)); + } + + @Override + public void acquireDBusConnectionLater(@NonNull Function1 dBusConnectionConsumer, long delay) { + dBusHandler.postDelayed(() -> dBusConnectionRunnable.accept(dBusConnectionConsumer::invoke), delay); + } } diff --git a/app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationService.java b/app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationService.java deleted file mode 100644 index 5b263441..00000000 --- a/app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationService.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * AsteroidOSSync - * Copyright (c) 2024 AsteroidOS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.asteroidos.sync.dbus; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.util.Log; - -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; - -import org.asteroidos.sync.NotificationPreferences; -import org.freedesktop.Notifications; -import org.asteroidos.sync.services.INotificationHandler; -import org.freedesktop.dbus.DBusSigHandler; -import org.freedesktop.dbus.UInt32; -import org.freedesktop.dbus.Variant; - -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -public class DBusNotificationService implements INotificationHandler, DBusSigHandler { - private final IDBusConnectionProvider connectionProvider; - - private static final HashFunction murmur32 = Hashing.murmur3_32_fixed(0); - - private final BiMap mapping = HashBiMap.create(); - - private Context mCtx; - - private NotificationReceiver mNReceiver; - - public DBusNotificationService(Context ctx, IDBusConnectionProvider connectionProvider) { - this.mCtx = ctx; - this.connectionProvider = connectionProvider; - - connectionProvider.acquireDBusConnectionLater(notify -> Log.w("DBusNotificationService", Arrays.toString(notify.getNames())), 1000); - } - - @Override - public void postNotification(Context context, Intent intent) { - String event = intent.getStringExtra("event"); - - if (Objects.equals(event, "posted")) { - String packageName = intent.getStringExtra("packageName"); - NotificationPreferences.putPackageToSeen(context, packageName); - NotificationPreferences.NotificationOption notificationOption = - NotificationPreferences.getNotificationPreferenceForApp(context, packageName); - if (notificationOption == NotificationPreferences.NotificationOption.NO_NOTIFICATIONS) - return; - - String key = Objects.requireNonNull(intent.getStringExtra("key")); - String appName = intent.getStringExtra("appName"); - String appIcon = intent.getStringExtra("appIcon"); - String summary = intent.getStringExtra("summary"); - String body = intent.getStringExtra("body"); - String vibration; - if (notificationOption == NotificationPreferences.NotificationOption.SILENT_NOTIFICATION) - vibration = "notif_silent"; - else if (notificationOption == null - || notificationOption == NotificationPreferences.NotificationOption.NORMAL_VIBRATION - || notificationOption == NotificationPreferences.NotificationOption.DEFAULT) - vibration = "notif_normal"; - else if (notificationOption == NotificationPreferences.NotificationOption.STRONG_VIBRATION) - vibration = "notif_strong"; - else if (notificationOption == NotificationPreferences.NotificationOption.RINGTONE_VIBRATION) - vibration = "ringtone"; - else - throw new IllegalArgumentException("Not all options handled"); - - connectionProvider.acquireDBusConnectionLater(notify -> { - synchronized (mapping) { - mapping.put(key, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications.class) - .Notify(appName, mapping.getOrDefault(key, new UInt32(0)), appIcon, summary, body, Collections.emptyList(), - Map.of( - "x-nemo-feedback", new Variant<>(vibration), - "x-nemo-preview-body", new Variant<>(body), - "x-nemo-preview-summary", new Variant<>(summary), - "urgency", new Variant<>((byte) 3)), 0)); - } - }, 500); - } else if (Objects.equals(event, "removed")) { - String key = Objects.requireNonNull(intent.getStringExtra("key")); - // Avoid an infinite loop when the user dismisses the notification on the watch - if (mapping.containsKey(key)) { - UInt32 id; - synchronized (mapping) { - id = mapping.get(key); - mapping.remove(key); - } - connectionProvider.acquireDBusConnectionLater(notify -> notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications.class) - .CloseNotification(id), 500); - } - } - } - - @Override - public void handle(Notifications.NotificationClosed s) { - synchronized (mapping) { - if (s.reason == Notifications.NotificationClosed.REASON_DISMISSED_BY_USER - && mapping.containsValue(s.id)) { - Intent dismiss = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE"); - dismiss.putExtra("command", "dismiss"); - dismiss.putExtra("key", mapping.inverse().get(s.id)); - - mapping.inverse().remove(s.id); - mCtx.sendBroadcast(dismiss); - } - } - } - - @Override - public void sync() { - if (mNReceiver == null) { - IntentFilter filter = new IntentFilter(); - filter.addAction("org.asteroidos.sync.NOTIFICATION_LISTENER"); - mNReceiver = new NotificationReceiver(); - mCtx.registerReceiver(mNReceiver, filter); - - Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE"); - i.putExtra("command", "refresh"); - mCtx.sendBroadcast(i); - } - connectionProvider.acquireDBusConnection(notify -> notify.addSigHandler(Notifications.NotificationClosed.class, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications.class), DBusNotificationService.this)); - } - - @Override - public void unsync() { - connectionProvider.acquireDBusConnection(notify -> notify.removeSigHandler(Notifications.NotificationClosed.class, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications.class), DBusNotificationService.this)); - if (mNReceiver != null) { - try { - mCtx.unregisterReceiver(mNReceiver); - } catch (IllegalArgumentException ignored) { - } - mNReceiver = null; - } - } - - class NotificationReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - postNotification(context, intent); - } - } -} diff --git a/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.java b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.kt similarity index 72% rename from app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.java rename to app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.kt index 88e144a7..a918d837 100644 --- a/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.java +++ b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.kt @@ -15,11 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +package org.asteroidos.sync.dbus -package org.asteroidos.sync.dbus; +import org.freedesktop.dbus.connections.impl.DBusConnection +import org.freedesktop.dbus.exceptions.DBusException -public interface IDBusConnectionProvider { - public void acquireDBusConnection(IDBusConnectionCallback dBusConnectionConsumer); - - public void acquireDBusConnectionLater(IDBusConnectionCallback dBusConnectionConsumer, long delay); -} +interface IDBusConnectionCallback { + @Throws(DBusException::class) + fun handleConnection(connection: DBusConnection) +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.kt b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.kt new file mode 100644 index 00000000..b2d08459 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.kt @@ -0,0 +1,25 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.asteroidos.sync.dbus + +import org.freedesktop.dbus.connections.impl.DBusConnection + +interface IDBusConnectionProvider { + fun acquireDBusConnection(dBusConnectionConsumer: (connection: DBusConnection) -> Unit) + fun acquireDBusConnectionLater(dBusConnectionConsumer: (connection: DBusConnection) -> Unit, delay: Long) +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt new file mode 100644 index 00000000..4d82362c --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt @@ -0,0 +1,397 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.asteroidos.sync.dbus + +import android.content.Context +import android.media.AudioManager +import android.os.Build +import android.os.Handler +import android.os.HandlerThread +import android.os.Message +import android.util.Log +import androidx.media3.common.MediaItem +import androidx.media3.common.Player.* +import com.google.common.collect.Lists +import com.google.common.hash.Hashing +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.android.asCoroutineDispatcher +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.asteroidos.sync.media.IMediaService +import org.asteroidos.sync.media.MediaSupervisor +import org.freedesktop.dbus.DBusPath +import org.freedesktop.dbus.connections.impl.DBusConnection +import org.freedesktop.dbus.interfaces.Properties.PropertiesChanged +import org.freedesktop.dbus.types.Variant +import org.mpris.MediaPlayer2 +import org.mpris.mediaplayer2.Player +import java.nio.charset.Charset +import java.util.Collections + +class MediaService(private val mCtx: Context, private val supervisor: MediaSupervisor, private val connectionProvider: IDBusConnectionProvider) : IMediaService, MediaPlayer2, Player { + private val mNReceiver: NotificationService.NotificationReceiver? = null + private val hashing = Hashing.goodFastHash(64) + + override fun sync() { + connectionProvider.acquireDBusConnection { connection: DBusConnection -> + connection.requestBusName("org.mpris.MediaPlayer2.AsteroidOSSync") + connection.exportObject("/org/mpris/MediaPlayer2", this@MediaService) + } + } + + override fun unsync() { + connectionProvider.acquireDBusConnection { connection: DBusConnection -> + connection.unExportObject("/org/mpris/MediaPlayer2") + connection.releaseBusName("org.mpris.MediaPlayer2.AsteroidOSSync") + } + } + + override fun onReset() { + connectionProvider.acquireDBusConnection { connection: DBusConnection -> + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Metadata", Variant(metadata)) as Map>?, Collections.emptyList())) + } + } + + override fun canQuit(): Boolean { + return false + } + + override fun isFullscreen(): Boolean { + return false + } + + override fun setFullscreen(_property: Boolean) { + } + + override fun canSetFullscreen(): Boolean { + return false + } + + override fun canRaise(): Boolean { + return false + } + + override fun hasTrackList(): Boolean { + // TODO: Track List!!! :grin: + return false + } + + override fun getIdentity(): String { + return "Android" + } + + override fun getSupportedUriSchemes(): List { + return emptyList() + } + + override fun getSupportedMimeTypes(): List { + return emptyList() + } + + override fun Raise() {} + override fun Quit() {} + override fun getPlaybackStatus(): String { + var status = "Stopped" + val controller = supervisor.mediaController + if (controller != null) { + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isPlaying) status = "Playing" else if (controller.playbackState == STATE_READY && !controller.playWhenReady) status = "Paused" + } + } + Log.i("MediaService", "Status: $status") + return status + } + + override fun getLoopStatus(): String { + val controller = supervisor.mediaController + return if (controller != null) { + Handler(controller.applicationLooper).run { + when (controller.repeatMode) { + REPEAT_MODE_ALL -> "Playlist" + REPEAT_MODE_ONE -> "Track" + REPEAT_MODE_OFF -> "None" + else -> throw IllegalStateException("Unexpected value: " + controller.repeatMode) + } + } + } else "None" + } + + override fun setLoopStatus(_property: String) { + val controller = supervisor.mediaController + if (controller != null + && controller.isCommandAvailable(COMMAND_SET_REPEAT_MODE)) { + Handler(controller.applicationLooper).run { + when (_property) { + "None" -> controller.repeatMode = REPEAT_MODE_OFF + "Track" -> controller.repeatMode = REPEAT_MODE_ONE + "Playlist" -> controller.repeatMode = REPEAT_MODE_ALL + } + } + } + } + + override fun getRate(): Double { + val controller = supervisor.mediaController + return controller?.playbackParameters?.speed?.toDouble() ?: 1.0 + } + + override fun setRate(_property: Double) { + val controller = supervisor.mediaController + if (controller != null + && controller.isCommandAvailable(COMMAND_SET_SPEED_AND_PITCH)) { + controller.setPlaybackSpeed(_property.toFloat()) + } + } + + override fun isShuffle(): Boolean { + val controller = supervisor.mediaController + return controller?.shuffleModeEnabled ?: false + } + + override fun setShuffle(_property: Boolean) { + val controller = supervisor.mediaController + if (controller != null + && controller.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)) { + controller.shuffleModeEnabled = _property + } + } + + private fun mediaToPath(packageName: String, item: MediaItem?): DBusPath { + val mediaId = Hashing.combineOrdered(Lists.newArrayList( + hashing.hashString(item?.mediaMetadata?.title ?: "", Charset.defaultCharset()), + hashing.hashString(item?.mediaId ?: "", Charset.defaultCharset()))) + return DBusPath("/" + packageName.replace('.', '/') + "/" + mediaId) + } + + override fun getMetadata(): Map> { + val controller = supervisor.mediaController + if (controller == null || controller.currentMediaItem == null) return Collections.singletonMap>("mpris:trackid", Variant(DBusPath("/org/mpris/MediaPlayer2/TrackList/NoTrack"))) + var result: Map> + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + result = java.util.Map.of( + "mpris:trackid", Variant(currentMediaIdObjectPath), + "mpris:length", Variant(controller.contentDuration * 1000) + ) + } + return result + } + + private val currentMediaIdObjectPath get() = mediaToPath(supervisor.mediaController?.connectedToken?.packageName ?: "", supervisor.mediaController?.currentMediaItem) + + override fun getVolume(): Double { + // TODO:XXX: + var result: Double + val controller = supervisor.mediaController ?: return 0.0 + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + result = controller.volume.toDouble() + } + return result + } + + override fun setVolume(_property: Double) { + val controller = supervisor.mediaController + if (controller != null) { + if (controller.isCommandAvailable(COMMAND_SET_VOLUME)) { + controller.volume = _property.toFloat() + } + } + } + + override fun getPosition(): Long { + val controller = supervisor.mediaController + return if (controller != null && controller.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)) { + controller.currentPosition * 1000L + } else 0L + } + + override fun getMinimumRate(): Double { + return .25 + } + + override fun getMaximumRate(): Double { + return 2.0 + } + + override fun canGoNext(): Boolean { + val controller = supervisor.mediaController ?: return false + var result: Boolean + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + result = controller.isCommandAvailable(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) && controller.hasNextMediaItem() + } + return result + } + + override fun canGoPrevious(): Boolean { + val controller = supervisor.mediaController ?: return false + var result: Boolean + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + result = controller.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) && controller.hasPreviousMediaItem() + } + return result + } + + override fun canPlay(): Boolean { + val controller = supervisor.mediaController ?: return false + var result: Boolean + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + result = controller.isCommandAvailable(COMMAND_PLAY_PAUSE) + } + return result + } + + override fun canPause(): Boolean { + val controller = supervisor.mediaController ?: return false + var result: Boolean + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + result = controller.isCommandAvailable(COMMAND_PLAY_PAUSE) + } + return result + } + + override fun canSeek(): Boolean { + val controller = supervisor.mediaController ?: return false + var result: Boolean + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + result = controller.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) + && controller.isCurrentMediaItemSeekable + } + return result + } + + override fun canControl(): Boolean = true + + override fun Next() { + val controller = supervisor.mediaController + if (controller != null && controller.isCommandAvailable(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)) { + controller.seekToNextMediaItem() + } + } + + override fun Previous() { + val controller = supervisor.mediaController + if (controller != null && controller.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)) { + controller.seekToPreviousMediaItem() + } + } + + override fun Pause() { + val controller = supervisor.mediaController + if (controller != null && controller.isCommandAvailable(COMMAND_PLAY_PAUSE)) { + controller.pause() + } + } + + override fun PlayPause() { + val controller = supervisor.mediaController + if (controller != null && controller.isCommandAvailable(COMMAND_PLAY_PAUSE)) { + if (controller.isPlaying) { + controller.pause() + } else { + controller.play() + } + } + Log.i("MediaService", "PlayPause: ${controller != null}, ${controller?.isCommandAvailable(COMMAND_PLAY_PAUSE) ?: false}") + } + + override fun Stop() { + val controller = supervisor.mediaController + if (controller != null && controller.isCommandAvailable(COMMAND_STOP)) { + controller.stop() + } + } + + override fun Play() { + val controller = supervisor.mediaController + if (controller != null && controller.isCommandAvailable(COMMAND_PLAY_PAUSE)) { + controller.play() + } + Log.i("MediaService", "Play: ${controller != null}, ${controller?.isCommandAvailable(COMMAND_PLAY_PAUSE) ?: false}") + } + + override fun Seek(Offset: Long) { + val controller = supervisor.mediaController + if (controller != null && controller.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) + && controller.isCurrentMediaItemSeekable) { + controller.seekTo(controller.currentPosition + Offset / 1000L) + } + } + + override fun SetPosition(TrackId: DBusPath, Position: Long) { + val controller = supervisor.mediaController + if (controller != null && controller.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) + && controller.isCurrentMediaItemSeekable) { + controller.seekTo(Position / 1000L) + } + } + + override fun OpenUri(Uri: String) {} + + override fun getObjectPath() = "/org/mpris/MediaPlayer2" + + override fun isRemote(): Boolean = false + + override fun onPositionDiscontinuity(oldPosition: PositionInfo, newPosition: PositionInfo, reason: Int) { + connectionProvider.acquireDBusConnection { connection -> + connection.sendMessage(Player.Seeked(objectPath, newPosition.positionMs / 1000L)) + } + } + + override fun onIsPlayingChanged(isPlaying: Boolean) { + connectionProvider.acquireDBusConnection { connection -> + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(playbackStatus)) as Map>?, Collections.emptyList())) + } + } + + override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { + connectionProvider.acquireDBusConnection { connection -> + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(playbackStatus)) as Map>?, Collections.emptyList())) + } + } + + override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { + connectionProvider.acquireDBusConnection { connection -> + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Metadata", Variant(metadata)) as Map>?, Collections.emptyList())) + } + } + + override fun onPlaybackStateChanged(playbackState: Int) { + connectionProvider.acquireDBusConnection { connection -> + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(playbackStatus)) as Map>?, Collections.emptyList())) + } + } + + override fun onVolumeChanged(volume: Float) { + connectionProvider.acquireDBusConnection { connection -> + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Volume", Variant(playbackStatus)) as Map>?, Collections.emptyList())) + } + } + + override fun onAvailableCommandsChanged(availableCommands: Commands) { + connectionProvider.acquireDBusConnection { connection -> + val map: Map> = java.util.Map.of( + "CanGoNext", Variant(canGoNext()), + "CanGoPrevious", Variant(canGoPrevious()), + "CanPlay", Variant(canPlay()), + "CanPause", Variant(canPause()), + "CanSeek", Variant(canSeek()), + "CanControl", Variant(canControl()), + ) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", map, Collections.emptyList())) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt b/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt new file mode 100644 index 00000000..39f031d4 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt @@ -0,0 +1,138 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.asteroidos.sync.dbus + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.util.Log +import com.google.common.collect.BiMap +import com.google.common.collect.HashBiMap +import com.google.common.hash.Hashing +import org.asteroidos.sync.NotificationPreferences +import org.asteroidos.sync.NotificationPreferences.NotificationOption +import org.asteroidos.sync.services.INotificationHandler +import org.freedesktop.Notifications +import org.freedesktop.Notifications.NotificationClosed +import org.freedesktop.dbus.connections.impl.DBusConnection +import org.freedesktop.dbus.interfaces.DBusSigHandler +import org.freedesktop.dbus.types.UInt32 +import org.freedesktop.dbus.types.Variant +import java.util.Arrays +import java.util.Map +import java.util.Objects + +class NotificationService(private val mCtx: Context, private val connectionProvider: IDBusConnectionProvider) : INotificationHandler, DBusSigHandler { + private val mapping: BiMap = HashBiMap.create() + private var mNReceiver: NotificationReceiver? = null + + init { + connectionProvider.acquireDBusConnectionLater({ notify: DBusConnection -> Log.w("DBusNotificationService", Arrays.toString(notify.names)) }, 1000) + } + + override fun postNotification(context: Context, intent: Intent) { + val event = intent.getStringExtra("event") + if (event == "posted") { + val packageName = intent.getStringExtra("packageName") + NotificationPreferences.putPackageToSeen(context, packageName) + val notificationOption = NotificationPreferences.getNotificationPreferenceForApp(context, packageName) + if (notificationOption == NotificationOption.NO_NOTIFICATIONS) return + val key = intent.getStringExtra("key")!! + val appName = intent.getStringExtra("appName") + val appIcon = intent.getStringExtra("appIcon") + val summary = intent.getStringExtra("summary") + val body = intent.getStringExtra("body") + val vibration: String + vibration = if (notificationOption == NotificationOption.SILENT_NOTIFICATION) "notif_silent" else if (notificationOption == null || notificationOption == NotificationOption.NORMAL_VIBRATION || notificationOption == NotificationOption.DEFAULT) "notif_normal" else if (notificationOption == NotificationOption.STRONG_VIBRATION) "notif_strong" else if (notificationOption == NotificationOption.RINGTONE_VIBRATION) "ringtone" else throw IllegalArgumentException("Not all options handled") + connectionProvider.acquireDBusConnection { notify: DBusConnection -> + synchronized(mapping) { + mapping[key] = notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications::class.java) + .Notify(appName, mapping.getOrDefault(key, UInt32(0)), appIcon, summary, body, emptyList(), + Map.of>( + "x-nemo-feedback", Variant(vibration), + "x-nemo-preview-body", Variant(body), + "x-nemo-preview-summary", Variant(summary), + "urgency", Variant(3.toByte())), 0) + } + } + } else if (event == "removed") { + val key = Objects.requireNonNull(intent.getStringExtra("key")) + // Avoid an infinite loop when the user dismisses the notification on the watch + if (mapping.containsKey(key)) { + var id: UInt32? + synchronized(mapping) { + id = mapping[key] + mapping.remove(key) + } + connectionProvider.acquireDBusConnection { notify: DBusConnection -> + notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications::class.java) + .CloseNotification(id) + } + } + } + } + + override fun handle(s: NotificationClosed?) { + synchronized(mapping) { + if (s != null + && s.reason.toInt() == 2 + && mapping.containsValue(s.id)) { + val dismiss = Intent("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE") + dismiss.putExtra("command", "dismiss") + dismiss.putExtra("key", mapping.inverse()[s.id]) + mapping.inverse().remove(s.id) + mCtx.sendBroadcast(dismiss) + } + } + } + + override fun sync() { + if (mNReceiver == null) { + val filter = IntentFilter() + filter.addAction("org.asteroidos.sync.NOTIFICATION_LISTENER") + mNReceiver = NotificationReceiver() + mCtx.registerReceiver(mNReceiver, filter) + val i = Intent("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE") + i.putExtra("command", "refresh") + mCtx.sendBroadcast(i) + } + connectionProvider.acquireDBusConnection { notify: DBusConnection -> notify.addSigHandler(NotificationClosed::class.java, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications::class.java), this@NotificationService) } + } + + override fun unsync() { + connectionProvider.acquireDBusConnection { notify: DBusConnection -> notify.removeSigHandler(NotificationClosed::class.java, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications::class.java), this@NotificationService) } + if (mNReceiver != null) { + try { + mCtx.unregisterReceiver(mNReceiver) + } catch (ignored: IllegalArgumentException) { + } + mNReceiver = null + } + } + + internal inner class NotificationReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + postNotification(context, intent) + } + } + + companion object { + private val murmur32 = Hashing.murmur3_32_fixed(0) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/dbus/package-info.java b/app/src/main/java/org/asteroidos/sync/dbus/package-info.java new file mode 100644 index 00000000..22345b15 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/package-info.java @@ -0,0 +1,19 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.dbus; \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.java b/app/src/main/java/org/asteroidos/sync/media/IMediaService.kt similarity index 73% rename from app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.java rename to app/src/main/java/org/asteroidos/sync/media/IMediaService.kt index 146b60a7..a179adb8 100644 --- a/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.java +++ b/app/src/main/java/org/asteroidos/sync/media/IMediaService.kt @@ -15,13 +15,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +package org.asteroidos.sync.media -package org.asteroidos.sync.dbus; +import androidx.media3.common.Player +import org.asteroidos.sync.connectivity.IService -import org.freedesktop.dbus.DBusConnection; -import org.freedesktop.dbus.exceptions.DBusException; - -public interface IDBusConnectionCallback { - - public void handleConnection(DBusConnection connection) throws DBusException; -} +interface IMediaService : IService, Player.Listener { + fun onReset() +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/media/MediaSupervisor.kt b/app/src/main/java/org/asteroidos/sync/media/MediaSupervisor.kt new file mode 100644 index 00000000..852e50d1 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/media/MediaSupervisor.kt @@ -0,0 +1,126 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.asteroidos.sync.media + +import android.content.ComponentName +import android.content.Context +import android.content.SharedPreferences +import android.media.session.MediaSessionManager +import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener +import android.os.Handler +import android.os.Looper +import android.util.Log +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi +import androidx.media3.session.MediaController +import androidx.media3.session.SessionToken +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.guava.await +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.asteroidos.sync.connectivity.IService +import org.asteroidos.sync.services.NLService +import java.util.Objects + +class MediaSupervisor(private val mCtx: Context) : IService, OnActiveSessionsChangedListener { + + var mMediaCallback: IMediaService? = null + + var mediaController: MediaController? = null + private set + private var mMediaSessionManager: MediaSessionManager? = null + + var mediaControllerPackageName: String? = null + private set + + private val mSettings: SharedPreferences = mCtx.getSharedPreferences(PREFS_NAME, 0) + + val scope = CoroutineScope(Job() + Dispatchers.Main) + + @OptIn(UnstableApi::class) override fun onActiveSessionsChanged(controllers: List?) { + if (!controllers.isNullOrEmpty()) { + mediaController?.removeListener(mMediaCallback!!) + mediaController = null + mMediaCallback!!.onReset() + + scope.launch { + val mediaController = MediaController.Builder(mCtx, SessionToken.createSessionToken(mCtx, controllers.first().sessionToken).await()).buildAsync().await() + mediaController.addListener(mMediaCallback!!) + + this@MediaSupervisor.mediaController = mediaController + + mMediaCallback!!.onMediaMetadataChanged(mediaController.mediaMetadata) + mMediaCallback!!.onPlaybackStateChanged(mediaController.playbackState) + Log.d(TAG, "MediaController set: " + mediaController.connectedToken?.packageName) + val editor = mSettings.edit() + editor.putString(PREFS_MEDIA_CONTROLLER_PACKAGE, mediaController.connectedToken?.packageName) + editor.apply() + mediaControllerPackageName = mediaController.connectedToken?.packageName + } + } else { + mediaControllerPackageName = null + mediaController = null + mMediaCallback!!.onReset() + } + } + + override fun sync() { + if (mMediaSessionManager == null) { + try { + mMediaSessionManager = mCtx.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager + val controllers = mMediaSessionManager?.getActiveSessions(ComponentName(mCtx, NLService::class.java)) + val handler = Handler(Looper.getMainLooper()) + handler.post { + onActiveSessionsChanged(controllers) + mMediaSessionManager?.addOnActiveSessionsChangedListener(this, ComponentName(mCtx, NLService::class.java)) + } + } catch (e: SecurityException) { + Log.w(TAG, "No Notification Access") + } + } + } + + override fun unsync() { + if (mMediaSessionManager != null) { + mMediaSessionManager?.removeOnActiveSessionsChangedListener(this) + mMediaSessionManager = null + } + if (mediaController != null) { + try { + mediaController?.removeListener(mMediaCallback!!) + } catch (ignored: IllegalArgumentException) { + } + mediaController = null + } + } + + companion object { + val TAG = MediaSupervisor::class.java.toString() +// private const val MEDIA_COMMAND_PREVIOUS: Byte = 0x0 +// private const val MEDIA_COMMAND_NEXT: Byte = 0x1 +// private const val MEDIA_COMMAND_PLAY: Byte = 0x2 +// private const val MEDIA_COMMAND_PAUSE: Byte = 0x3 +// private const val MEDIA_COMMAND_VOLUME: Byte = 0x4 + const val PREFS_NAME = "MediaPreferences" + const val PREFS_MEDIA_CONTROLLER_PACKAGE = "media_controller_package" + const val PREFS_MEDIA_CONTROLLER_PACKAGE_DEFAULT = "default" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java index da8e9d1f..44f50fd3 100644 --- a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java +++ b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java @@ -47,13 +47,14 @@ import org.asteroidos.sync.connectivity.IConnectivityService; import org.asteroidos.sync.connectivity.IService; import org.asteroidos.sync.connectivity.IServiceCallback; -import org.asteroidos.sync.connectivity.MediaService; import org.asteroidos.sync.connectivity.ScreenshotService; import org.asteroidos.sync.connectivity.SilentModeService; import org.asteroidos.sync.connectivity.SlirpService; import org.asteroidos.sync.connectivity.TimeService; import org.asteroidos.sync.connectivity.WeatherService; -import org.asteroidos.sync.dbus.DBusNotificationService; +import org.asteroidos.sync.dbus.MediaService; +import org.asteroidos.sync.dbus.NotificationService; +import org.asteroidos.sync.media.MediaSupervisor; import java.util.ArrayList; import java.util.Arrays; @@ -285,7 +286,6 @@ public void onCreate() { if (bleServices.isEmpty()) { // Register Services - registerBleService(new MediaService(getApplicationContext(), this)); registerBleService(new WeatherService(getApplicationContext(), this)); registerBleService(new ScreenshotService(getApplicationContext(), this)); registerBleService(new TimeService(getApplicationContext(), this)); @@ -294,7 +294,12 @@ public void onCreate() { if (nonBleServices.isEmpty()) { nonBleServices.add(new SilentModeService(getApplicationContext())); - nonBleServices.add(new DBusNotificationService(getApplicationContext(), slirpService)); + nonBleServices.add(new NotificationService(getApplicationContext(), slirpService)); + MediaSupervisor supervisor = new MediaSupervisor(getApplicationContext()); + MediaService service = new MediaService(getApplicationContext(), supervisor, slirpService); + supervisor.setMMediaCallback(service); + nonBleServices.add(service); + nonBleServices.add(supervisor); } handleConnect(); diff --git a/app/src/main/java/org/freedesktop/DBus.java b/app/src/main/java/org/freedesktop/DBus.java deleted file mode 100644 index 5d99a648..00000000 --- a/app/src/main/java/org/freedesktop/DBus.java +++ /dev/null @@ -1,490 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import java.util.Map; -import java.util.List; - -import org.freedesktop.dbus.DBusInterface; -import org.freedesktop.dbus.DBusSignal; -import org.freedesktop.dbus.Position; -import org.freedesktop.dbus.Struct; -import org.freedesktop.dbus.Tuple; -import org.freedesktop.dbus.UInt16; -import org.freedesktop.dbus.UInt32; -import org.freedesktop.dbus.UInt64; -import org.freedesktop.dbus.Variant; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; - -public interface DBus extends DBusInterface -{ - public static final int DBUS_NAME_FLAG_ALLOW_REPLACEMENT = 0x01; - public static final int DBUS_NAME_FLAG_REPLACE_EXISTING = 0x02; - public static final int DBUS_NAME_FLAG_DO_NOT_QUEUE = 0x04; - public static final int DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = 1; - public static final int DBUS_REQUEST_NAME_REPLY_IN_QUEUE = 2; - public static final int DBUS_REQUEST_NAME_REPLY_EXISTS = 3; - public static final int DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER = 4; - public static final int DBUS_RELEASE_NAME_REPLY_RELEASED = 1; - public static final int DBUS_RELEASE_NAME_REPLY_NON_EXISTANT = 2; - public static final int DBUS_RELEASE_NAME_REPLY_NOT_OWNER = 3; - public static final int DBUS_START_REPLY_SUCCESS = 1; - public static final int DBUS_START_REPLY_ALREADY_RUNNING = 2; - /** - * All DBus Applications should respond to the Ping method on this interface - */ - public interface Peer extends DBusInterface - { - public void Ping(); - } - /** - * Objects can provide introspection data via this interface and method. - * See the Introspection Format. - */ - public interface Introspectable extends DBusInterface - { - /** - * @return The XML introspection data for this object - */ - public String Introspect(); - } - /** - * A standard properties interface. - */ - public interface Properties extends DBusInterface - { - /** - * Get the value for the given property. - * @param interface_name The interface this property is associated with. - * @param property_name The name of the property. - * @return The value of the property (may be any valid DBus type). - */ - public A Get (String interface_name, String property_name); - /** - * Set the value for the given property. - * @param interface_name The interface this property is associated with. - * @param property_name The name of the property. - * @param value The new value of the property (may be any valid DBus type). - */ - public void Set (String interface_name, String property_name, A value); - /** - * Get all properties and values. - * @param interface_name The interface the properties is associated with. - * @return The properties mapped to their values. - */ - public Map GetAll (String interface_name); - } - /** - * Messages generated locally in the application. - */ - public interface Local extends DBusInterface - { - public class Disconnected extends DBusSignal - { - public Disconnected(String path) throws DBusException - { - super(path); - } - } - } - - /** - * Initial message to register ourselves on the Bus. - * @return The unique name of this connection to the Bus. - */ - public String Hello(); - /** - * Lists all connected names on the Bus. - * @return An array of all connected names. - */ - public String[] ListNames(); - /** - * Determine if a name has an owner. - * @param name The name to query. - * @return true if the name has an owner. - */ - public boolean NameHasOwner(String name); - /** - * Get the connection unique name that owns the given name. - * @param name The name to query. - * @return The connection which owns the name. - */ - public String GetNameOwner(String name); - /** - * Get the Unix UID that owns a connection name. - * @param connection_name The connection name. - * @return The Unix UID that owns it. - */ - public UInt32 GetConnectionUnixUser(String connection_name); - /** - * Start a service. If the given service is not provided - * by any application, it will be started according to the .service file - * for that service. - * @param name The service name to start. - * @param flags Unused. - * @return DBUS_START_REPLY constants. - */ - public UInt32 StartServiceByName(String name, UInt32 flags); - /** - * Request a name on the bus. - * @param name The name to request. - * @param flags DBUS_NAME flags. - * @return DBUS_REQUEST_NAME_REPLY constants. - */ - public UInt32 RequestName(String name, UInt32 flags); - /** - * Release a name on the bus. - * @param name The name to release. - * @return DBUS_RELEASE_NAME_REPLY constants. - */ - public UInt32 ReleaseName(String name); - - /** - * Add a match rule. - * Will cause you to receive messages that aren't directed to you which - * match this rule. - * @param matchrule The Match rule as a string. Format Undocumented. - */ - public void AddMatch(String matchrule) throws Error.MatchRuleInvalid; - - /** - * Remove a match rule. - * Will cause you to stop receiving messages that aren't directed to you which - * match this rule. - * @param matchrule The Match rule as a string. Format Undocumented. - */ - public void RemoveMatch(String matchrule) throws Error.MatchRuleInvalid; - - /** - * List the connections currently queued for a name. - * @param name The name to query - * @return A list of unique connection IDs. - */ - public String[] ListQueuedOwners(String name); - - /** - * Returns the proccess ID associated with a connection. - * @param connection_name The name of the connection - * @return The PID of the connection. - */ - public UInt32 GetConnectionUnixProcessID(String connection_name); - - /** - * Does something undocumented. - */ - public Byte[] GetConnectionSELinuxSecurityContext(String a); - - /** - * Does something undocumented. - */ - public void ReloadConfig(); - - /** - * Signal sent when the owner of a name changes - */ - public class NameOwnerChanged extends DBusSignal - { - public final String name; - public final String old_owner; - public final String new_owner; - public NameOwnerChanged(String path, String name, String old_owner, String new_owner) throws DBusException - { - super(path, new Object[] { name, old_owner, new_owner }); - this.name = name; - this.old_owner = old_owner; - this.new_owner = new_owner; - } - } - /** - * Signal sent to a connection when it loses a name - */ - public class NameLost extends DBusSignal - { - public final String name; - public NameLost(String path, String name) throws DBusException - { - super(path, name); - this.name = name; - } - } - /** - * Signal sent to a connection when it aquires a name - */ - public class NameAcquired extends DBusSignal - { - public final String name; - public NameAcquired(String path, String name) throws DBusException - { - super(path, name); - this.name = name; - } - } - /** - * Contains standard errors that can be thrown from methods. - */ - public interface Error - { - /** - * Thrown if the method called was unknown on the remote object - */ - @SuppressWarnings("serial") - public class UnknownMethod extends DBusExecutionException - { - public UnknownMethod(String message) - { - super(message); - } - } - /** - * Thrown if the object was unknown on a remote connection - */ - @SuppressWarnings("serial") - public class UnknownObject extends DBusExecutionException - { - public UnknownObject(String message) - { - super(message); - } - } - /** - * Thrown if the requested service was not available - */ - @SuppressWarnings("serial") - public class ServiceUnknown extends DBusExecutionException - { - public ServiceUnknown(String message) - { - super(message); - } - } - /** - * Thrown if the match rule is invalid - */ - @SuppressWarnings("serial") - public class MatchRuleInvalid extends DBusExecutionException - { - public MatchRuleInvalid(String message) - { - super(message); - } - } - /** - * Thrown if there is no reply to a method call - */ - @SuppressWarnings("serial") - public class NoReply extends DBusExecutionException - { - public NoReply(String message) - { - super(message); - } - } - /** - * Thrown if a message is denied due to a security policy - */ - @SuppressWarnings("serial") - public class AccessDenied extends DBusExecutionException - { - public AccessDenied(String message) - { - super(message); - } - } - } - /** - * Description of the interface or method, returned in the introspection data - */ - @Retention(RetentionPolicy.RUNTIME) - public @interface Description - { - String value(); - } - /** - * Indicates that a DBus interface or method is deprecated - */ - @Retention(RetentionPolicy.RUNTIME) - public @interface Deprecated {} - /** - * Contains method-specific annotations - */ - public interface Method - { - /** - * Methods annotated with this do not send a reply - */ - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - public @interface NoReply {} - /** - * Give an error that the method can return - */ - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - public @interface Error - { - String value(); - } - } - /** - * Contains GLib-specific annotations - */ - public interface GLib - { - /** - * Define a C symbol to map to this method. Used by GLib only - */ - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - public @interface CSymbol - { - String value(); - } - } - /** - * Contains Binding-test interfaces - */ - public interface Binding - { - public interface SingleTests extends DBusInterface - { - @Description("Returns the sum of the values in the input list") - public UInt32 Sum(byte[] a); - } - public interface TestClient extends DBusInterface - { - @Description("when the trigger signal is received, this method should be called on the sending process/object.") - public void Response(UInt16 a, double b); - @Description("Causes a callback") - public static class Trigger extends DBusSignal - { - public final UInt16 a; - public final double b; - public Trigger(String path, UInt16 a, double b) throws DBusException - { - super(path, a, b); - this.a = a; - this.b = b; - } - } - - } - public interface Tests extends DBusInterface - { - @Description("Returns whatever it is passed") - public Variant Identity(Variant input); - @Description("Returns whatever it is passed") - public byte IdentityByte(byte input); - @Description("Returns whatever it is passed") - public boolean IdentityBool(boolean input); - @Description("Returns whatever it is passed") - public short IdentityInt16(short input); - @Description("Returns whatever it is passed") - public UInt16 IdentityUInt16(UInt16 input); - @Description("Returns whatever it is passed") - public int IdentityInt32(int input); - @Description("Returns whatever it is passed") - public UInt32 IdentityUInt32(UInt32 input); - @Description("Returns whatever it is passed") - public long IdentityInt64(long input); - @Description("Returns whatever it is passed") - public UInt64 IdentityUInt64(UInt64 input); - @Description("Returns whatever it is passed") - public double IdentityDouble(double input); - @Description("Returns whatever it is passed") - public String IdentityString(String input); - @Description("Returns whatever it is passed") - public Variant[] IdentityArray(Variant[] input); - @Description("Returns whatever it is passed") - public byte[] IdentityByteArray(byte[] input); - @Description("Returns whatever it is passed") - public boolean[] IdentityBoolArray(boolean[] input); - @Description("Returns whatever it is passed") - public short[] IdentityInt16Array(short[] input); - @Description("Returns whatever it is passed") - public UInt16[] IdentityUInt16Array(UInt16[] input); - @Description("Returns whatever it is passed") - public int[] IdentityInt32Array(int[] input); - @Description("Returns whatever it is passed") - public UInt32[] IdentityUInt32Array(UInt32[] input); - @Description("Returns whatever it is passed") - public long[] IdentityInt64Array(long[] input); - @Description("Returns whatever it is passed") - public UInt64[] IdentityUInt64Array(UInt64[] input); - @Description("Returns whatever it is passed") - public double[] IdentityDoubleArray(double[] input); - @Description("Returns whatever it is passed") - public String[] IdentityStringArray(String[] input); - @Description("Returns the sum of the values in the input list") - public long Sum(int[] a); - @Description("Given a map of A => B, should return a map of B => a list of all the As which mapped to B") - public Map> InvertMapping(Map a); - @Description("This method returns the contents of a struct as separate values") - public Triplet DeStruct(TestStruct a); - @Description("Given any compound type as a variant, return all the primitive types recursively contained within as an array of variants") - public List> Primitize(Variant a); - @Description("inverts it's input") - public boolean Invert(boolean a); - @Description("triggers sending of a signal from the supplied object with the given parameter") - public void Trigger(String a, UInt64 b); - @Description("Causes the server to exit") - public void Exit(); - } - public interface TestSignals extends DBusInterface - { - @Description("Sent in response to a method call") - public static class Triggered extends DBusSignal - { - public final UInt64 a; - public Triggered(String path, UInt64 a) throws DBusException - { - super(path, a); - this.a = a; - } - } - } - public final class Triplet extends Tuple - { - @Position(0) - public final A a; - @Position(1) - public final B b; - @Position(2) - public final C c; - public Triplet(A a, B b, C c) - { - this.a = a; - this.b = b; - this.c = c; - } - } - public final class TestStruct extends Struct - { - @Position(0) - public final String a; - @Position(1) - public final UInt32 b; - @Position(2) - public final Short c; - public TestStruct(String a, UInt32 b, Short c) - { - this.a = a; - this.b = b; - this.c = c; - } - } - } -} diff --git a/app/src/main/java/org/freedesktop/GetNotificationsStruct.java b/app/src/main/java/org/freedesktop/GetNotificationsStruct.java new file mode 100644 index 00000000..ff02149c --- /dev/null +++ b/app/src/main/java/org/freedesktop/GetNotificationsStruct.java @@ -0,0 +1,69 @@ +package org.freedesktop; + +import java.util.List; +import java.util.Map; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.annotations.Position; +import org.freedesktop.dbus.types.UInt32; +import org.freedesktop.dbus.types.Variant; + +/** + * Auto-generated class. + */ +public class GetNotificationsStruct extends Struct { + @Position(0) + private final String member0; + @Position(1) + private final UInt32 member1; + @Position(2) + private final String member2; + @Position(3) + private final String member3; + @Position(4) + private final List member4; + @Position(5) + private final Map> member5; + @Position(6) + private final int member6; + + public GetNotificationsStruct(String member0, UInt32 member1, String member2, String member3, List member4, Map> member5, int member6) { + this.member0 = member0; + this.member1 = member1; + this.member2 = member2; + this.member3 = member3; + this.member4 = member4; + this.member5 = member5; + this.member6 = member6; + } + + + public String getMember0() { + return member0; + } + + public UInt32 getMember1() { + return member1; + } + + public String getMember2() { + return member2; + } + + public String getMember3() { + return member3; + } + + public List getMember4() { + return member4; + } + + public Map> getMember5() { + return member5; + } + + public int getMember6() { + return member6; + } + + +} diff --git a/app/src/main/java/org/freedesktop/GetServerInformationTuple.java b/app/src/main/java/org/freedesktop/GetServerInformationTuple.java new file mode 100644 index 00000000..24ee33ff --- /dev/null +++ b/app/src/main/java/org/freedesktop/GetServerInformationTuple.java @@ -0,0 +1,56 @@ +package org.freedesktop; + +import org.freedesktop.dbus.Tuple; +import org.freedesktop.dbus.annotations.Position; + +/** + * Auto-generated class. + */ +public class GetServerInformationTuple extends Tuple { + @Position(0) + private String _arg0; + @Position(1) + private String name; + @Position(2) + private String vendor; + @Position(3) + private String version; + + public GetServerInformationTuple(String _arg0, String name, String vendor, String version) { + this._arg0 = _arg0; + this.name = name; + this.vendor = vendor; + this.version = version; + } + + public void setArg0(String arg) { + _arg0 = arg; + } + + public String getArg0() { + return _arg0; + } + public void setName(String arg) { + name = arg; + } + + public String getName() { + return name; + } + public void setVendor(String arg) { + vendor = arg; + } + + public String getVendor() { + return vendor; + } + public void setVersion(String arg) { + version = arg; + } + + public String getVersion() { + return version; + } + + +} diff --git a/app/src/main/java/org/freedesktop/Notifications.java b/app/src/main/java/org/freedesktop/Notifications.java index e873a5b2..859e6d5f 100644 --- a/app/src/main/java/org/freedesktop/Notifications.java +++ b/app/src/main/java/org/freedesktop/Notifications.java @@ -1,52 +1,69 @@ -/* - * AsteroidOSSync - * Copyright (c) 2024 AsteroidOS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package org.freedesktop; -import org.freedesktop.dbus.DBusInterface; -import org.freedesktop.dbus.DBusSignal; -import org.freedesktop.dbus.UInt32; -import org.freedesktop.dbus.Variant; -import org.freedesktop.dbus.exceptions.DBusException; - import java.util.List; import java.util.Map; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.interfaces.DBusInterface; +import org.freedesktop.dbus.messages.DBusSignal; +import org.freedesktop.dbus.types.UInt32; +import org.freedesktop.dbus.types.Variant; +/** + * Auto-generated class. + */ public interface Notifications extends DBusInterface { + + public List GetCapabilities(); + public UInt32 Notify(String appName, UInt32 replacesId, String appIcon, String summary, String body, List actions, Map> hints, int expireTimeout); + public void CloseNotification(UInt32 id); + public GetServerInformationTuple GetServerInformation(); + public List GetNotifications(String appName); + + public static class NotificationClosed extends DBusSignal { - public static final int REASON_EXPIRED = 1; - public static final int REASON_DISMISSED_BY_USER = 2; - public static final int REASON_CLOSE_NOTIFICATION_CALLED = 3; - public static final int REASON_RESERVED = 4; - - public final UInt32 id; - public final int reason; - public NotificationClosed(String path, UInt32 id, UInt32 reason) throws DBusException { - super(path, id, reason); - this.id = id; - this.reason = reason.intValue(); + + private final UInt32 id; + private final UInt32 reason; + + public NotificationClosed(String _path, UInt32 _id, UInt32 _reason) throws DBusException { + super(_path, _id, _reason); + this.id = _id; + this.reason = _reason; + } + + + public UInt32 getId() { + return id; } + + public UInt32 getReason() { + return reason; + } + + } - public List GetCapabilities(); + public static class ActionInvoked extends DBusSignal { - public UInt32 Notify(String app_name, UInt32 replaces_id, String app_icon, String summary, String body, List actions, Map hints, int expire_timeout); + private final UInt32 id; + private final String actionKey; - public void CloseNotification(UInt32 id); + public ActionInvoked(String _path, UInt32 _id, String _actionKey) throws DBusException { + super(_path, _id, _actionKey); + this.id = _id; + this.actionKey = _actionKey; + } + + + public UInt32 getId() { + return id; + } + + public String getActionKey() { + return actionKey; + } + + + } } diff --git a/app/src/main/java/org/freedesktop/dbus/AbstractConnection.java b/app/src/main/java/org/freedesktop/dbus/AbstractConnection.java deleted file mode 100644 index 2b289cc4..00000000 --- a/app/src/main/java/org/freedesktop/dbus/AbstractConnection.java +++ /dev/null @@ -1,1030 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Type; - -import java.io.File; -import java.io.IOException; - -import java.text.MessageFormat; -import java.text.ParseException; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.Properties; -import java.util.Vector; - -import java.util.regex.Pattern; - -import org.freedesktop.DBus; -import org.freedesktop.dbus.exceptions.NotConnected; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; -import org.freedesktop.dbus.exceptions.FatalDBusException; -import org.freedesktop.dbus.exceptions.FatalException; - -import cx.ath.matthew.debug.Debug; - - -/** Handles a connection to DBus. - */ -public abstract class AbstractConnection -{ - protected class FallbackContainer - { - private Map fallbacks = new HashMap(); - public synchronized void add(String path, ExportedObject eo) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "Adding fallback on "+path+" of "+eo); - fallbacks.put(path.split("/"), eo); - } - public synchronized void remove(String path) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "Removing fallback on "+path); - fallbacks.remove(path.split("/")); - } - public synchronized ExportedObject get(String path) - { - int best = 0; - int i = 0; - ExportedObject bestobject = null; - String[] pathel = path.split("/"); - for (String[] fbpath: fallbacks.keySet()) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Trying fallback path "+Arrays.deepToString(fbpath)+" to match "+Arrays.deepToString(pathel)); - for (i = 0; i < pathel.length && i < fbpath.length; i++) - if (!pathel[i].equals(fbpath[i])) break; - if (i > 0 && i == fbpath.length && i > best) - bestobject = fallbacks.get(fbpath); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Matches "+i+" bestobject now "+bestobject); - } - if (Debug.debug) Debug.print(Debug.DEBUG, "Found fallback for "+path+" of "+bestobject); - return bestobject; - } - } - protected class _thread extends Thread - { - public _thread() - { - setName("DBusConnection"); - } - public void run() - { - try { - Message m = null; - while (_run) { - m = null; - - // read from the wire - try { - // this blocks on outgoing being non-empty or a message being available. - m = readIncoming(); - if (m != null) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Got Incoming Message: "+m); - synchronized (this) { notifyAll(); } - - if (m instanceof DBusSignal) - handleMessage((DBusSignal) m); - else if (m instanceof MethodCall) - handleMessage((MethodCall) m); - else if (m instanceof MethodReturn) - handleMessage((MethodReturn) m); - else if (m instanceof Error) - handleMessage((Error) m); - - m = null; - } - } catch (Exception e) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - if (e instanceof FatalException) { - disconnect(); - } - } - - } - synchronized (this) { notifyAll(); } - } catch (Exception e) { - if (Debug.debug && EXCEPTION_DEBUG) Debug.print(Debug.ERR, e); - } - } - } - private class _globalhandler implements org.freedesktop.DBus.Peer, org.freedesktop.DBus.Introspectable - { - private String objectpath; - public _globalhandler() - { - this.objectpath = null; - } - public _globalhandler(String objectpath) - { - this.objectpath = objectpath; - } - public boolean isRemote() { return false; } - public void Ping() { return; } - public String Introspect() - { - String intro = objectTree.Introspect(objectpath); - if (null == intro) { - ExportedObject eo = fallbackcontainer.get(objectpath); - if (null != eo) intro = eo.introspectiondata; - } - if (null == intro) - throw new DBus.Error.UnknownObject("Introspecting on non-existant object"); - else return - "\n"+intro; - } - } - protected class _workerthread extends Thread - { - private boolean _run = true; - public void halt() - { - _run = false; - } - public void run() - { - while (_run) { - Runnable r = null; - synchronized (runnables) { - while (runnables.size() == 0 && _run) - try { runnables.wait(); } catch (InterruptedException Ie) {} - if (runnables.size() > 0) - r = runnables.removeFirst(); - } - if (null != r) r.run(); - } - } - } - private class _sender extends Thread - { - public _sender() - { - setName("Sender"); - } - public void run() - { - Message m = null; - - if (Debug.debug) Debug.print(Debug.INFO, "Monitoring outbound queue"); - // block on the outbound queue and send from it - while (_run) { - if (null != outgoing) synchronized (outgoing) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking"); - while (outgoing.size() == 0 && _run) - try { outgoing.wait(); } catch (InterruptedException Ie) {} - if (Debug.debug) Debug.print(Debug.VERBOSE, "Notified"); - if (outgoing.size() > 0) - m = outgoing.remove(); - if (Debug.debug) Debug.print(Debug.DEBUG, "Got message: "+m); - } - if (null != m) - sendMessage(m); - m = null; - } - - if (Debug.debug) Debug.print(Debug.INFO, "Flushing outbound queue and quitting"); - // flush the outbound queue before disconnect. - if (null != outgoing) do { - EfficientQueue ogq = outgoing; - synchronized (ogq) { - outgoing = null; - } - if (!ogq.isEmpty()) - m = ogq.remove(); - else m = null; - sendMessage(m); - } while (null != m); - - // close the underlying streams - } - } - /** - * Timeout in us on checking the BUS for incoming messages and sending outgoing messages - */ - protected static final int TIMEOUT = 100000; - /** Initial size of the pending calls map */ - private static final int PENDING_MAP_INITIAL_SIZE = 10; - static final String BUSNAME_REGEX = "^[-_a-zA-Z][-_a-zA-Z0-9]*(\\.[-_a-zA-Z][-_a-zA-Z0-9]*)*$"; - static final String CONNID_REGEX = "^:[0-9]*\\.[0-9]*$"; - static final String OBJECT_REGEX = "^/([-_a-zA-Z0-9]+(/[-_a-zA-Z0-9]+)*)?$"; - static final byte THREADCOUNT = 4; - static final int MAX_ARRAY_LENGTH = 67108864; - static final int MAX_NAME_LENGTH = 255; - protected Map exportedObjects; - private ObjectTree objectTree; - private _globalhandler _globalhandlerreference; - protected Map importedObjects; - protected Map>> handledSignals; - protected EfficientMap pendingCalls; - protected Map> pendingCallbacks; - protected Map> pendingCallbackReplys; - protected LinkedList runnables; - protected LinkedList<_workerthread> workers; - protected FallbackContainer fallbackcontainer; - protected boolean _run; - EfficientQueue outgoing; - LinkedList pendingErrors; - private static final Map infomap = new HashMap(); - protected _thread thread; - protected _sender sender; - protected Transport transport; - protected String addr; - protected boolean weakreferences = false; - static final Pattern dollar_pattern = Pattern.compile("[$]"); - public static final boolean EXCEPTION_DEBUG; - static final boolean FLOAT_SUPPORT; - protected boolean connected = false; - static { - FLOAT_SUPPORT = (null != System.getenv("DBUS_JAVA_FLOATS")); - EXCEPTION_DEBUG = (null != System.getenv("DBUS_JAVA_EXCEPTION_DEBUG")); - if (EXCEPTION_DEBUG) { - Debug.print("Debugging of internal exceptions enabled"); - Debug.setThrowableTraces(true); - } - if (Debug.debug) { - File f = new File("debug.conf"); - if (f.exists()) { - Debug.print("Loading debug config file: "+f); - try { - Debug.loadConfig(f); - } catch (IOException IOe) {} - } else { - Properties p = new Properties(); - p.setProperty("ALL", "INFO"); - Debug.print("debug config file "+f+" does not exist, not loading."); - } - Debug.setHexDump(true); - } - } - - protected AbstractConnection(String address) throws DBusException - { - exportedObjects = new HashMap(); - importedObjects = new HashMap(); - _globalhandlerreference = new _globalhandler(); - synchronized (exportedObjects) { - exportedObjects.put(null, new ExportedObject(_globalhandlerreference, weakreferences)); - } - handledSignals = new HashMap>>(); - pendingCalls = new EfficientMap(PENDING_MAP_INITIAL_SIZE); - outgoing = new EfficientQueue(PENDING_MAP_INITIAL_SIZE); - pendingCallbacks = new HashMap>(); - pendingCallbackReplys = new HashMap>(); - pendingErrors = new LinkedList(); - runnables = new LinkedList(); - workers = new LinkedList<_workerthread>(); - objectTree = new ObjectTree(); - fallbackcontainer = new FallbackContainer(); - synchronized (workers) { - for (int i = 0; i < THREADCOUNT; i++) { - _workerthread t = new _workerthread(); - t.start(); - workers.add(t); - } - } - _run = true; - addr = address; - } - - protected void listen() - { - // start listening - thread = new _thread(); - thread.start(); - sender = new _sender(); - sender.start(); - } - - /** - * Change the number of worker threads to receive method calls and handle signals. - * Default is 4 threads - * @param newcount The new number of worker Threads to use. - */ - public void changeThreadCount(byte newcount) - { - synchronized (workers) { - if (workers.size() > newcount) { - int n = workers.size() - newcount; - for (int i = 0; i < n; i++) { - _workerthread t = workers.removeFirst(); - t.halt(); - } - } else if (workers.size() < newcount) { - int n = newcount-workers.size(); - for (int i = 0; i < n; i++) { - _workerthread t = new _workerthread(); - t.start(); - workers.add(t); - } - } - } - } - private void addRunnable(Runnable r) - { - synchronized(runnables) { - runnables.add(r); - runnables.notifyAll(); - } - } - - String getExportedObject(DBusInterface i) throws DBusException - { - synchronized (exportedObjects) { - for (String s: exportedObjects.keySet()) - if (i.equals(exportedObjects.get(s).object.get())) - return s; - } - - String s = importedObjects.get(i).objectpath; - if (null != s) return s; - - throw new DBusException("Not an object exported or imported by this connection"); - } - - abstract DBusInterface getExportedObject(String source, String path) throws DBusException; - - /** - * Returns a structure with information on the current method call. - * @return the DBusCallInfo for this method call, or null if we are not in a method call. - */ - public static DBusCallInfo getCallInfo() - { - DBusCallInfo info; - synchronized (infomap) { - info = infomap.get(Thread.currentThread()); - } - return info; - } - - /** - * If set to true the bus will not hold a strong reference to exported objects. - * If they go out of scope they will automatically be unexported from the bus. - * The default is to hold a strong reference, which means objects must be - * explicitly unexported before they will be garbage collected. - */ - public void setWeakReferences(boolean weakreferences) - { - this.weakreferences = weakreferences; - } - - /** - * Export an object so that its methods can be called on DBus. - * @param objectpath The path to the object we are exposing. MUST be in slash-notation, like "/org/freedesktop/Local", - * and SHOULD end with a capitalised term. Only one object may be exposed on each path at any one time, but an object - * may be exposed on several paths at once. - * @param object The object to export. - * @throws DBusException If the objectpath is already exporting an object. - * or if objectpath is incorrectly formatted, - */ - public void exportObject(String objectpath, DBusInterface object) throws DBusException - { - if (null == objectpath || "".equals(objectpath)) - throw new DBusException($_("Must Specify an Object Path")); - if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid object path: ")+objectpath); - synchronized (exportedObjects) { - if (null != exportedObjects.get(objectpath)) - throw new DBusException($_("Object already exported")); - ExportedObject eo = new ExportedObject(object, weakreferences); - exportedObjects.put(objectpath, eo); - objectTree.add(objectpath, eo, eo.introspectiondata); - } - } - /** - * Export an object as a fallback object. - * This object will have it's methods invoked for all paths starting - * with this object path. - * @param objectprefix The path below which the fallback handles calls. - * MUST be in slash-notation, like "/org/freedesktop/Local", - * @param object The object to export. - * @throws DBusException If the objectpath is incorrectly formatted, - */ - public void addFallback(String objectprefix, DBusInterface object) throws DBusException - { - if (null == objectprefix || "".equals(objectprefix)) - throw new DBusException($_("Must Specify an Object Path")); - if (!objectprefix.matches(OBJECT_REGEX)||objectprefix.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid object path: ")+objectprefix); - ExportedObject eo = new ExportedObject(object, weakreferences); - fallbackcontainer.add(objectprefix, eo); - } - /** - * Remove a fallback - * @param objectprefix The prefix to remove the fallback for. - */ - public void removeFallback(String objectprefix) - { - fallbackcontainer.remove(objectprefix); - } - /** - * Stop Exporting an object - * @param objectpath The objectpath to stop exporting. - */ - public void unExportObject(String objectpath) - { - synchronized (exportedObjects) { - exportedObjects.remove(objectpath); - objectTree.remove(objectpath); - } - } - /** - * Return a reference to a remote object. - * This method will resolve the well known name (if given) to a unique bus name when you call it. - * This means that if a well known name is released by one process and acquired by another calls to - * objects gained from this method will continue to operate on the original process. - * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local") - * or may be a DBus address such as ":1-16". - * @param objectpath The path on which the process is exporting the object.$ - * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures - * as the interface the remote object is exporting. - * @return A reference to a remote object. - * @throws ClassCastException If type is not a sub-type of DBusInterface - * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. - */ - /** - * Send a signal. - * @param signal The signal to send. - */ - public void sendSignal(DBusSignal signal) - { - queueOutgoing(signal); - } - void queueOutgoing(Message m) - { - synchronized (outgoing) { - if (null == outgoing) return; - outgoing.add(m); - if (Debug.debug) Debug.print(Debug.DEBUG, "Notifying outgoing thread"); - outgoing.notifyAll(); - } - } - /** - * Remove a Signal Handler. - * Stops listening for this signal. - * @param type The signal to watch for. - * @throws DBusException If listening for the signal on the bus failed. - * @throws ClassCastException If type is not a sub-type of DBusSignal. - */ - public void removeSigHandler(Class type, DBusSigHandler handler) throws DBusException - { - if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); - removeSigHandler(new DBusMatchRule(type), handler); - } - /** - * Remove a Signal Handler. - * Stops listening for this signal. - * @param type The signal to watch for. - * @param object The object emitting the signal. - * @throws DBusException If listening for the signal on the bus failed. - * @throws ClassCastException If type is not a sub-type of DBusSignal. - */ - public void removeSigHandler(Class type, DBusInterface object, DBusSigHandler handler) throws DBusException - { - if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); - String objectpath = importedObjects.get(object).objectpath; - if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid object path: ")+objectpath); - removeSigHandler(new DBusMatchRule(type, null, objectpath), handler); - } - - protected abstract void removeSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException; - /** - * Add a Signal Handler. - * Adds a signal handler to call when a signal is received which matches the specified type and name. - * @param type The signal to watch for. - * @param handler The handler to call when a signal is received. - * @throws DBusException If listening for the signal on the bus failed. - * @throws ClassCastException If type is not a sub-type of DBusSignal. - */ - @SuppressWarnings("unchecked") - public void addSigHandler(Class type, DBusSigHandler handler) throws DBusException - { - if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); - addSigHandler(new DBusMatchRule(type), (DBusSigHandler) handler); - } - /** - * Add a Signal Handler. - * Adds a signal handler to call when a signal is received which matches the specified type, name and object. - * @param type The signal to watch for. - * @param object The object from which the signal will be emitted - * @param handler The handler to call when a signal is received. - * @throws DBusException If listening for the signal on the bus failed. - * @throws ClassCastException If type is not a sub-type of DBusSignal. - */ - @SuppressWarnings("unchecked") - public void addSigHandler(Class type, DBusInterface object, DBusSigHandler handler) throws DBusException - { - if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); - String objectpath = importedObjects.get(object).objectpath; - if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid object path: ")+objectpath); - addSigHandler(new DBusMatchRule(type, null, objectpath), (DBusSigHandler) handler); - } - - protected abstract void addSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException; - - protected void addSigHandlerWithoutMatch(Class signal, DBusSigHandler handler) throws DBusException - { - DBusMatchRule rule = new DBusMatchRule(signal); - SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); - synchronized (handledSignals) { - Vector> v = handledSignals.get(key); - if (null == v) { - v = new Vector>(); - v.add(handler); - handledSignals.put(key, v); - } else - v.add(handler); - } - } - - /** - * Disconnect from the Bus. - */ - public void disconnect() - { - connected = false; - if (Debug.debug) Debug.print(Debug.INFO, "Sending disconnected signal"); - try { - handleMessage(new org.freedesktop.DBus.Local.Disconnected("/")); - } catch (Exception ee) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, ee); - } - - if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting Abstract Connection"); - // run all pending tasks. - while (runnables.size() > 0) - synchronized (runnables) { - runnables.notifyAll(); - } - - // stop the main thread - _run = false; - - // unblock the sending thread. - synchronized (outgoing) { - outgoing.notifyAll(); - } - - // disconnect from the trasport layer - try { - if (null != transport) { - transport.disconnect(); - transport = null; - } - } catch (IOException IOe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); - } - - // stop all the workers - synchronized(workers) { - for (_workerthread t: workers) - t.halt(); - } - - // make sure none are blocking on the runnables queue still - synchronized (runnables) { - runnables.notifyAll(); - } - } - - public void finalize() - { - disconnect(); - } - /** - * Return any DBus error which has been received. - * @return A DBusExecutionException, or null if no error is pending. - */ - public DBusExecutionException getError() - { - synchronized (pendingErrors) { - if (pendingErrors.size() == 0) return null; - else - return pendingErrors.removeFirst().getException(); - } - } - - /** - * Call a method asynchronously and set a callback. - * This handler will be called in a separate thread. - * @param object The remote object on which to call the method. - * @param m The name of the method on the interface to call. - * @param callback The callback handler. - * @param parameters The parameters to call the method with. - */ - @SuppressWarnings("unchecked") - public void callWithCallback(DBusInterface object, String m, CallbackHandler callback, Object... parameters) - { - if (Debug.debug) Debug.print(Debug.VERBOSE, "callWithCallback("+object+","+m+", "+callback); - Class[] types = new Class[parameters.length]; - for (int i = 0; i < parameters.length; i++) - types[i] = parameters[i].getClass(); - RemoteObject ro = importedObjects.get(object); - - try { - Method me; - if (null == ro.iface) - me = object.getClass().getMethod(m, types); - else - me = ro.iface.getMethod(m, types); - RemoteInvocationHandler.executeRemoteMethod(ro, me, this, RemoteInvocationHandler.CALL_TYPE_CALLBACK, callback, parameters); - } catch (DBusExecutionException DBEe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); - throw DBEe; - } catch (Exception e) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - throw new DBusExecutionException(e.getMessage()); - } - } - - /** - * Call a method asynchronously and get a handle with which to get the reply. - * @param object The remote object on which to call the method. - * @param m The name of the method on the interface to call. - * @param parameters The parameters to call the method with. - * @return A handle to the call. - */ - @SuppressWarnings("unchecked") - public DBusAsyncReply callMethodAsync(DBusInterface object, String m, Object... parameters) - { - Class[] types = new Class[parameters.length]; - for (int i = 0; i < parameters.length; i++) - types[i] = parameters[i].getClass(); - RemoteObject ro = importedObjects.get(object); - - try { - Method me; - if (null == ro.iface) - me = object.getClass().getMethod(m, types); - else - me = ro.iface.getMethod(m, types); - return (DBusAsyncReply) RemoteInvocationHandler.executeRemoteMethod(ro, me, this, RemoteInvocationHandler.CALL_TYPE_ASYNC, null, parameters); - } catch (DBusExecutionException DBEe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); - throw DBEe; - } catch (Exception e) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - throw new DBusExecutionException(e.getMessage()); - } - } - - private void handleMessage(final MethodCall m) throws DBusException - { - if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming method call: "+m); - - ExportedObject eo = null; - Method meth = null; - Object o = null; - - if (null == m.getInterface() || - m.getInterface().equals("org.freedesktop.DBus.Peer") || - m.getInterface().equals("org.freedesktop.DBus.Introspectable")) { - synchronized (exportedObjects) { - eo = exportedObjects.get(null); - } - if (null != eo && null == eo.object.get()) { - unExportObject(null); - eo = null; - } - if (null != eo) { - meth = eo.methods.get(new MethodTuple(m.getName(), m.getSig())); - } - if (null != meth) - o = new _globalhandler(m.getPath()); - else - eo = null; - } - if (null == o) { - // now check for specific exported functions - - synchronized (exportedObjects) { - eo = exportedObjects.get(m.getPath()); - } - if (null != eo && null == eo.object.get()) { - if (Debug.debug) Debug.print(Debug.INFO, "Unexporting "+m.getPath()+" implicitly"); - unExportObject(m.getPath()); - eo = null; - } - - if (null == eo) { - eo = fallbackcontainer.get(m.getPath()); - } - - if (null == eo) { - try { - queueOutgoing(new Error(m, new DBus.Error.UnknownObject(m.getPath()+ $_(" is not an object provided by this process.")))); - } catch (DBusException DBe) {} - return; - } - if (Debug.debug) { - Debug.print(Debug.VERBOSE, "Searching for method "+m.getName()+" with signature "+m.getSig()); - Debug.print(Debug.VERBOSE, "List of methods on "+eo+":"); - for (MethodTuple mt: eo.methods.keySet()) - Debug.print(Debug.VERBOSE, " "+mt+" => "+eo.methods.get(mt)); - } - meth = eo.methods.get(new MethodTuple(m.getName(), m.getSig())); - if (null == meth) { - try { - queueOutgoing(new Error(m, new DBus.Error.UnknownMethod(MessageFormat.format($_("The method `{0}.{1}' does not exist on this object."), new Object[] { m.getInterface(), m.getName() })))); - } catch (DBusException DBe) {} - return; - } - o = eo.object.get(); - } - - // now execute it - final Method me = meth; - final Object ob = o; - final boolean noreply = (1 == (m.getFlags() & Message.Flags.NO_REPLY_EXPECTED)); - final DBusCallInfo info = new DBusCallInfo(m); - final AbstractConnection conn = this; - if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for method "+meth); - addRunnable(new Runnable() - { - private boolean run = false; - public synchronized void run() - { - if (run) return; - run = true; - if (Debug.debug) Debug.print(Debug.DEBUG, "Running method "+me+" for remote call"); - try { - Type[] ts = me.getGenericParameterTypes(); - m.setArgs(Marshalling.deSerializeParameters(m.getParameters(), ts, conn)); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserialised "+Arrays.deepToString(m.getParameters())+" to types "+Arrays.deepToString(ts)); - } catch (Exception e) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - try { - conn.queueOutgoing(new Error(m, new DBus.Error.UnknownMethod($_("Failure in de-serializing message: ")+e))); - } catch (DBusException DBe) {} - return; - } - - try { - synchronized (infomap) { - infomap.put(Thread.currentThread(), info); - } - Object result; - try { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Invoking Method: "+me+" on "+ob+" with parameters "+Arrays.deepToString(m.getParameters())); - result = me.invoke(ob, m.getParameters()); - } catch (InvocationTargetException ITe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, ITe.getCause()); - throw ITe.getCause(); - } - synchronized (infomap) { - infomap.remove(Thread.currentThread()); - } - if (!noreply) { - MethodReturn reply; - if (Void.TYPE.equals(me.getReturnType())) - reply = new MethodReturn(m, null); - else { - StringBuffer sb = new StringBuffer(); - for (String s: Marshalling.getDBusType(me.getGenericReturnType())) - sb.append(s); - Object[] nr = Marshalling.convertParameters(new Object[] { result }, new Type[] {me.getGenericReturnType()}, conn); - - reply = new MethodReturn(m, sb.toString(),nr); - } - conn.queueOutgoing(reply); - } - } catch (DBusExecutionException DBEe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); - try { - conn.queueOutgoing(new Error(m, DBEe)); - } catch (DBusException DBe) {} - } catch (Throwable e) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - try { - conn.queueOutgoing(new Error(m, new DBusExecutionException(MessageFormat.format($_("Error Executing Method {0}.{1}: {2}"), new Object[] { m.getInterface(), m.getName(), e.getMessage() })))); - } catch (DBusException DBe) {} - } - } - }); - } - @SuppressWarnings({"unchecked","deprecation"}) - private void handleMessage(final DBusSignal s) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming signal: "+s); - Vector> v = new Vector>(); - synchronized(handledSignals) { - Vector> t; - t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), null, null)); - if (null != t) v.addAll(t); - t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), s.getPath(), null)); - if (null != t) v.addAll(t); - t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), null, s.getSource())); - if (null != t) v.addAll(t); - t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), s.getPath(), s.getSource())); - if (null != t) v.addAll(t); - } - if (0 == v.size()) return; - final AbstractConnection conn = this; - for (final DBusSigHandler h: v) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for signal "+s+" with handler "+h); - addRunnable(new Runnable() { - private boolean run = false; - public synchronized void run() - { - if (run) return; - run = true; - try { - DBusSignal rs; - if (s instanceof DBusSignal.internalsig || s.getClass().equals(DBusSignal.class)) - rs = s.createReal(conn); - else - rs = s; - ((DBusSigHandler)h).handle(rs); - } catch (DBusException DBe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); - try { - conn.queueOutgoing(new Error(s, new DBusExecutionException("Error handling signal "+s.getInterface()+"."+s.getName()+": "+DBe.getMessage()))); - } catch (DBusException DBe2) {} - } - } - }); - } - } - private void handleMessage(final Error err) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming error: "+err); - MethodCall m = null; - if (null == pendingCalls) return; - synchronized (pendingCalls) { - if (pendingCalls.contains(err.getReplySerial())) - m = pendingCalls.remove(err.getReplySerial()); - } - if (null != m) { - m.setReply(err); - CallbackHandler cbh = null; - DBusAsyncReply asr = null; - synchronized (pendingCallbacks) { - cbh = pendingCallbacks.remove(m); - if (Debug.debug) Debug.print(Debug.VERBOSE, cbh+" = pendingCallbacks.remove("+m+")"); - asr = pendingCallbackReplys.remove(m); - } - // queue callback for execution - if (null != cbh) { - final CallbackHandler fcbh = cbh; - if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Error Runnable with callback handler "+fcbh); - addRunnable(new Runnable() { - private boolean run = false; - public synchronized void run() - { - if (run) return; - run = true; - try { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Running Error Callback for "+err); - DBusCallInfo info = new DBusCallInfo(err); - synchronized (infomap) { - infomap.put(Thread.currentThread(), info); - } - - fcbh.handleError(err.getException()); - synchronized (infomap) { - infomap.remove(Thread.currentThread()); - } - - } catch (Exception e) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - } - } - }); - } - - } - else - synchronized (pendingErrors) { - pendingErrors.addLast(err); } - } - @SuppressWarnings("unchecked") - private void handleMessage(final MethodReturn mr) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming method return: "+mr); - MethodCall m = null; - if (null == pendingCalls) return; - synchronized (pendingCalls) { - if (pendingCalls.contains(mr.getReplySerial())) - m = pendingCalls.remove(mr.getReplySerial()); - } - if (null != m) { - m.setReply(mr); - mr.setCall(m); - CallbackHandler cbh = null; - DBusAsyncReply asr = null; - synchronized (pendingCallbacks) { - cbh = pendingCallbacks.remove(m); - if (Debug.debug) Debug.print(Debug.VERBOSE, cbh+" = pendingCallbacks.remove("+m+")"); - asr = pendingCallbackReplys.remove(m); - } - // queue callback for execution - if (null != cbh) { - final CallbackHandler fcbh = cbh; - final DBusAsyncReply fasr = asr; - if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for method "+fasr.getMethod()+" with callback handler "+fcbh); - addRunnable(new Runnable() { - private boolean run = false; - public synchronized void run() - { - if (run) return; - run = true; - try { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Running Callback for "+mr); - DBusCallInfo info = new DBusCallInfo(mr); - synchronized (infomap) { - infomap.put(Thread.currentThread(), info); - } - - fcbh.handle(RemoteInvocationHandler.convertRV(mr.getSig(), mr.getParameters(), fasr.getMethod(), fasr.getConnection())); - synchronized (infomap) { - infomap.remove(Thread.currentThread()); - } - - } catch (Exception e) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - } - } - }); - } - - } else - try { - queueOutgoing(new Error(mr, new DBusExecutionException($_("Spurious reply. No message with the given serial id was awaiting a reply.")))); - } catch (DBusException DBe) {} - } - protected void sendMessage(Message m) - { - try { - if (!connected) throw new NotConnected($_("Disconnected")); - if (m instanceof DBusSignal) - ((DBusSignal) m).appendbody(this); - - if (m instanceof MethodCall) { - if (0 == (m.getFlags() & Message.Flags.NO_REPLY_EXPECTED)) - if (null == pendingCalls) - ((MethodCall) m).setReply(new Error("org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.Disconnected", 0, "s", new Object[] { $_("Disconnected") })); - else synchronized (pendingCalls) { - pendingCalls.put(m.getSerial(),(MethodCall) m); - } - } - - transport.mout.writeMessage(m); - - } catch (Exception e) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - if (m instanceof MethodCall && e instanceof NotConnected) - try { - ((MethodCall) m).setReply(new Error("org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.Disconnected", 0, "s", new Object[] { $_("Disconnected") })); - } catch (DBusException DBe) {} - if (m instanceof MethodCall && e instanceof DBusExecutionException) - try { - ((MethodCall)m).setReply(new Error(m, e)); - } catch (DBusException DBe) {} - else if (m instanceof MethodCall) - try { - if (Debug.debug) Debug.print(Debug.INFO, "Setting reply to "+m+" as an error"); - ((MethodCall)m).setReply(new Error(m, new DBusExecutionException($_("Message Failed to Send: ")+e.getMessage()))); - } catch (DBusException DBe) {} - else if (m instanceof MethodReturn) - try { - transport.mout.writeMessage(new Error(m, e)); - } catch(IOException IOe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); - } catch(DBusException IOe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - } - if (e instanceof IOException) disconnect(); - } - } - private Message readIncoming() throws DBusException - { - if (!connected) throw new NotConnected($_("No transport present")); - Message m = null; - try { - m = transport.min.readMessage(); - } catch (IOException IOe) { - throw new FatalDBusException(IOe.getMessage()); - } - return m; - } - /** - * Returns the address this connection is connected to. - */ - public BusAddress getAddress() throws ParseException { return new BusAddress(addr); } -} diff --git a/app/src/main/java/org/freedesktop/dbus/ArrayFrob.java b/app/src/main/java/org/freedesktop/dbus/ArrayFrob.java deleted file mode 100644 index df4f46bf..00000000 --- a/app/src/main/java/org/freedesktop/dbus/ArrayFrob.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.lang.reflect.Array; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Hashtable; -import java.util.List; - -import cx.ath.matthew.debug.Debug; - -class ArrayFrob -{ - static Hashtable, Class> primitiveToWrapper = new Hashtable, Class>(); - static Hashtable, Class> wrapperToPrimitive = new Hashtable, Class>(); - static { - primitiveToWrapper.put( Boolean.TYPE, Boolean.class ); - primitiveToWrapper.put( Byte.TYPE, Byte.class ); - primitiveToWrapper.put( Short.TYPE, Short.class ); - primitiveToWrapper.put( Character.TYPE, Character.class ); - primitiveToWrapper.put( Integer.TYPE, Integer.class ); - primitiveToWrapper.put( Long.TYPE, Long.class ); - primitiveToWrapper.put( Float.TYPE, Float.class ); - primitiveToWrapper.put( Double.TYPE, Double.class ); - wrapperToPrimitive.put( Boolean.class, Boolean.TYPE ); - wrapperToPrimitive.put( Byte.class, Byte.TYPE ); - wrapperToPrimitive.put( Short.class, Short.TYPE ); - wrapperToPrimitive.put( Character.class, Character.TYPE ); - wrapperToPrimitive.put( Integer.class, Integer.TYPE ); - wrapperToPrimitive.put( Long.class, Long.TYPE ); - wrapperToPrimitive.put( Float.class, Float.TYPE ); - wrapperToPrimitive.put( Double.class, Double.TYPE ); - - } - @SuppressWarnings("unchecked") - public static T[] wrap(Object o) throws IllegalArgumentException - { - Class ac = o.getClass(); - if (!ac.isArray()) throw new IllegalArgumentException($_("Not an array")); - Class cc = ac.getComponentType(); - Class ncc = primitiveToWrapper.get(cc); - if (null == ncc) throw new IllegalArgumentException($_("Not a primitive type")); - T[] ns = (T[]) Array.newInstance(ncc, Array.getLength(o)); - for (int i = 0; i < ns.length; i++) - ns[i] = (T) Array.get(o, i); - return ns; - } - @SuppressWarnings("unchecked") - public static Object unwrap(T[] ns) throws IllegalArgumentException - { - Class ac = (Class) ns.getClass(); - Class cc = (Class) ac.getComponentType(); - Class ncc = wrapperToPrimitive.get(cc); - if (null == ncc) throw new IllegalArgumentException($_("Not a wrapper type")); - Object o = Array.newInstance(ncc, ns.length); - for (int i = 0; i < ns.length; i++) - Array.set(o, i, ns[i]); - return o; - } - public static List listify(T[] ns) throws IllegalArgumentException - { - return Arrays.asList(ns); - } - @SuppressWarnings("unchecked") - public static List listify(Object o) throws IllegalArgumentException - { - if (o instanceof Object[]) return listify((T[]) o); - if (!o.getClass().isArray()) throw new IllegalArgumentException($_("Not an array")); - List l = new ArrayList(Array.getLength(o)); - for (int i = 0; i < Array.getLength(o); i++) - l.add((T)Array.get(o, i)); - return l; - } - @SuppressWarnings("unchecked") - public static T[] delist(List l, Class c) throws IllegalArgumentException - { - return l.toArray((T[]) Array.newInstance(c, 0)); - } - public static Object delistprimitive(List l, Class c) throws IllegalArgumentException - { - Object o = Array.newInstance(c, l.size()); - for (int i = 0; i < l.size(); i++) - Array.set(o, i, l.get(i)); - return o; - } - @SuppressWarnings("unchecked") - public static Object convert(Object o, Class c) throws IllegalArgumentException - { - /* Possible Conversions: - * - ** List -> List - ** List -> int[] - ** List -> Integer[] - ** int[] -> int[] - ** int[] -> List - ** int[] -> Integer[] - ** Integer[] -> Integer[] - ** Integer[] -> int[] - ** Integer[] -> List - */ - try { - // List -> List - if (List.class.equals(c) - && o instanceof List) - return o; - - // int[] -> List - // Integer[] -> List - if (List.class.equals(c) - && o.getClass().isArray()) - return listify(o); - - // int[] -> int[] - // Integer[] -> Integer[] - if (o.getClass().isArray() - && c.isArray() - && o.getClass().getComponentType().equals(c.getComponentType())) - return o; - - // int[] -> Integer[] - if (o.getClass().isArray() - && c.isArray() - && o.getClass().getComponentType().isPrimitive()) - return wrap(o); - - // Integer[] -> int[] - if (o.getClass().isArray() - && c.isArray() - && c.getComponentType().isPrimitive()) - return unwrap((Object[]) o); - - // List -> int[] - if (o instanceof List - && c.isArray() - && c.getComponentType().isPrimitive()) - return delistprimitive((List) o, (Class) c.getComponentType()); - - // List -> Integer[] - if (o instanceof List - && c.isArray()) - return delist((List) o, (Class) c.getComponentType()); - - if (o.getClass().isArray() - && c.isArray()) - return type((Object[]) o, (Class) c.getComponentType()); - - } catch (Exception e) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - throw new IllegalArgumentException(e); - } - - throw new IllegalArgumentException(MessageFormat.format($_("Not An Expected Convertion type from {0} to {1}"), new Object[] { o.getClass(), c})); - } - public static Object[] type(Object[] old, Class c) - { - Object[] ns = (Object[]) Array.newInstance(c, old.length); - for (int i = 0; i < ns.length; i++) - ns[i] = old[i]; - return ns; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/BusAddress.java b/app/src/main/java/org/freedesktop/dbus/BusAddress.java deleted file mode 100644 index 79d29865..00000000 --- a/app/src/main/java/org/freedesktop/dbus/BusAddress.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; -import static org.freedesktop.dbus.Gettext.$_; -import java.text.ParseException; -import java.util.Map; -import java.util.HashMap; -import cx.ath.matthew.debug.Debug; - -public class BusAddress -{ - private String type; - private Map parameters; - public BusAddress(String address) throws ParseException - { - if (null == address || "".equals(address)) throw new ParseException($_("Bus address is blank"), 0); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Parsing bus address: "+address); - String[] ss = address.split(":", 2); - if (ss.length < 2) throw new ParseException($_("Bus address is invalid: ")+address, 0); - type = ss[0]; - if (Debug.debug) Debug.print(Debug.VERBOSE, "Transport type: "+type); - String[] ps = ss[1].split(","); - parameters = new HashMap(); - for (String p: ps) { - String[] kv = p.split("=", 2); - parameters.put(kv[0], kv[1]); - } - if (Debug.debug) Debug.print(Debug.VERBOSE, "Transport options: "+parameters); - } - public String getType() { return type; } - public String getParameter(String key) { return parameters.get(key); } - public String toString() { return type+": "+parameters; } -} diff --git a/app/src/main/java/org/freedesktop/dbus/CallbackHandler.java b/app/src/main/java/org/freedesktop/dbus/CallbackHandler.java deleted file mode 100644 index b05b5001..00000000 --- a/app/src/main/java/org/freedesktop/dbus/CallbackHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import org.freedesktop.dbus.exceptions.DBusExecutionException; - -/** - * Interface for callbacks in async mode - */ -public interface CallbackHandler -{ - public void handle(ReturnType r); - public void handleError(DBusExecutionException e); -} diff --git a/app/src/main/java/org/freedesktop/dbus/Container.java b/app/src/main/java/org/freedesktop/dbus/Container.java deleted file mode 100644 index d2efb677..00000000 --- a/app/src/main/java/org/freedesktop/dbus/Container.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.lang.reflect.Field; -import java.lang.reflect.Type; - -/** - * This class is the super class of both Structs and Tuples - * and holds common methods. - */ -abstract class Container -{ - private static Map typecache = new HashMap(); - static void putTypeCache(Type k, Type[] v) - { - typecache.put(k, v); - } - static Type[] getTypeCache(Type k) - { - return typecache.get(k); - } - private Object[] parameters = null; - public Container() {} - private void setup() - { - Field[] fs = getClass().getDeclaredFields(); - Object[] args = new Object[fs.length]; - - int diff = 0; - for (Field f : fs) { - Position p = f.getAnnotation(Position.class); - if (null == p) { - diff++; - continue; - } - try { - args[p.value()] = f.get(this); - } catch (IllegalAccessException IAe) {} - } - - this.parameters = new Object[args.length - diff]; - System.arraycopy(args, 0, parameters, 0, parameters.length); - } - /** - * Returns the struct contents in order. - * @throws DBusException If there is a problem doing this. - */ - public final Object[] getParameters() - { - if (null != parameters) return parameters; - setup(); - return parameters; - } - /** Returns this struct as a string. */ - public final String toString() - { - String s = getClass().getName()+"<"; - if (null == parameters) - setup(); - if (0 == parameters.length) - return s+">"; - for (Object o: parameters) - s += o+", "; - return s.replaceAll(", $", ">"); - } - public final boolean equals(Object other) - { - if (other instanceof Container) { - Container that = (Container) other; - if (this.getClass().equals(that.getClass())) - return Arrays.equals(this.getParameters(), that.getParameters()); - else return false; - } - else return false; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java b/app/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java deleted file mode 100644 index c3843812..00000000 --- a/app/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; - -import org.freedesktop.DBus.Error.NoReply; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; - -import cx.ath.matthew.debug.Debug; - -/** - * A handle to an asynchronous method call. - */ -public class DBusAsyncReply -{ - /** - * Check if any of a set of asynchronous calls have had a reply. - * @param replies A Collection of handles to replies to check. - * @return A Collection only containing those calls which have had replies. - */ - public static Collection> hasReply(Collection> replies) - { - Collection> c = new ArrayList>(replies); - Iterator> i = c.iterator(); - while (i.hasNext()) - if (!i.next().hasReply()) i.remove(); - return c; - } - - private ReturnType rval = null; - private DBusExecutionException error = null; - private MethodCall mc; - private Method me; - private AbstractConnection conn; - DBusAsyncReply(MethodCall mc, Method me, AbstractConnection conn) - { - this.mc = mc; - this.me = me; - this.conn = conn; - } - @SuppressWarnings("unchecked") - private synchronized void checkReply() - { - if (mc.hasReply()) { - Message m = mc.getReply(); - if (m instanceof Error) - error = ((Error) m).getException(); - else if (m instanceof MethodReturn) { - try { - rval = (ReturnType) RemoteInvocationHandler.convertRV(m.getSig(), m.getParameters(), me, conn); - } catch (DBusExecutionException DBEe) { - error = DBEe; - } catch (DBusException DBe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); - error = new DBusExecutionException(DBe.getMessage()); - } - } - } - } - - /** - * Check if we've had a reply. - * @return True if we have a reply - */ - public boolean hasReply() - { - if (null != rval || null != error) return true; - checkReply(); - return null != rval || null != error; - } - - /** - * Get the reply. - * @return The return value from the method. - * @throws DBusExecutionException if the reply to the method was an error. - * @throws NoReply if the method hasn't had a reply yet - */ - public ReturnType getReply() throws DBusExecutionException - { - if (null != rval) return rval; - else if (null != error) throw error; - checkReply(); - if (null != rval) return rval; - else if (null != error) throw error; - else throw new NoReply($_("Async call has not had a reply")); - } - - public String toString() - { - return $_("Waiting for: ")+mc; - } - Method getMethod() { return me; } - AbstractConnection getConnection() { return conn; } - MethodCall getCall() { return mc; } -} - diff --git a/app/src/main/java/org/freedesktop/dbus/DBusCallInfo.java b/app/src/main/java/org/freedesktop/dbus/DBusCallInfo.java deleted file mode 100644 index d34ef4ef..00000000 --- a/app/src/main/java/org/freedesktop/dbus/DBusCallInfo.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -/** - * Holds information on a method call - */ -public class DBusCallInfo -{ - /** - * Indicates the caller won't wait for a reply (and we won't send one). - */ - public static final int NO_REPLY = Message.Flags.NO_REPLY_EXPECTED; - public static final int ASYNC = 0x100; - private String source; - private String destination; - private String objectpath; - private String iface; - private String method; - private int flags; - DBusCallInfo(Message m) - { - this.source = m.getSource(); - this.destination = m.getDestination(); - this.objectpath = m.getPath(); - this.iface = m.getInterface(); - this.method = m.getName(); - this.flags = m.getFlags(); - } - - /** Returns the BusID which called the method */ - public String getSource() { return source; } - /** Returns the name with which we were addressed on the Bus */ - public String getDestination() { return destination; } - /** Returns the object path used to call this method */ - public String getObjectPath() { return objectpath; } - /** Returns the interface this method was called with */ - public String getInterface() { return iface; } - /** Returns the method name used to call this method */ - public String getMethod() { return method; } - /** Returns any flags set on this method call */ - public int getFlags() { return flags; } -} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusConnection.java b/app/src/main/java/org/freedesktop/dbus/DBusConnection.java deleted file mode 100644 index b07a764b..00000000 --- a/app/src/main/java/org/freedesktop/dbus/DBusConnection.java +++ /dev/null @@ -1,780 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.lang.reflect.Proxy; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; - -import java.text.MessageFormat; -import java.text.ParseException; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.Vector; - -import org.freedesktop.DBus; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; -import org.freedesktop.dbus.exceptions.NotConnected; - -import cx.ath.matthew.debug.Debug; - -/** Handles a connection to DBus. - *

- * This is a Singleton class, only 1 connection to the SYSTEM or SESSION busses can be made. - * Repeated calls to getConnection will return the same reference. - *

- *

- * Signal Handlers and method calls from remote objects are run in their own threads, you MUST handle the concurrency issues. - *

- */ -public class DBusConnection extends AbstractConnection -{ - /** - * Add addresses of peers to a set which will watch for them to - * disappear and automatically remove them from the set. - */ - public class PeerSet implements Set, DBusSigHandler - { - private Set addresses; - public PeerSet() - { - addresses = new TreeSet(); - try { - addSigHandler(new DBusMatchRule(DBus.NameOwnerChanged.class, null, null), this); - } catch (DBusException DBe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); - } - } - public void handle(DBus.NameOwnerChanged noc) - { - if (Debug.debug) - Debug.print(Debug.DEBUG, "Received NameOwnerChanged("+noc.name+","+noc.old_owner+","+noc.new_owner+")"); - if ("".equals(noc.new_owner) && addresses.contains(noc.name)) - remove(noc.name); - } - public boolean add(String address) - { - if (Debug.debug) - Debug.print(Debug.DEBUG, "Adding "+address); - synchronized (addresses) { - return addresses.add(address); - } - } - public boolean addAll(Collection addresses) - { - synchronized (this.addresses) { - return this.addresses.addAll(addresses); - } - } - public void clear() - { - synchronized (addresses) { - addresses.clear(); - } - } - public boolean contains(Object o) - { - return addresses.contains(o); - } - public boolean containsAll(Collection os) - { - return addresses.containsAll(os); - } - public boolean equals(Object o) - { - if (o instanceof PeerSet) - return ((PeerSet) o).addresses.equals(addresses); - else return false; - } - public int hashCode() - { - return addresses.hashCode(); - } - public boolean isEmpty() - { - return addresses.isEmpty(); - } - public Iterator iterator() - { - return addresses.iterator(); - } - public boolean remove(Object o) - { - if (Debug.debug) - Debug.print(Debug.DEBUG, "Removing "+o); - synchronized(addresses) { - return addresses.remove(o); - } - } - public boolean removeAll(Collection os) - { - synchronized(addresses) { - return addresses.removeAll(os); - } - } - public boolean retainAll(Collection os) - { - synchronized(addresses) { - return addresses.retainAll(os); - } - } - public int size() - { - return addresses.size(); - } - public Object[] toArray() - { - synchronized(addresses) { - return addresses.toArray(); - } - } - public T[] toArray(T[] a) - { - synchronized(addresses) { - return addresses.toArray(a); - } - } - } - private class _sighandler implements DBusSigHandler - { - public void handle(DBusSignal s) - { - if (s instanceof org.freedesktop.DBus.Local.Disconnected) { - if (Debug.debug) Debug.print(Debug.WARN, "Handling Disconnected signal from bus"); - try { - Error err = new Error( - "org.freedesktop.DBus.Local" , "org.freedesktop.DBus.Local.Disconnected", 0, "s", new Object[] { $_("Disconnected") }); - if (null != pendingCalls) synchronized (pendingCalls) { - long[] set = pendingCalls.getKeys(); - for (long l: set) if (-1 != l) { - MethodCall m = pendingCalls.remove(l); - if (null != m) - m.setReply(err); - } - } - synchronized (pendingErrors) { - pendingErrors.add(err); - } - } catch (DBusException DBe) {} - } else if (s instanceof org.freedesktop.DBus.NameAcquired) { - busnames.add(((org.freedesktop.DBus.NameAcquired) s).name); - } - } - } - - /** - * System Bus - */ - public static final int SYSTEM = 0; - /** - * Session Bus - */ - public static final int SESSION = 1; - - public static final String DEFAULT_SYSTEM_BUS_ADDRESS = "unix:path=/var/run/dbus/system_bus_socket"; - - private List busnames; - - private static final Map conn = new HashMap(); - private int _refcount = 0; - private Object _reflock = new Object(); - private DBus _dbus; - - /** - * Connect to the BUS. If a connection already exists to the specified Bus, a reference to it is returned. - * @param address The address of the bus to connect to - * @throws DBusException If there is a problem connecting to the Bus. - */ - public static DBusConnection getConnection(String address) throws DBusException - { - synchronized (conn) { - DBusConnection c = conn.get(address); - if (null != c) { - synchronized (c._reflock) { c._refcount++; } - return c; - } - else { - c = new DBusConnection(address); - conn.put(address, c); - return c; - } - } - } - /** - * Connect to the BUS. If a connection already exists to the specified Bus, a reference to it is returned. - * @param bustype The Bus to connect to. - * @see #SYSTEM - * @see #SESSION - * @throws DBusException If there is a problem connecting to the Bus. - */ - public static DBusConnection getConnection(int bustype) throws DBusException - { - synchronized (conn) { - String s = null; - switch (bustype) { - case SYSTEM: - s = System.getenv("DBUS_SYSTEM_BUS_ADDRESS"); - if (null == s) s = DEFAULT_SYSTEM_BUS_ADDRESS; - break; - case SESSION: - s = System.getenv("DBUS_SESSION_BUS_ADDRESS"); - if (null == s) { - // address gets stashed in $HOME/.dbus/session-bus/`dbus-uuidgen --get`-`sed 's/:\(.\)\..*/\1/' <<< $DISPLAY` - String display = System.getenv("DISPLAY"); - if (null == display) throw new DBusException($_("Cannot Resolve Session Bus Address")); - File uuidfile = new File("/var/lib/dbus/machine-id"); - if (!uuidfile.exists()) throw new DBusException($_("Cannot Resolve Session Bus Address")); - try { - BufferedReader r = new BufferedReader(new FileReader(uuidfile)); - String uuid = r.readLine(); - String homedir = System.getProperty("user.home"); - File addressfile = new File(homedir + "/.dbus/session-bus", - uuid + "-" + display.replaceAll(":([0-9]*)\\..*", "$1")); - if (!addressfile.exists()) throw new DBusException($_("Cannot Resolve Session Bus Address")); - r = new BufferedReader(new FileReader(addressfile)); - String l; - while (null != (l = r.readLine())) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Reading D-Bus session data: "+l); - if (l.matches("DBUS_SESSION_BUS_ADDRESS.*")) { - s = l.replaceAll("^[^=]*=", ""); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Parsing "+l+" to "+s); - } - } - if (null == s || "".equals(s)) throw new DBusException($_("Cannot Resolve Session Bus Address")); - if (Debug.debug) Debug.print(Debug.INFO, "Read bus address "+s+" from file "+addressfile.toString()); - } catch (Exception e) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - throw new DBusException($_("Cannot Resolve Session Bus Address")); - } - } - break; - default: - throw new DBusException($_("Invalid Bus Type: ")+bustype); - } - DBusConnection c = conn.get(s); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Getting bus connection for "+s+": "+c); - if (null != c) { - synchronized (c._reflock) { c._refcount++; } - return c; - } - else { - if (Debug.debug) Debug.print(Debug.DEBUG, "Creating new bus connection to: "+s); - c = new DBusConnection(s); - conn.put(s, c); - return c; - } - } - } - @SuppressWarnings("unchecked") - private DBusConnection(String address) throws DBusException - { - super(address); - busnames = new Vector(); - - synchronized (_reflock) { - _refcount = 1; - } - - try { - transport = new Transport(addr, AbstractConnection.TIMEOUT); - connected = true; - } catch (IOException IOe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); - disconnect(); - throw new DBusException($_("Failed to connect to bus ")+IOe.getMessage()); - } catch (ParseException Pe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Pe); - disconnect(); - throw new DBusException($_("Failed to connect to bus ")+Pe.getMessage()); - } - - // start listening for calls - listen(); - - // register disconnect handlers - DBusSigHandler h = new _sighandler(); - addSigHandlerWithoutMatch(org.freedesktop.DBus.Local.Disconnected.class, h); - addSigHandlerWithoutMatch(org.freedesktop.DBus.NameAcquired.class, h); - - // register ourselves - _dbus = getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); - try { - busnames.add(_dbus.Hello()); - } catch (DBusExecutionException DBEe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); - throw new DBusException(DBEe.getMessage()); - } - } - - @SuppressWarnings("unchecked") - DBusInterface dynamicProxy(String source, String path) throws DBusException - { - if (Debug.debug) Debug.print(Debug.INFO, "Introspecting "+path+" on "+source+" for dynamic proxy creation"); - try { - DBus.Introspectable intro = getRemoteObject(source, path, DBus.Introspectable.class); - String data = intro.Introspect(); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Got introspection data: "+data); - String[] tags = data.split("[<>]"); - Vector ifaces = new Vector(); - for (String tag: tags) { - if (tag.startsWith("interface")) { - ifaces.add(tag.replaceAll("^interface *name *= *['\"]([^'\"]*)['\"].*$", "$1")); - } - } - Vector> ifcs = new Vector>(); - for(String iface: ifaces) { - if (Debug.debug) Debug.print(Debug.DEBUG, "Trying interface "+iface); - int j = 0; - while (j >= 0) { - try { - Class ifclass = Class.forName(iface); - if (!ifcs.contains(ifclass)) - ifcs.add(ifclass); - break; - } catch (Exception e) {} - j = iface.lastIndexOf("."); - char[] cs = iface.toCharArray(); - if (j >= 0) { - cs[j] = '$'; - iface = String.valueOf(cs); - } - } - } - - if (ifcs.size() == 0) throw new DBusException($_("Could not find an interface to cast to")); - - RemoteObject ro = new RemoteObject(source, path, null, false); - DBusInterface newi = (DBusInterface) - Proxy.newProxyInstance(ifcs.get(0).getClassLoader(), - ifcs.toArray(new Class[0]), - new RemoteInvocationHandler(this, ro)); - importedObjects.put(newi, ro); - return newi; - } catch (Exception e) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - throw new DBusException(MessageFormat.format($_("Failed to create proxy object for {0} exported by {1}. Reason: {2}"), new Object[] { path, source, e.getMessage() })); - } - } - - DBusInterface getExportedObject(String source, String path) throws DBusException - { - ExportedObject o = null; - synchronized (exportedObjects) { - o = exportedObjects.get(path); - } - if (null != o && null == o.object.get()) { - unExportObject(path); - o = null; - } - if (null != o) return o.object.get(); - if (null == source) throw new DBusException($_("Not an object exported by this connection and no remote specified")); - return dynamicProxy(source, path); - } - - /** - * Release a bus name. - * Releases the name so that other people can use it - * @param busname The name to release. MUST be in dot-notation like "org.freedesktop.local" - * @throws DBusException If the busname is incorrectly formatted. - */ - public void releaseBusName(String busname) throws DBusException - { - if (!busname.matches(BUSNAME_REGEX)||busname.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid bus name")); - synchronized (this.busnames) { - UInt32 rv; - try { - rv = _dbus.ReleaseName(busname); - } catch (DBusExecutionException DBEe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); - throw new DBusException(DBEe.getMessage()); - } - this.busnames.remove(busname); - } - } - /** - * Request a bus name. - * Request the well known name that this should respond to on the Bus. - * @param busname The name to respond to. MUST be in dot-notation like "org.freedesktop.local" - * @throws DBusException If the register name failed, or our name already exists on the bus. - * or if busname is incorrectly formatted. - */ - public void requestBusName(String busname) throws DBusException - { - if (!busname.matches(BUSNAME_REGEX)||busname.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid bus name")); - synchronized (this.busnames) { - UInt32 rv; - try { - rv = _dbus.RequestName(busname, - new UInt32(DBus.DBUS_NAME_FLAG_REPLACE_EXISTING | - DBus.DBUS_NAME_FLAG_DO_NOT_QUEUE)); - } catch (DBusExecutionException DBEe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); - throw new DBusException(DBEe.getMessage()); - } - switch (rv.intValue()) { - case DBus.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: break; - case DBus.DBUS_REQUEST_NAME_REPLY_IN_QUEUE: throw new DBusException($_("Failed to register bus name")); - case DBus.DBUS_REQUEST_NAME_REPLY_EXISTS: throw new DBusException($_("Failed to register bus name")); - case DBus.DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: break; - default: break; - } - this.busnames.add(busname); - } - } - - /** - * Returns the unique name of this connection. - */ - public String getUniqueName() - { - return busnames.get(0); - } - /** - * Returns all the names owned by this connection. - */ - public String[] getNames() - { - Set names = new TreeSet(); - names.addAll(busnames); - return names.toArray(new String[0]); - } - public I getPeerRemoteObject(String busname, String objectpath, Class type) throws DBusException - { - return getPeerRemoteObject(busname, objectpath, type, true); - } - /** - * Return a reference to a remote object. - * This method will resolve the well known name (if given) to a unique bus name when you call it. - * This means that if a well known name is released by one process and acquired by another calls to - * objects gained from this method will continue to operate on the original process. - * - * This method will use bus introspection to determine the interfaces on a remote object and so - * may block and may fail. The resulting proxy object will, however, be castable - * to any interface it implements. It will also autostart the process if applicable. Also note - * that the resulting proxy may fail to execute the correct method with overloaded methods - * and that complex types may fail in interesting ways. Basically, if something odd happens, - * try specifying the interface explicitly. - * - * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local") - * or may be a DBus address such as ":1-16". - * @param objectpath The path on which the process is exporting the object.$ - * @return A reference to a remote object. - * @throws ClassCastException If type is not a sub-type of DBusInterface - * @throws DBusException If busname or objectpath are incorrectly formatted. - */ - public DBusInterface getPeerRemoteObject(String busname, String objectpath) throws DBusException - { - if (null == busname) throw new DBusException($_("Invalid bus name: null")); - - if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) - || busname.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid bus name: ")+busname); - - String unique = _dbus.GetNameOwner(busname); - - return dynamicProxy(unique, objectpath); - } - - /** - * Return a reference to a remote object. - * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. - * In particular this means that if a process providing the well known name disappears and is taken over by another process - * proxy objects gained by this method will make calls on the new proccess. - * - * This method will use bus introspection to determine the interfaces on a remote object and so - * may block and may fail. The resulting proxy object will, however, be castable - * to any interface it implements. It will also autostart the process if applicable. Also note - * that the resulting proxy may fail to execute the correct method with overloaded methods - * and that complex types may fail in interesting ways. Basically, if something odd happens, - * try specifying the interface explicitly. - * - * @param busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local") - * or may be a DBus address such as ":1-16". - * @param objectpath The path on which the process is exporting the object. - * @return A reference to a remote object. - * @throws ClassCastException If type is not a sub-type of DBusInterface - * @throws DBusException If busname or objectpath are incorrectly formatted. - */ - public DBusInterface getRemoteObject(String busname, String objectpath) throws DBusException - { - if (null == busname) throw new DBusException($_("Invalid bus name: null")); - if (null == objectpath) throw new DBusException($_("Invalid object path: null")); - - if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) - || busname.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid bus name: ")+busname); - - if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid object path: ")+objectpath); - - return dynamicProxy(busname, objectpath); - } - - /** - * Return a reference to a remote object. - * This method will resolve the well known name (if given) to a unique bus name when you call it. - * This means that if a well known name is released by one process and acquired by another calls to - * objects gained from this method will continue to operate on the original process. - * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local") - * or may be a DBus address such as ":1-16". - * @param objectpath The path on which the process is exporting the object.$ - * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures - * as the interface the remote object is exporting. - * @param autostart Disable/Enable auto-starting of services in response to calls on this object. - * Default is enabled; when calling a method with auto-start enabled, if the destination is a well-known name - * and is not owned the bus will attempt to start a process to take the name. When disabled an error is - * returned immediately. - * @return A reference to a remote object. - * @throws ClassCastException If type is not a sub-type of DBusInterface - * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. - */ - public I getPeerRemoteObject(String busname, String objectpath, Class type, boolean autostart) throws DBusException - { - if (null == busname) throw new DBusException($_("Invalid bus name: null")); - - if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) - || busname.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid bus name: ")+busname); - - String unique = _dbus.GetNameOwner(busname); - - return getRemoteObject(unique, objectpath, type, autostart); - } - /** - * Return a reference to a remote object. - * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. - * In particular this means that if a process providing the well known name disappears and is taken over by another process - * proxy objects gained by this method will make calls on the new proccess. - * @param busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local") - * or may be a DBus address such as ":1-16". - * @param objectpath The path on which the process is exporting the object. - * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures - * as the interface the remote object is exporting. - * @return A reference to a remote object. - * @throws ClassCastException If type is not a sub-type of DBusInterface - * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. - */ - public I getRemoteObject(String busname, String objectpath, Class type) throws DBusException - { - return getRemoteObject(busname, objectpath, type, true); - } - /** - * Return a reference to a remote object. - * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. - * In particular this means that if a process providing the well known name disappears and is taken over by another process - * proxy objects gained by this method will make calls on the new proccess. - * @param busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local") - * or may be a DBus address such as ":1-16". - * @param objectpath The path on which the process is exporting the object. - * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures - * as the interface the remote object is exporting. - * @param autostart Disable/Enable auto-starting of services in response to calls on this object. - * Default is enabled; when calling a method with auto-start enabled, if the destination is a well-known name - * and is not owned the bus will attempt to start a process to take the name. When disabled an error is - * returned immediately. - * @return A reference to a remote object. - * @throws ClassCastException If type is not a sub-type of DBusInterface - * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. - */ - @SuppressWarnings("unchecked") - public I getRemoteObject(String busname, String objectpath, Class type, boolean autostart) throws DBusException - { - if (null == busname) throw new DBusException($_("Invalid bus name: null")); - if (null == objectpath) throw new DBusException($_("Invalid object path: null")); - if (null == type) throw new ClassCastException($_("Not A DBus Interface")); - - if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) - || busname.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid bus name: ")+busname); - - if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid object path: ")+objectpath); - - if (!DBusInterface.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Interface")); - - // don't let people import things which don't have a - // valid D-Bus interface name - if (type.getName().equals(type.getSimpleName())) - throw new DBusException($_("DBusInterfaces cannot be declared outside a package")); - - RemoteObject ro = new RemoteObject(busname, objectpath, type, autostart); - I i = (I) Proxy.newProxyInstance(type.getClassLoader(), - new Class[] { type }, new RemoteInvocationHandler(this, ro)); - importedObjects.put(i, ro); - return i; - } - /** - * Remove a Signal Handler. - * Stops listening for this signal. - * @param type The signal to watch for. - * @param source The source of the signal. - * @throws DBusException If listening for the signal on the bus failed. - * @throws ClassCastException If type is not a sub-type of DBusSignal. - */ - public void removeSigHandler(Class type, String source, DBusSigHandler handler) throws DBusException - { - if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); - if (source.matches(BUSNAME_REGEX)) throw new DBusException($_("Cannot watch for signals based on well known bus name as source, only unique names.")); - if (!source.matches(CONNID_REGEX)||source.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid bus name: ")+source); - removeSigHandler(new DBusMatchRule(type, source, null), handler); - } - /** - * Remove a Signal Handler. - * Stops listening for this signal. - * @param type The signal to watch for. - * @param source The source of the signal. - * @param object The object emitting the signal. - * @throws DBusException If listening for the signal on the bus failed. - * @throws ClassCastException If type is not a sub-type of DBusSignal. - */ - public void removeSigHandler(Class type, String source, DBusInterface object, DBusSigHandler handler) throws DBusException - { - if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); - if (source.matches(BUSNAME_REGEX)) throw new DBusException($_("Cannot watch for signals based on well known bus name as source, only unique names.")); - if (!source.matches(CONNID_REGEX)||source.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid bus name: ")+source); - String objectpath = importedObjects.get(object).objectpath; - if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid object path: ")+objectpath); - removeSigHandler(new DBusMatchRule(type, source, objectpath), handler); - } - protected void removeSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException - { - - SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); - synchronized (handledSignals) { - Vector> v = handledSignals.get(key); - if (null != v) { - v.remove(handler); - if (0 == v.size()) { - handledSignals.remove(key); - try { - _dbus.RemoveMatch(rule.toString()); - } catch (NotConnected NC) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, NC); - } catch (DBusExecutionException DBEe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); - throw new DBusException(DBEe.getMessage()); - } - } - } - } - } - /** - * Add a Signal Handler. - * Adds a signal handler to call when a signal is received which matches the specified type, name and source. - * @param type The signal to watch for. - * @param source The process which will send the signal. This MUST be a unique bus name and not a well known name. - * @param handler The handler to call when a signal is received. - * @throws DBusException If listening for the signal on the bus failed. - * @throws ClassCastException If type is not a sub-type of DBusSignal. - */ - @SuppressWarnings("unchecked") - public void addSigHandler(Class type, String source, DBusSigHandler handler) throws DBusException - { - if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); - if (source.matches(BUSNAME_REGEX)) throw new DBusException($_("Cannot watch for signals based on well known bus name as source, only unique names.")); - if (!source.matches(CONNID_REGEX)||source.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid bus name: ")+source); - addSigHandler(new DBusMatchRule(type, source, null), (DBusSigHandler) handler); - } - /** - * Add a Signal Handler. - * Adds a signal handler to call when a signal is received which matches the specified type, name, source and object. - * @param type The signal to watch for. - * @param source The process which will send the signal. This MUST be a unique bus name and not a well known name. - * @param object The object from which the signal will be emitted - * @param handler The handler to call when a signal is received. - * @throws DBusException If listening for the signal on the bus failed. - * @throws ClassCastException If type is not a sub-type of DBusSignal. - */ - @SuppressWarnings("unchecked") - public void addSigHandler(Class type, String source, DBusInterface object, DBusSigHandler handler) throws DBusException - { - if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); - if (source.matches(BUSNAME_REGEX)) throw new DBusException($_("Cannot watch for signals based on well known bus name as source, only unique names.")); - if (!source.matches(CONNID_REGEX)||source.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid bus name: ")+source); - String objectpath = importedObjects.get(object).objectpath; - if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid object path: ")+objectpath); - addSigHandler(new DBusMatchRule(type, source, objectpath), (DBusSigHandler) handler); - } - protected void addSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException - { - try { - _dbus.AddMatch(rule.toString()); - } catch (DBusExecutionException DBEe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); - throw new DBusException(DBEe.getMessage()); - } - SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); - synchronized (handledSignals) { - Vector> v = handledSignals.get(key); - if (null == v) { - v = new Vector>(); - v.add(handler); - handledSignals.put(key, v); - } else - v.add(handler); - } - } - /** - * Disconnect from the Bus. - * This only disconnects when the last reference to the bus has disconnect called on it - * or has been destroyed. - */ - public void disconnect() - { - synchronized (conn) { - synchronized (_reflock) { - if (0 == --_refcount) { - if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting DBusConnection"); - // Set all pending messages to have an error. - try { - Error err = new Error( - "org.freedesktop.DBus.Local" , "org.freedesktop.DBus.Local.Disconnected", 0, "s", new Object[] { $_("Disconnected") }); - synchronized (pendingCalls) { - long[] set = pendingCalls.getKeys(); - for (long l: set) if (-1 != l) { - MethodCall m = pendingCalls.remove(l); - if (null != m) - m.setReply(err); - } - pendingCalls = null; - } - synchronized (pendingErrors) { - pendingErrors.add(err); - } - } catch (DBusException DBe) {} - - conn.remove(addr); - super.disconnect(); - } - } - } - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusInterface.java b/app/src/main/java/org/freedesktop/dbus/DBusInterface.java deleted file mode 100644 index 7b0d30af..00000000 --- a/app/src/main/java/org/freedesktop/dbus/DBusInterface.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; -/** - * Denotes a class as exportable or a remote interface which can be called. - *

- * Any interface which should be exported or imported should extend this - * interface. All public methods from that interface are exported/imported - * with the given method signatures. - *

- *

- * All method calls on exported objects are run in their own threads. - * Application writers are responsible for any concurrency issues. - *

- */ -public interface DBusInterface -{ - /** - * Returns true on remote objects. - * Local objects implementing this interface MUST return false. - */ - public boolean isRemote(); -} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java b/app/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java deleted file mode 100644 index 5400a929..00000000 --- a/app/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Force the interface name to be different to the Java class name. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface DBusInterfaceName -{ - /** The replacement interface name. */ - String value(); -} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusMap.java b/app/src/main/java/org/freedesktop/dbus/DBusMap.java deleted file mode 100644 index b9d06d72..00000000 --- a/app/src/main/java/org/freedesktop/dbus/DBusMap.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.Vector; - -class DBusMap implements Map -{ - Object[][] entries; - public DBusMap(Object[][] entries) - { - this.entries=entries; - } - class Entry implements Map.Entry, Comparable - { - private int entry; - public Entry(int i) - { - this.entry = i; - } - public boolean equals(Object o) - { - if (null == o) return false; - if (!(o instanceof DBusMap.Entry)) return false; - return this.entry == ((Entry) o).entry; - } - @SuppressWarnings("unchecked") - public K getKey() - { - return (K) entries[entry][0]; - } - @SuppressWarnings("unchecked") - public V getValue() - { - return (V) entries[entry][1]; - } - public int hashCode() - { - return entries[entry][0].hashCode(); - } - public V setValue(V value) - { - throw new UnsupportedOperationException(); - } - public int compareTo(Entry e) - { - return entry - e.entry; - } - } - - public void clear() - { - throw new UnsupportedOperationException(); - } - public boolean containsKey(Object key) - { - for (int i = 0; i < entries.length; i++) - if (key == entries[i][0] || (key != null && key.equals(entries[i][0]))) - return true; - return false; - } - public boolean containsValue(Object value) - { - for (int i = 0; i < entries.length; i++) - if (value == entries[i][1] || (value != null && value.equals(entries[i][1]))) - return true; - return false; - } - public Set> entrySet() - { - Set> s = new TreeSet>(); - for (int i = 0; i < entries.length; i++) - s.add(new Entry(i)); - return s; - } - @SuppressWarnings("unchecked") - public V get(Object key) - { - for (int i = 0; i < entries.length; i++) - if (key == entries[i][0] || (key != null && key.equals(entries[i][0]))) - return (V) entries[i][1]; - return null; - } - public boolean isEmpty() - { - return entries.length == 0; - } - @SuppressWarnings("unchecked") - public Set keySet() - { - Set s = new TreeSet(); - for (Object[] entry: entries) - s.add((K) entry[0]); - return s; - } - public V put(K key, V value) - { - throw new UnsupportedOperationException(); - } - public void putAll(Map t) - { - throw new UnsupportedOperationException(); - } - public V remove(Object key) - { - throw new UnsupportedOperationException(); - } - public int size() - { - return entries.length; - } - @SuppressWarnings("unchecked") - public Collection values() - { - List l = new Vector(); - for (Object[] entry: entries) - l.add((V) entry[1]); - return l; - } - public int hashCode() - { - return Arrays.deepHashCode(entries); - } - @SuppressWarnings("unchecked") - public boolean equals(Object o) - { - if (null == o) return false; - if (!(o instanceof Map)) return false; - return ((Map) o).entrySet().equals(entrySet()); - } - public String toString() - { - String s = "{ "; - for (int i = 0; i < entries.length; i++) - s += entries[i][0] + " => " + entries[i][1] + ","; - return s.replaceAll(".$", " }"); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusMatchRule.java b/app/src/main/java/org/freedesktop/dbus/DBusMatchRule.java deleted file mode 100644 index d75f5b66..00000000 --- a/app/src/main/java/org/freedesktop/dbus/DBusMatchRule.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; - -import java.util.HashMap; - -public class DBusMatchRule -{ - /* signal, error, method_call, method_reply */ - private String type; - private String iface; - private String member; - private String object; - private String source; - private static HashMap> signalTypeMap = - new HashMap>(); - static Class getCachedSignalType(String type) - { - return signalTypeMap.get(type); - } - public DBusMatchRule(String type, String iface, String member) - { - this.type = type; - this.iface = iface; - this.member = member; - } - public DBusMatchRule(DBusExecutionException e) throws DBusException - { - this(e.getClass()); - member = null; - type = "error"; - } - public DBusMatchRule(Message m) - { - iface = m.getInterface(); - member = m.getName(); - if (m instanceof DBusSignal) - type = "signal"; - else if (m instanceof Error) { - type = "error"; - member = null; - } - else if (m instanceof MethodCall) - type = "method_call"; - else if (m instanceof MethodReturn) - type = "method_reply"; - } - public DBusMatchRule(Class c, String method) throws DBusException - { - this(c); - member = method; - type = "method_call"; - } - public DBusMatchRule(Class c, String source, String object) throws DBusException - { - this(c); - this.source = source; - this.object = object; - } - @SuppressWarnings("unchecked") - public DBusMatchRule(Class c) throws DBusException - { - if (DBusInterface.class.isAssignableFrom(c)) { - if (null != c.getAnnotation(DBusInterfaceName.class)) - iface = c.getAnnotation(DBusInterfaceName.class).value(); - else - iface = AbstractConnection.dollar_pattern.matcher(c.getName()).replaceAll("."); - if (!iface.matches(".*\\..*")) - throw new DBusException($_("DBusInterfaces must be defined in a package.")); - member = null; - type = null; - } - else if (DBusSignal.class.isAssignableFrom(c)) { - if (null == c.getEnclosingClass()) - throw new DBusException($_("Signals must be declared as a member of a class implementing DBusInterface which is the member of a package.")); - else - if (null != c.getEnclosingClass().getAnnotation(DBusInterfaceName.class)) - iface = c.getEnclosingClass().getAnnotation(DBusInterfaceName.class).value(); - else - iface = AbstractConnection.dollar_pattern.matcher(c.getEnclosingClass().getName()).replaceAll("."); - // Don't export things which are invalid D-Bus interfaces - if (!iface.matches(".*\\..*")) - throw new DBusException($_("DBusInterfaces must be defined in a package.")); - if (c.isAnnotationPresent(DBusMemberName.class)) - member = c.getAnnotation(DBusMemberName.class).value(); - else - member = c.getSimpleName(); - signalTypeMap.put(iface+'$'+member, (Class) c); - type = "signal"; - } - else if (Error.class.isAssignableFrom(c)) { - if (null != c.getAnnotation(DBusInterfaceName.class)) - iface = c.getAnnotation(DBusInterfaceName.class).value(); - else - iface = AbstractConnection.dollar_pattern.matcher(c.getName()).replaceAll("."); - if (!iface.matches(".*\\..*")) - throw new DBusException($_("DBusInterfaces must be defined in a package.")); - member = null; - type = "error"; - } - else if (DBusExecutionException.class.isAssignableFrom(c)) { - if (null != c.getClass().getAnnotation(DBusInterfaceName.class)) - iface = c.getClass().getAnnotation(DBusInterfaceName.class).value(); - else - iface = AbstractConnection.dollar_pattern.matcher(c.getClass().getName()).replaceAll("."); - if (!iface.matches(".*\\..*")) - throw new DBusException($_("DBusInterfaces must be defined in a package.")); - member = null; - type = "error"; - } - else - throw new DBusException($_("Invalid type for match rule: ")+c); - } - public String toString() - { - String s = null; - if (null != type) s = null == s ? "type='"+type+"'" : s + ",type='"+type+"'"; - if (null != member) s = null == s ? "member='"+member+"'" : s + ",member='"+member+"'"; - if (null != iface) s = null == s ? "interface='"+iface+"'" : s + ",interface='"+iface+"'"; - if (null != source) s = null == s ? "sender='"+source+"'" : s + ",sender='"+source+"'"; - if (null != object) s = null == s ? "path='"+object+"'" : s + ",path='"+object+"'"; - return s; - } - public String getType() { return type; } - public String getInterface() { return iface; } - public String getMember() { return member; } - public String getSource() { return source; } - public String getObject() { return object; } - -} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusMemberName.java b/app/src/main/java/org/freedesktop/dbus/DBusMemberName.java deleted file mode 100644 index da7f8fd2..00000000 --- a/app/src/main/java/org/freedesktop/dbus/DBusMemberName.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Force the member (method/signal) name on the bus to be different to the Java name. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE,ElementType.METHOD}) -public @interface DBusMemberName -{ - /** The replacement member name. */ - String value(); -} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusSerializable.java b/app/src/main/java/org/freedesktop/dbus/DBusSerializable.java deleted file mode 100644 index 8e311375..00000000 --- a/app/src/main/java/org/freedesktop/dbus/DBusSerializable.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; -import org.freedesktop.dbus.exceptions.DBusException; -/** - * Custom classes may be sent over DBus if they implement this interface. - *

- * In addition to the serialize method, classes MUST implement - * a deserialize method which returns null and takes as it's arguments - * all the DBus types the class will be serialied to in order and - * with type parameterisation. They MUST also provide a - * zero-argument constructor. - *

- *

- * The serialize method should return the class properties you wish to - * serialize, correctly formatted for the wire - * (DBusConnection.convertParameters() can help with this), in order in an - * Object array. - *

- *

- * The deserialize method will be called once after the zero-argument - * constructor. This should contain all the code to initialise the object - * from the types. - *

- */ -public interface DBusSerializable -{ - public Object[] serialize() throws DBusException; -} - diff --git a/app/src/main/java/org/freedesktop/dbus/DBusSigHandler.java b/app/src/main/java/org/freedesktop/dbus/DBusSigHandler.java deleted file mode 100644 index a56d8451..00000000 --- a/app/src/main/java/org/freedesktop/dbus/DBusSigHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; -/** Handle a signal on DBus. - * All Signal handlers are run in their own Thread. - * Application writers are responsible for managing any concurrency issues. - */ -public interface DBusSigHandler -{ - /** - * Handle a signal. - * @param s The signal to handle. If such a class exists, the - * signal will be an instance of the class with the correct type signature. - * Otherwise it will be an instance of DBusSignal - */ - public void handle(T s); -} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusSignal.java b/app/src/main/java/org/freedesktop/dbus/DBusSignal.java deleted file mode 100644 index 424d5bdd..00000000 --- a/app/src/main/java/org/freedesktop/dbus/DBusSignal.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.lang.reflect.Constructor; -import java.lang.reflect.GenericDeclaration; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Vector; - -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.MessageFormatException; - -import cx.ath.matthew.debug.Debug; - -public class DBusSignal extends Message -{ - DBusSignal() { } - public DBusSignal(String source, String path, String iface, String member, String sig, Object... args) throws DBusException - { - super(Message.Endian.BIG, Message.MessageType.SIGNAL, (byte) 0); - - if (null == path || null == member || null == iface) - throw new MessageFormatException($_("Must specify object path, interface and signal name to Signals.")); - headers.put(Message.HeaderField.PATH,path); - headers.put(Message.HeaderField.MEMBER,member); - headers.put(Message.HeaderField.INTERFACE,iface); - - Vector hargs = new Vector(); - hargs.add(new Object[] { Message.HeaderField.PATH, new Object[] { ArgumentType.OBJECT_PATH_STRING, path } }); - hargs.add(new Object[] { Message.HeaderField.INTERFACE, new Object[] { ArgumentType.STRING_STRING, iface } }); - hargs.add(new Object[] { Message.HeaderField.MEMBER, new Object[] { ArgumentType.STRING_STRING, member } }); - - if (null != source) { - headers.put(Message.HeaderField.SENDER,source); - hargs.add(new Object[] { Message.HeaderField.SENDER, new Object[] { ArgumentType.STRING_STRING, source } }); - } - - if (null != sig) { - hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); - headers.put(Message.HeaderField.SIGNATURE,sig); - setArgs(args); - } - - blen = new byte[4]; - appendBytes(blen); - append("ua(yv)", ++serial, hargs.toArray()); - pad((byte)8); - - long c = bytecounter; - if (null != sig) append(sig, args); - marshallint(bytecounter-c, blen, 0, 4); - bodydone = true; - } - static class internalsig extends DBusSignal - { - public internalsig(String source, String objectpath, String type, String name, String sig, Object[] parameters, long serial) throws DBusException - { - super(source, objectpath, type, name, sig, parameters, serial); - } - } - private static Map, Type[]> typeCache = new HashMap, Type[]>(); - private static Map> classCache = new HashMap>(); - private static Map, Constructor> conCache = new HashMap, Constructor>(); - private static Map signames = new HashMap(); - private static Map intnames = new HashMap(); - private Class c; - private boolean bodydone = false; - private byte[] blen; - - static void addInterfaceMap(String java, String dbus) - { - intnames.put(dbus, java); - } - static void addSignalMap(String java, String dbus) - { - signames.put(dbus, java); - } - - static DBusSignal createSignal(Class c, String source, String objectpath, String sig, long serial, Object... parameters) throws DBusException - { - String type = ""; - if (null != c.getEnclosingClass()) { - if (null != c.getEnclosingClass().getAnnotation(DBusInterfaceName.class)) - type = c.getEnclosingClass().getAnnotation(DBusInterfaceName.class).value(); - else - type = AbstractConnection.dollar_pattern.matcher(c.getEnclosingClass().getName()).replaceAll("."); - - } else - throw new DBusException($_("Signals must be declared as a member of a class implementing DBusInterface which is the member of a package.")); - DBusSignal s = new internalsig(source, objectpath, type, c.getSimpleName(), sig, parameters, serial); - s.c = c; - return s; - } - @SuppressWarnings("unchecked") - private static Class createSignalClass(String intname, String signame) throws DBusException - { - String name = intname+'$'+signame; - Class c = classCache.get(name); - if (null == c) c = DBusMatchRule.getCachedSignalType(name); - if (null != c) return c; - do { - try { - c = (Class) Class.forName(name); - } catch (ClassNotFoundException CNFe) {} - name = name.replaceAll("\\.([^\\.]*)$", "\\$$1"); - } while (null == c && name.matches(".*\\..*")); - if (null == c) - throw new DBusException($_("Could not create class from signal ")+intname+'.'+signame); - classCache.put(name, c); - return c; - } - @SuppressWarnings("unchecked") - DBusSignal createReal(AbstractConnection conn) throws DBusException - { - String intname = intnames.get(getInterface()); - String signame = signames.get(getName()); - if (null == intname) intname = getInterface(); - if (null == signame) signame = getName(); - if (null == c) - c = createSignalClass(intname,signame); - if (Debug.debug) Debug.print(Debug.DEBUG, "Converting signal to type: "+c); - Type[] types = typeCache.get(c); - Constructor con = conCache.get(c); - if (null == types) { - con = (Constructor) c.getDeclaredConstructors()[0]; - conCache.put(c, con); - Type[] ts = con.getGenericParameterTypes(); - types = new Type[ts.length-1]; - for (int i = 1; i < ts.length; i++) - if (ts[i] instanceof TypeVariable) - for (Type b: ((TypeVariable) ts[i]).getBounds()) - types[i-1] = b; - else - types[i-1] = ts[i]; - typeCache.put(c, types); - } - - try { - DBusSignal s; - Object[] args = Marshalling.deSerializeParameters(getParameters(), types, conn); - if (null == args) s = (DBusSignal) con.newInstance(getPath()); - else { - Object[] params = new Object[args.length + 1]; - params[0] = getPath(); - System.arraycopy(args, 0, params, 1, args.length); - - if (Debug.debug) Debug.print(Debug.DEBUG, "Creating signal of type "+c+" with parameters "+Arrays.deepToString(params)); - s = (DBusSignal) con.newInstance(params); - } - s.headers = headers; - s.wiredata = wiredata; - s.bytecounter = wiredata.length; - return s; - } catch (Exception e) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - throw new DBusException(e.getMessage()); - } - } - /** - * Create a new signal. - * This contructor MUST be called by all sub classes. - * @param objectpath The path to the object this is emitted from. - * @param args The parameters of the signal. - * @throws DBusException This is thrown if the subclass is incorrectly defined. - */ - @SuppressWarnings("unchecked") - protected DBusSignal(String objectpath, Object... args) throws DBusException - { - super(Message.Endian.BIG, Message.MessageType.SIGNAL, (byte) 0); - - if (!objectpath.matches(AbstractConnection.OBJECT_REGEX)) throw new DBusException($_("Invalid object path: ")+objectpath); - - Class tc = getClass(); - String member; - if (tc.isAnnotationPresent(DBusMemberName.class)) - member = tc.getAnnotation(DBusMemberName.class).value(); - else - member = tc.getSimpleName(); - String iface = null; - Class enc = tc.getEnclosingClass(); - if (null == enc || - !DBusInterface.class.isAssignableFrom(enc) || - enc.getName().equals(enc.getSimpleName())) - throw new DBusException($_("Signals must be declared as a member of a class implementing DBusInterface which is the member of a package.")); - else - if (null != enc.getAnnotation(DBusInterfaceName.class)) - iface = enc.getAnnotation(DBusInterfaceName.class).value(); - else - iface = AbstractConnection.dollar_pattern.matcher(enc.getName()).replaceAll("."); - - headers.put(Message.HeaderField.PATH,objectpath); - headers.put(Message.HeaderField.MEMBER,member); - headers.put(Message.HeaderField.INTERFACE,iface); - - Vector hargs = new Vector(); - hargs.add(new Object[] { Message.HeaderField.PATH, new Object[] { ArgumentType.OBJECT_PATH_STRING, objectpath } }); - hargs.add(new Object[] { Message.HeaderField.INTERFACE, new Object[] { ArgumentType.STRING_STRING, iface } }); - hargs.add(new Object[] { Message.HeaderField.MEMBER, new Object[] { ArgumentType.STRING_STRING, member } }); - - String sig = null; - if (0 < args.length) { - try { - Type[] types = typeCache.get(tc); - if (null == types) { - Constructor con = (Constructor) tc.getDeclaredConstructors()[0]; - conCache.put(tc, con); - Type[] ts = con.getGenericParameterTypes(); - types = new Type[ts.length-1]; - for (int i = 1; i <= types.length; i++) - if (ts[i] instanceof TypeVariable) - types[i-1] = ((TypeVariable) ts[i]).getBounds()[0]; - else - types[i-1] = ts[i]; - typeCache.put(tc, types); - } - sig = Marshalling.getDBusType(types); - hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); - headers.put(Message.HeaderField.SIGNATURE,sig); - setArgs(args); - } catch (Exception e) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - throw new DBusException($_("Failed to add signal parameters: ")+e.getMessage()); - } - } - - blen = new byte[4]; - appendBytes(blen); - append("ua(yv)", ++serial, hargs.toArray()); - pad((byte)8); - } - void appendbody(AbstractConnection conn) throws DBusException - { - if (bodydone) return; - - Type[] types = typeCache.get(getClass()); - Object[] args = Marshalling.convertParameters(getParameters(), types, conn); - setArgs(args); - String sig = getSig(); - - long c = bytecounter; - if (null != args && 0 < args.length) append(sig, args); - marshallint(bytecounter-c, blen, 0, 4); - bodydone = true; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/DirectConnection.java b/app/src/main/java/org/freedesktop/dbus/DirectConnection.java deleted file mode 100644 index 96bce7b7..00000000 --- a/app/src/main/java/org/freedesktop/dbus/DirectConnection.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.lang.reflect.Proxy; -import java.io.File; -import java.io.IOException; -import java.net.ServerSocket; -import java.text.MessageFormat; -import java.text.ParseException; -import java.util.Random; -import java.util.Vector; - -import org.freedesktop.DBus; -import org.freedesktop.dbus.exceptions.DBusException; - -import cx.ath.matthew.debug.Debug; - -/** Handles a peer to peer connection between two applications withou a bus daemon. - *

- * Signal Handlers and method calls from remote objects are run in their own threads, you MUST handle the concurrency issues. - *

- */ -public class DirectConnection extends AbstractConnection -{ - /** - * Create a direct connection to another application. - * @param address The address to connect to. This is a standard D-Bus address, except that the additional parameter 'listen=true' should be added in the application which is creating the socket. - */ - public DirectConnection(String address) throws DBusException - { - super(address); - - try { - transport = new Transport(addr, AbstractConnection.TIMEOUT); - connected = true; - } catch (IOException IOe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); - throw new DBusException($_("Failed to connect to bus ")+IOe.getMessage()); - } catch (ParseException Pe) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Pe); - throw new DBusException($_("Failed to connect to bus ")+Pe.getMessage()); - } - - listen(); - } - - /** - * Creates a bus address for a randomly generated tcp port. - * @return a random bus address. - */ - public static String createDynamicTCPSession() - { - String address = "tcp:host=localhost"; - int port; - try { - ServerSocket s = new ServerSocket(); - s.bind(null); - port = s.getLocalPort(); - s.close(); - } catch (Exception e) { - Random r = new Random(); - port = 32768 + (Math.abs(r.nextInt()) % 28232); - } - address += ",port="+port; - address += ",guid="+Transport.genGUID(); - if (Debug.debug) Debug.print("Created Session address: "+address); - return address; - } - - /** - * Creates a bus address for a randomly generated abstract unix socket. - * @return a random bus address. - */ - public static String createDynamicSession() - { - String address = "unix:"; - String path = "/tmp/dbus-XXXXXXXXXX"; - Random r = new Random(); - do { - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < 10; i++) - sb.append((char) ((Math.abs(r.nextInt()) % 26) + 65)); - path = path.replaceAll("..........$", sb.toString()); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Trying path "+path); - } while ((new File(path)).exists()); - address += "abstract="+path; - address += ",guid="+Transport.genGUID(); - if (Debug.debug) Debug.print("Created Session address: "+address); - return address; - } - DBusInterface dynamicProxy(String path) throws DBusException - { - try { - DBus.Introspectable intro = (DBus.Introspectable) getRemoteObject(path, DBus.Introspectable.class); - String data = intro.Introspect(); - String[] tags = data.split("[<>]"); - Vector ifaces = new Vector(); - for (String tag: tags) { - if (tag.startsWith("interface")) { - ifaces.add(tag.replaceAll("^interface *name *= *['\"]([^'\"]*)['\"].*$", "$1")); - } - } - Vector> ifcs = new Vector>(); - for(String iface: ifaces) { - int j = 0; - while (j >= 0) { - try { - ifcs.add(Class.forName(iface)); - break; - } catch (Exception e) {} - j = iface.lastIndexOf("."); - char[] cs = iface.toCharArray(); - if (j >= 0) { - cs[j] = '$'; - iface = String.valueOf(cs); - } - } - } - - if (ifcs.size() == 0) throw new DBusException($_("Could not find an interface to cast to")); - - RemoteObject ro = new RemoteObject(null, path, null, false); - DBusInterface newi = (DBusInterface) - Proxy.newProxyInstance(ifcs.get(0).getClassLoader(), - ifcs.toArray(new Class[0]), - new RemoteInvocationHandler(this, ro)); - importedObjects.put(newi, ro); - return newi; - } catch (Exception e) { - if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - throw new DBusException(MessageFormat.format($_("Failed to create proxy object for {0}; reason: {1}."), new Object[] { path, e.getMessage()})); - } - } - - DBusInterface getExportedObject(String path) throws DBusException - { - ExportedObject o = null; - synchronized (exportedObjects) { - o = exportedObjects.get(path); - } - if (null != o && null == o.object.get()) { - unExportObject(path); - o = null; - } - if (null != o) return o.object.get(); - return dynamicProxy(path); - } - - /** - * Return a reference to a remote object. - * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. - * In particular this means that if a process providing the well known name disappears and is taken over by another process - * proxy objects gained by this method will make calls on the new proccess. - * - * This method will use bus introspection to determine the interfaces on a remote object and so - * may block and may fail. The resulting proxy object will, however, be castable - * to any interface it implements. It will also autostart the process if applicable. Also note - * that the resulting proxy may fail to execute the correct method with overloaded methods - * and that complex types may fail in interesting ways. Basically, if something odd happens, - * try specifying the interface explicitly. - * - * @param objectpath The path on which the process is exporting the object. - * @return A reference to a remote object. - * @throws ClassCastException If type is not a sub-type of DBusInterface - * @throws DBusException If busname or objectpath are incorrectly formatted. - */ - public DBusInterface getRemoteObject(String objectpath) throws DBusException - { - if (null == objectpath) throw new DBusException($_("Invalid object path: null")); - - if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid object path: ")+objectpath); - - return dynamicProxy(objectpath); - } - - /** - * Return a reference to a remote object. - * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. - * In particular this means that if a process providing the well known name disappears and is taken over by another process - * proxy objects gained by this method will make calls on the new proccess. - * @param objectpath The path on which the process is exporting the object. - * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures - * as the interface the remote object is exporting. - * @return A reference to a remote object. - * @throws ClassCastException If type is not a sub-type of DBusInterface - * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. - */ - public DBusInterface getRemoteObject(String objectpath, Class type) throws DBusException - { - if (null == objectpath) throw new DBusException($_("Invalid object path: null")); - if (null == type) throw new ClassCastException($_("Not A DBus Interface")); - - if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) - throw new DBusException($_("Invalid object path: ")+objectpath); - - if (!DBusInterface.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Interface")); - - // don't let people import things which don't have a - // valid D-Bus interface name - if (type.getName().equals(type.getSimpleName())) - throw new DBusException($_("DBusInterfaces cannot be declared outside a package")); - - RemoteObject ro = new RemoteObject(null, objectpath, type, false); - DBusInterface i = (DBusInterface) Proxy.newProxyInstance(type.getClassLoader(), - new Class[] { type }, new RemoteInvocationHandler(this, ro)); - importedObjects.put(i, ro); - return i; - } - protected void removeSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException - { - SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); - synchronized (handledSignals) { - Vector> v = handledSignals.get(key); - if (null != v) { - v.remove(handler); - if (0 == v.size()) { - handledSignals.remove(key); - } - } - } - } - protected void addSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException - { - SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); - synchronized (handledSignals) { - Vector> v = handledSignals.get(key); - if (null == v) { - v = new Vector>(); - v.add(handler); - handledSignals.put(key, v); - } else - v.add(handler); - } - } - DBusInterface getExportedObject(String source, String path) throws DBusException - { - return getExportedObject(path); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/EfficientMap.java b/app/src/main/java/org/freedesktop/dbus/EfficientMap.java deleted file mode 100644 index 67a24848..00000000 --- a/app/src/main/java/org/freedesktop/dbus/EfficientMap.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -/** - * Provides a long => MethodCall map which doesn't allocate objects - * on insertion/removal. Keys must be inserted in ascending order. */ -class EfficientMap -{ - private long[] kv; - private MethodCall[] vv; - private int start; - private int end; - private int init_size; - public EfficientMap(int initial_size) - { - init_size = initial_size; - shrink(); - } - private void grow() - { - // create new vectors twice as long - long[] oldkv = kv; - kv = new long[oldkv.length*2]; - MethodCall[] oldvv = vv; - vv = new MethodCall[oldvv.length*2]; - - // copy start->length to the start of the new vector - System.arraycopy(oldkv,start,kv,0,oldkv.length-start); - System.arraycopy(oldvv,start,vv,0,oldvv.length-start); - // copy 0->end to the next part of the new vector - if (end != (oldkv.length-1)) { - System.arraycopy(oldkv,0,kv,oldkv.length-start,end+1); - System.arraycopy(oldvv,0,vv,oldvv.length-start,end+1); - } - // reposition pointers - start = 0; - end = oldkv.length; - } - // create a new vector with just the valid keys in and return it - public long[] getKeys() - { - int size; - if (start < end) size = end-start; - else size = kv.length-(start-end); - long[] lv = new long[size]; - int copya; - if (size > kv.length-start) copya = kv.length-start; - else copya = size; - System.arraycopy(kv,start,lv,0,copya); - if (copya < size) { - System.arraycopy(kv,0,lv,copya,size-copya); - } - return lv; - } - private void shrink() - { - if (null != kv && kv.length == init_size) return; - // reset to original size - kv = new long[init_size]; - vv = new MethodCall[init_size]; - start = 0; - end = 0; - } - public void put(long l, MethodCall m) - { - // put this at the end - kv[end] = l; - vv[end] = m; - // move the end - if (end == (kv.length-1)) end = 0; else end++; - // if we are out of space, grow. - if (end == start) grow(); - } - public MethodCall remove(long l) - { - // find the item - int pos = find(l); - // if we don't have it return null - if (-1 == pos) return null; - // get the value - MethodCall m = vv[pos]; - // set it as unused - vv[pos] = null; - kv[pos] = -1; - // move the pointer to the first full element - while (-1 == kv[start]) { - if (start == (kv.length-1)) start = 0; else start++; - // if we have emptied the list, shrink it - if (start == end) { shrink(); break; } - } - return m; - } - public boolean contains(long l) - { - // check if find succeeds - return -1 != find(l); - } - /* could binary search, but it's probably the first one */ - private int find(long l) - { - int i = start; - while (i != end && kv[i] != l) - if (i == (kv.length-1)) i = 0; else i++; - if (i == end) return -1; - return i; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/EfficientQueue.java b/app/src/main/java/org/freedesktop/dbus/EfficientQueue.java deleted file mode 100644 index 5724730f..00000000 --- a/app/src/main/java/org/freedesktop/dbus/EfficientQueue.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import cx.ath.matthew.debug.Debug; - -/** - * Provides a Message queue which doesn't allocate objects - * on insertion/removal. */ -class EfficientQueue -{ - private Message[] mv; - private int start; - private int end; - private int init_size; - public EfficientQueue(int initial_size) - { - init_size = initial_size; - shrink(); - } - private void grow() - { - if (Debug.debug) Debug.print(Debug.DEBUG, "Growing"); - // create new vectors twice as long - Message[] oldmv = mv; - mv = new Message[oldmv.length*2]; - - // copy start->length to the start of the new vector - System.arraycopy(oldmv,start,mv,0,oldmv.length-start); - // copy 0->end to the next part of the new vector - if (end != (oldmv.length-1)) { - System.arraycopy(oldmv,0,mv,oldmv.length-start,end+1); - } - // reposition pointers - start = 0; - end = oldmv.length; - } - // create a new vector with just the valid keys in and return it - public Message[] getKeys() - { - if (start == end) return new Message[0]; - Message[] lv; - if (start < end) { - int size = end-start; - lv = new Message[size]; - System.arraycopy(mv, start, lv, 0, size); - } else { - int size = mv.length-start+end; - lv = new Message[size]; - System.arraycopy(mv, start, lv, 0, mv.length-start); - System.arraycopy(mv, 0, lv, mv.length-start, end); - } - return lv; - } - private void shrink() - { - if (Debug.debug) Debug.print(Debug.DEBUG, "Shrinking"); - if (null != mv && mv.length == init_size) return; - // reset to original size - mv = new Message[init_size]; - start = 0; - end = 0; - } - public void add(Message m) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "Enqueueing Message "+m); - // put this at the end - mv[end] = m; - // move the end - if (end == (mv.length-1)) end = 0; else end++; - // if we are out of space, grow. - if (end == start) grow(); - } - public Message remove() - { - if (start == end) return null; - // find the item - int pos = start; - // get the value - Message m = mv[pos]; - // set it as unused - mv[pos] = null; - if (start == (mv.length-1)) start = 0; else start++; - if (Debug.debug) Debug.print(Debug.DEBUG, "Dequeueing "+m); - return m; - } - public boolean isEmpty() - { - // check if find succeeds - return start == end; - } - public int size() - { - if (end >= start) - return end-start; - else - return mv.length-start+end; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/Error.java b/app/src/main/java/org/freedesktop/dbus/Error.java deleted file mode 100644 index 93f14af6..00000000 --- a/app/src/main/java/org/freedesktop/dbus/Error.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.lang.reflect.Constructor; -import java.util.Vector; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; -import org.freedesktop.dbus.exceptions.MessageFormatException; -import org.freedesktop.dbus.exceptions.NotConnected; - -import cx.ath.matthew.debug.Debug; - -/** - * Error messages which can be sent over the bus. - */ -public class Error extends Message -{ - Error() { } - public Error(String dest, String errorName, long replyserial, String sig, Object... args) throws DBusException - { - this(null, dest, errorName, replyserial, sig, args); - } - public Error(String source, String dest, String errorName, long replyserial, String sig, Object... args) throws DBusException - { - super(Message.Endian.BIG, Message.MessageType.ERROR, (byte) 0); - - if (null == errorName) - throw new MessageFormatException($_("Must specify error name to Errors.")); - headers.put(Message.HeaderField.REPLY_SERIAL,replyserial); - headers.put(Message.HeaderField.ERROR_NAME,errorName); - - Vector hargs = new Vector(); - hargs.add(new Object[] { Message.HeaderField.ERROR_NAME, new Object[] { ArgumentType.STRING_STRING, errorName } }); - hargs.add(new Object[] { Message.HeaderField.REPLY_SERIAL, new Object[] { ArgumentType.UINT32_STRING, replyserial } }); - - if (null != source) { - headers.put(Message.HeaderField.SENDER,source); - hargs.add(new Object[] { Message.HeaderField.SENDER, new Object[] { ArgumentType.STRING_STRING, source } }); - } - - if (null != dest) { - headers.put(Message.HeaderField.DESTINATION,dest); - hargs.add(new Object[] { Message.HeaderField.DESTINATION, new Object[] { ArgumentType.STRING_STRING, dest } }); - } - - if (null != sig) { - hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); - headers.put(Message.HeaderField.SIGNATURE,sig); - setArgs(args); - } - - byte[] blen = new byte[4]; - appendBytes(blen); - append("ua(yv)", serial, hargs.toArray()); - pad((byte)8); - - long c = bytecounter; - if (null != sig) append(sig, args); - marshallint(bytecounter-c, blen, 0, 4); - } - public Error(String source, Message m, Throwable e) throws DBusException - { - this(source, m.getSource(), AbstractConnection.dollar_pattern.matcher(e.getClass().getName()).replaceAll("."), m.getSerial(), "s", e.getMessage()); - } - public Error(Message m, Throwable e) throws DBusException - { - this(m.getSource(), AbstractConnection.dollar_pattern.matcher(e.getClass().getName()).replaceAll("."), m.getSerial(), "s", e.getMessage()); - } - @SuppressWarnings("unchecked") - private static Class createExceptionClass(String name) - { - if (name == "org.freedesktop.DBus.Local.Disconnected") return NotConnected.class; - Class c = null; - do { - try { - c = (Class) Class.forName(name); - } catch (ClassNotFoundException CNFe) {} - name = name.replaceAll("\\.([^\\.]*)$", "\\$$1"); - } while (null == c && name.matches(".*\\..*")); - return c; - } - /** - * Turns this into an exception of the correct type - */ - public DBusExecutionException getException() - { - try { - Class c = createExceptionClass(getName()); - if (null == c || !DBusExecutionException.class.isAssignableFrom(c)) c = DBusExecutionException.class; - Constructor con = c.getConstructor(String.class); - DBusExecutionException ex; - Object[] args = getParameters(); - if (null == args || 0 == args.length) - ex = con.newInstance(""); - else { - String s = ""; - for (Object o: args) - s += o + " "; - ex = con.newInstance(s.trim()); - } - ex.setType(getName()); - return ex; - } catch (Exception e) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug && null != e.getCause()) - Debug.print(Debug.ERR, e.getCause()); - DBusExecutionException ex; - Object[] args = null; - try { - args = getParameters(); - } catch (Exception ee) {} - if (null == args || 0 == args.length) - ex = new DBusExecutionException(""); - else { - String s = ""; - for (Object o: args) - s += o + " "; - ex = new DBusExecutionException(s.trim()); - } - ex.setType(getName()); - return ex; - } - } - /** - * Throw this as an exception of the correct type - */ - public void throwException() throws DBusExecutionException - { - throw getException(); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/ExportedObject.java b/app/src/main/java/org/freedesktop/dbus/ExportedObject.java deleted file mode 100644 index ed84add0..00000000 --- a/app/src/main/java/org/freedesktop/dbus/ExportedObject.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.Map; - -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; - -class ExportedObject -{ - @SuppressWarnings("unchecked") - private String getAnnotations(AnnotatedElement c) - { - String ans = ""; - for (Annotation a: c.getDeclaredAnnotations()) { - Class t = a.annotationType(); - String value = ""; - try { - Method m = t.getMethod("value"); - value = m.invoke(a).toString(); - } catch (NoSuchMethodException NSMe) { - } catch (InvocationTargetException ITe) { - } catch (IllegalAccessException IAe) {} - - ans += " \n"; - } - return ans; - } - @SuppressWarnings("unchecked") - private Map getExportedMethods(Class c) throws DBusException - { - if (DBusInterface.class.equals(c)) return new HashMap(); - Map m = new HashMap(); - for (Class i: c.getInterfaces()) - if (DBusInterface.class.equals(i)) { - // add this class's public methods - if (null != c.getAnnotation(DBusInterfaceName.class)) { - String name = ((DBusInterfaceName) c.getAnnotation(DBusInterfaceName.class)).value(); - introspectiondata += " \n"; - DBusSignal.addInterfaceMap(c.getName(), name); - } else { - // don't let people export things which don't have a - // valid D-Bus interface name - if (c.getName().equals(c.getSimpleName())) - throw new DBusException($_("DBusInterfaces cannot be declared outside a package")); - if (c.getName().length() > DBusConnection.MAX_NAME_LENGTH) - throw new DBusException($_("Introspected interface name exceeds 255 characters. Cannot export objects of type ")+c.getName()); - else - introspectiondata += " \n"; - } - introspectiondata += getAnnotations(c); - for (Method meth: c.getDeclaredMethods()) - if (Modifier.isPublic(meth.getModifiers())) { - String ms = ""; - String name; - if (meth.isAnnotationPresent(DBusMemberName.class)) - name = meth.getAnnotation(DBusMemberName.class).value(); - else - name = meth.getName(); - if (name.length() > DBusConnection.MAX_NAME_LENGTH) - throw new DBusException($_("Introspected method name exceeds 255 characters. Cannot export objects with method ")+name); - introspectiondata += " \n"; - introspectiondata += getAnnotations(meth); - for (Class ex: meth.getExceptionTypes()) - if (DBusExecutionException.class.isAssignableFrom(ex)) - introspectiondata += - " \n"; - for (Type pt: meth.getGenericParameterTypes()) - for (String s: Marshalling.getDBusType(pt)) { - introspectiondata += " \n"; - ms += s; - } - if (!Void.TYPE.equals(meth.getGenericReturnType())) { - if (Tuple.class.isAssignableFrom((Class) meth.getReturnType())) { - ParameterizedType tc = (ParameterizedType) meth.getGenericReturnType(); - Type[] ts = tc.getActualTypeArguments(); - - for (Type t: ts) - if (t != null) - for (String s: Marshalling.getDBusType(t)) - introspectiondata += " \n"; - } else if (Object[].class.equals(meth.getGenericReturnType())) { - throw new DBusException($_("Return type of Object[] cannot be introspected properly")); - } else - for (String s: Marshalling.getDBusType(meth.getGenericReturnType())) - introspectiondata += " \n"; - } - introspectiondata += " \n"; - m.put(new MethodTuple(name, ms), meth); - } - for (Class sig: c.getDeclaredClasses()) - if (DBusSignal.class.isAssignableFrom(sig)) { - String name; - if (sig.isAnnotationPresent(DBusMemberName.class)) { - name = ((DBusMemberName) sig.getAnnotation(DBusMemberName.class)).value(); - DBusSignal.addSignalMap(sig.getSimpleName(), name); - } else - name = sig.getSimpleName(); - if (name.length() > DBusConnection.MAX_NAME_LENGTH) - throw new DBusException($_("Introspected signal name exceeds 255 characters. Cannot export objects with signals of type ")+name); - introspectiondata += " \n"; - Constructor con = sig.getConstructors()[0]; - Type[] ts = con.getGenericParameterTypes(); - for (int j = 1; j < ts.length; j++) - for (String s: Marshalling.getDBusType(ts[j])) - introspectiondata += " \n"; - introspectiondata += getAnnotations(sig); - introspectiondata += " \n"; - - } - introspectiondata += " \n"; - } else { - // recurse - m.putAll(getExportedMethods(i)); - } - return m; - } - Map methods; - Reference object; - String introspectiondata; - public ExportedObject(DBusInterface object, boolean weakreferences) throws DBusException - { - if (weakreferences) - this.object = new WeakReference(object); - else - this.object = new StrongReference(object); - introspectiondata = ""; - methods = getExportedMethods(object.getClass()); - introspectiondata += - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"; - introspectiondata += - " \n"+ - " \n"+ - " \n"+ - " \n"; - } -} - - diff --git a/app/src/main/java/org/freedesktop/dbus/Gettext.java b/app/src/main/java/org/freedesktop/dbus/Gettext.java deleted file mode 100644 index 5f2d31be..00000000 --- a/app/src/main/java/org/freedesktop/dbus/Gettext.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Pescetti Pseudo-Duplimate Generator - * - * Copyright (C) 2007 Matthew Johnson - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License Version 2 as published by - * the Free Software Foundation. This program is distributed in the hope that - * it will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. You should have received a - * copy of the GNU Lesser General Public License along with this program; if not, - * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - * - * To Contact me, please email src@matthew.ath.cx - * - */ -package org.freedesktop.dbus; - -import java.util.ResourceBundle; - -public class Gettext -{ -// private static ResourceBundle myResources = -// ResourceBundle.getBundle("dbusjava_localized"); -// public static String $_(String s) { -// return myResources.getString(s); -// } - public static String $_(String s) { - return s; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/InternalSignal.java b/app/src/main/java/org/freedesktop/dbus/InternalSignal.java deleted file mode 100644 index 55954d57..00000000 --- a/app/src/main/java/org/freedesktop/dbus/InternalSignal.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; -import org.freedesktop.dbus.exceptions.DBusException; -class InternalSignal extends DBusSignal -{ - public InternalSignal(String source, String objectpath, String name, String iface, String sig, long serial, Object... parameters) throws DBusException - { - super(objectpath, iface, name, sig, parameters); - this.serial = serial; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/Marshalling.java b/app/src/main/java/org/freedesktop/dbus/Marshalling.java deleted file mode 100644 index e0857e9a..00000000 --- a/app/src/main/java/org/freedesktop/dbus/Marshalling.java +++ /dev/null @@ -1,626 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Vector; - -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.types.DBusListType; -import org.freedesktop.dbus.types.DBusMapType; -import org.freedesktop.dbus.types.DBusStructType; - -import cx.ath.matthew.debug.Debug; - -/** - * Contains static methods for marshalling values. - */ -public class Marshalling -{ - private static Map typeCache = new HashMap(); - /** - * Will return the DBus type corresponding to the given Java type. - * Note, container type should have their ParameterizedType not their - * Class passed in here. - * @param c The Java types. - * @return The DBus types. - * @throws DBusException If the given type cannot be converted to a DBus type. - */ - public static String getDBusType(Type[] c) throws DBusException - { - StringBuffer sb = new StringBuffer(); - for (Type t: c) - for (String s: getDBusType(t)) - sb.append(s); - return sb.toString(); - } - /** - * Will return the DBus type corresponding to the given Java type. - * Note, container type should have their ParameterizedType not their - * Class passed in here. - * @param c The Java type. - * @return The DBus type. - * @throws DBusException If the given type cannot be converted to a DBus type. - */ - public static String[] getDBusType(Type c) throws DBusException - { - String[] cached = typeCache.get(c); - if (null != cached) return cached; - cached = getDBusType(c, false); - typeCache.put(c, cached); - return cached; - } - /** - * Will return the DBus type corresponding to the given Java type. - * Note, container type should have their ParameterizedType not their - * Class passed in here. - * @param c The Java type. - * @param basic If true enforces this to be a non-compound type. (compound types are Maps, Structs and Lists/arrays). - * @return The DBus type. - * @throws DBusException If the given type cannot be converted to a DBus type. - */ - public static String[] getDBusType(Type c, boolean basic) throws DBusException - { - return recursiveGetDBusType(c, basic, 0); - } - private static StringBuffer[] out = new StringBuffer[10]; - @SuppressWarnings("unchecked") - public static String[] recursiveGetDBusType(Type c, boolean basic, int level) throws DBusException - { - if (out.length <= level) { - StringBuffer[] newout = new StringBuffer[out.length]; - System.arraycopy(out, 0, newout, 0, out.length); - out = newout; - } - if (null == out[level]) out[level] = new StringBuffer(); - else out[level].delete(0, out[level].length()); - - if (basic && !(c instanceof Class)) - throw new DBusException(c+ $_(" is not a basic type")); - - if (c instanceof TypeVariable) out[level].append((char) Message.ArgumentType.VARIANT); - else if (c instanceof GenericArrayType) { - out[level].append((char) Message.ArgumentType.ARRAY); - String[] s = recursiveGetDBusType(((GenericArrayType) c).getGenericComponentType(), false, level+1); - if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); - out[level].append(s[0]); - } else if ((c instanceof Class && - DBusSerializable.class.isAssignableFrom((Class) c)) || - (c instanceof ParameterizedType && - DBusSerializable.class.isAssignableFrom((Class) ((ParameterizedType) c).getRawType()))) { - // it's a custom serializable type - Type[] newtypes = null; - if (c instanceof Class) { - for (Method m: ((Class) c).getDeclaredMethods()) - if (m.getName().equals("deserialize")) - newtypes = m.getGenericParameterTypes(); - } - else - for (Method m: ((Class) ((ParameterizedType) c).getRawType()).getDeclaredMethods()) - if (m.getName().equals("deserialize")) - newtypes = m.getGenericParameterTypes(); - - if (null == newtypes) throw new DBusException($_("Serializable classes must implement a deserialize method")); - - String[] sigs = new String[newtypes.length]; - for (int j = 0; j < sigs.length; j++) { - String[] ss = recursiveGetDBusType(newtypes[j], false, level+1); - if (1 != ss.length) throw new DBusException($_("Serializable classes must serialize to native DBus types")); - sigs[j] = ss[0]; - } - return sigs; - } - else if (c instanceof ParameterizedType) { - ParameterizedType p = (ParameterizedType) c; - if (p.getRawType().equals(Map.class)) { - out[level].append("a{"); - Type[] t = p.getActualTypeArguments(); - try { - String[] s = recursiveGetDBusType(t[0], true, level+1); - if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); - out[level].append(s[0]); - s = recursiveGetDBusType(t[1], false, level+1); - if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); - out[level].append(s[0]); - } catch (ArrayIndexOutOfBoundsException AIOOBe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe); - throw new DBusException($_("Map must have 2 parameters")); - } - out[level].append('}'); - } - else if (List.class.isAssignableFrom((Class) p.getRawType())) { - for (Type t: p.getActualTypeArguments()) { - if (Type.class.equals(t)) - out[level].append((char) Message.ArgumentType.SIGNATURE); - else { - String[] s = recursiveGetDBusType(t, false, level+1); - if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); - out[level].append((char) Message.ArgumentType.ARRAY); - out[level].append(s[0]); - } - } - } - else if (p.getRawType().equals(Variant.class)) { - out[level].append((char) Message.ArgumentType.VARIANT); - } - else if (DBusInterface.class.isAssignableFrom((Class) p.getRawType())) { - out[level].append((char) Message.ArgumentType.OBJECT_PATH); - } - else if (Tuple.class.isAssignableFrom((Class) p.getRawType())) { - Type[] ts = p.getActualTypeArguments(); - Vector vs = new Vector(); - for (Type t: ts) - for (String s: recursiveGetDBusType(t, false, level+1)) - vs.add(s); - return vs.toArray(new String[0]); - } - else - throw new DBusException($_("Exporting non-exportable parameterized type ")+c); - } - - else if (c.equals(Byte.class)) out[level].append((char) Message.ArgumentType.BYTE); - else if (c.equals(Byte.TYPE)) out[level].append((char) Message.ArgumentType.BYTE); - else if (c.equals(Boolean.class)) out[level].append((char) Message.ArgumentType.BOOLEAN); - else if (c.equals(Boolean.TYPE)) out[level].append((char) Message.ArgumentType.BOOLEAN); - else if (c.equals(Short.class)) out[level].append((char) Message.ArgumentType.INT16); - else if (c.equals(Short.TYPE)) out[level].append((char) Message.ArgumentType.INT16); - else if (c.equals(UInt16.class)) out[level].append((char) Message.ArgumentType.UINT16); - else if (c.equals(Integer.class)) out[level].append((char) Message.ArgumentType.INT32); - else if (c.equals(Integer.TYPE)) out[level].append((char) Message.ArgumentType.INT32); - else if (c.equals(UInt32.class)) out[level].append((char) Message.ArgumentType.UINT32); - else if (c.equals(Long.class)) out[level].append((char) Message.ArgumentType.INT64); - else if (c.equals(Long.TYPE)) out[level].append((char) Message.ArgumentType.INT64); - else if (c.equals(UInt64.class)) out[level].append((char) Message.ArgumentType.UINT64); - else if (c.equals(Double.class)) out[level].append((char) Message.ArgumentType.DOUBLE); - else if (c.equals(Double.TYPE)) out[level].append((char) Message.ArgumentType.DOUBLE); - else if (c.equals(Float.class) && AbstractConnection.FLOAT_SUPPORT) out[level].append((char) Message.ArgumentType.FLOAT); - else if (c.equals(Float.class)) out[level].append((char) Message.ArgumentType.DOUBLE); - else if (c.equals(Float.TYPE) && AbstractConnection.FLOAT_SUPPORT) out[level].append((char) Message.ArgumentType.FLOAT); - else if (c.equals(Float.TYPE)) out[level].append((char) Message.ArgumentType.DOUBLE); - else if (c.equals(String.class)) out[level].append((char) Message.ArgumentType.STRING); - else if (c.equals(Variant.class)) out[level].append((char) Message.ArgumentType.VARIANT); - else if (c instanceof Class && - DBusInterface.class.isAssignableFrom((Class) c)) out[level].append((char) Message.ArgumentType.OBJECT_PATH); - else if (c instanceof Class && - Path.class.equals((Class) c)) out[level].append((char) Message.ArgumentType.OBJECT_PATH); - else if (c instanceof Class && - ObjectPath.class.equals((Class) c)) out[level].append((char) Message.ArgumentType.OBJECT_PATH); - else if (c instanceof Class && - ((Class) c).isArray()) { - if (Type.class.equals(((Class) c).getComponentType())) - out[level].append((char) Message.ArgumentType.SIGNATURE); - else { - out[level].append((char) Message.ArgumentType.ARRAY); - String[] s = recursiveGetDBusType(((Class) c).getComponentType(), false, level+1); - if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); - out[level].append(s[0]); - } - } else if (c instanceof Class && - Struct.class.isAssignableFrom((Class) c)) { - out[level].append((char) Message.ArgumentType.STRUCT1); - Type[] ts = Container.getTypeCache(c); - if (null == ts) { - Field[] fs = ((Class) c).getDeclaredFields(); - ts = new Type[fs.length]; - for (Field f : fs) { - Position p = f.getAnnotation(Position.class); - if (null == p) continue; - ts[p.value()] = f.getGenericType(); - } - Container.putTypeCache(c, ts); - } - - for (Type t: ts) - if (t != null) - for (String s: recursiveGetDBusType(t, false, level+1)) - out[level].append(s); - out[level].append(')'); - } else { - throw new DBusException($_("Exporting non-exportable type ")+c); - } - - if (Debug.debug) Debug.print(Debug.VERBOSE, "Converted Java type: "+c+" to D-Bus Type: "+out[level]); - - return new String[] { out[level].toString() }; - } - - /** - * Converts a dbus type string into Java Type objects, - * @param dbus The DBus type or types. - * @param rv Vector to return the types in. - * @param limit Maximum number of types to parse (-1 == nolimit). - * @return number of characters parsed from the type string. - */ - public static int getJavaType(String dbus, List rv, int limit) throws DBusException - { - if (null == dbus || "".equals(dbus) || 0 == limit) return 0; - - try { - int i = 0; - for (; i < dbus.length() && (-1 == limit || limit > rv.size()); i++) - switch(dbus.charAt(i)) { - case Message.ArgumentType.STRUCT1: - int j = i+1; - for (int c = 1; c > 0; j++) { - if (')' == dbus.charAt(j)) c--; - else if (Message.ArgumentType.STRUCT1 == dbus.charAt(j)) c++; - } - - Vector contained = new Vector(); - int c = getJavaType(dbus.substring(i+1, j-1), contained, -1); - rv.add(new DBusStructType(contained.toArray(new Type[0]))); - i = j; - break; - case Message.ArgumentType.ARRAY: - if (Message.ArgumentType.DICT_ENTRY1 == dbus.charAt(i+1)) { - contained = new Vector(); - c = getJavaType(dbus.substring(i+2), contained, 2); - rv.add(new DBusMapType(contained.get(0), contained.get(1))); - i += (c+2); - } else { - contained = new Vector(); - c = getJavaType(dbus.substring(i+1), contained, 1); - rv.add(new DBusListType(contained.get(0))); - i += c; - } - break; - case Message.ArgumentType.VARIANT: - rv.add(Variant.class); - break; - case Message.ArgumentType.BOOLEAN: - rv.add(Boolean.class); - break; - case Message.ArgumentType.INT16: - rv.add(Short.class); - break; - case Message.ArgumentType.BYTE: - rv.add(Byte.class); - break; - case Message.ArgumentType.OBJECT_PATH: - rv.add(DBusInterface.class); - break; - case Message.ArgumentType.UINT16: - rv.add(UInt16.class); - break; - case Message.ArgumentType.INT32: - rv.add(Integer.class); - break; - case Message.ArgumentType.UINT32: - rv.add(UInt32.class); - break; - case Message.ArgumentType.INT64: - rv.add(Long.class); - break; - case Message.ArgumentType.UINT64: - rv.add(UInt64.class); - break; - case Message.ArgumentType.DOUBLE: - rv.add(Double.class); - break; - case Message.ArgumentType.FLOAT: - rv.add(Float.class); - break; - case Message.ArgumentType.STRING: - rv.add(String.class); - break; - case Message.ArgumentType.SIGNATURE: - rv.add(Type[].class); - break; - case Message.ArgumentType.DICT_ENTRY1: - rv.add(Map.Entry.class); - contained = new Vector(); - c = getJavaType(dbus.substring(i+1), contained, 2); - i+=c+1; - break; - default: - throw new DBusException(MessageFormat.format($_("Failed to parse DBus type signature: {0} ({1})."), new Object[] { dbus, dbus.charAt(i) })); - } - return i; - } catch (IndexOutOfBoundsException IOOBe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOOBe); - throw new DBusException($_("Failed to parse DBus type signature: ")+dbus); - } - } - /** - * Recursively converts types for serialization onto DBus. - * @param parameters The parameters to convert. - * @param types The (possibly generic) types of the parameters. - * @return The converted parameters. - * @throws DBusException Thrown if there is an error in converting the objects. - */ - @SuppressWarnings("unchecked") - public static Object[] convertParameters(Object[] parameters, Type[] types, AbstractConnection conn) throws DBusException - { - if (null == parameters) return null; - for (int i = 0; i < parameters.length; i++) { - if (Debug.debug) Debug.print(Debug.VERBOSE,"Converting "+i+" from "+parameters[i]+" to "+types[i]); - if (null == parameters[i]) continue; - - if (parameters[i] instanceof DBusSerializable) { - for (Method m: parameters[i].getClass().getDeclaredMethods()) - if (m.getName().equals("deserialize")) { - Type[] newtypes = m.getParameterTypes(); - Type[] expand = new Type[types.length + newtypes.length - 1]; - System.arraycopy(types, 0, expand, 0, i); - System.arraycopy(newtypes, 0, expand, i, newtypes.length); - System.arraycopy(types, i+1, expand, i+newtypes.length, types.length-i-1); - types = expand; - Object[] newparams = ((DBusSerializable) parameters[i]).serialize(); - Object[] exparams = new Object[parameters.length + newparams.length - 1]; - System.arraycopy(parameters, 0, exparams, 0, i); - System.arraycopy(newparams, 0, exparams, i, newparams.length); - System.arraycopy(parameters, i+1, exparams, i+newparams.length, parameters.length-i-1); - parameters = exparams; - } - i--; - } else if (parameters[i] instanceof Tuple) { - Type[] newtypes = ((ParameterizedType) types[i]).getActualTypeArguments(); - Type[] expand = new Type[types.length + newtypes.length - 1]; - System.arraycopy(types, 0, expand, 0, i); - System.arraycopy(newtypes, 0, expand, i, newtypes.length); - System.arraycopy(types, i+1, expand, i+newtypes.length, types. length-i-1); - types = expand; - Object[] newparams = ((Tuple) parameters[i]).getParameters(); - Object[] exparams = new Object[parameters.length + newparams.length - 1]; - System.arraycopy(parameters, 0, exparams, 0, i); - System.arraycopy(newparams, 0, exparams, i, newparams.length); - System.arraycopy(parameters, i+1, exparams, i+newparams.length, parameters.length-i-1); - parameters = exparams; - if (Debug.debug) Debug.print(Debug.VERBOSE, "New params: "+Arrays.deepToString(parameters)+" new types: "+Arrays.deepToString(types)); - i--; - } else if (types[i] instanceof TypeVariable && - !(parameters[i] instanceof Variant)) - // its an unwrapped variant, wrap it - parameters[i] = new Variant(parameters[i]); - else if (parameters[i] instanceof DBusInterface) - parameters[i] = conn.getExportedObject((DBusInterface) parameters[i]); - } - return parameters; - } - @SuppressWarnings("unchecked") - static Object deSerializeParameter(Object parameter, Type type, AbstractConnection conn) throws Exception - { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing from "+parameter.getClass()+" to "+type.getClass()); - if (null == parameter) - return null; - - // its a wrapped variant, unwrap it - if (type instanceof TypeVariable - && parameter instanceof Variant) { - parameter = ((Variant)parameter).getValue(); - } - - // Turn a signature into a Type[] - if (type instanceof Class - && ((Class) type).isArray() - && ((Class) type).getComponentType().equals(Type.class) - && parameter instanceof String) { - Vector rv = new Vector(); - getJavaType((String) parameter, rv, -1); - parameter = rv.toArray(new Type[0]); - } - - // its an object path, get/create the proxy - if (parameter instanceof ObjectPath) { - if (type instanceof Class && DBusInterface.class.isAssignableFrom((Class) type)) - parameter = conn.getExportedObject( - ((ObjectPath) parameter).source, - ((ObjectPath) parameter).path); - else - parameter = new Path(((ObjectPath) parameter).path); - } - - // it should be a struct. create it - if (parameter instanceof Object[] && - type instanceof Class && - Struct.class.isAssignableFrom((Class) type)) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Creating Struct "+type+" from "+parameter); - Type[] ts = Container.getTypeCache(type); - if (null == ts) { - Field[] fs = ((Class) type).getDeclaredFields(); - ts = new Type[fs.length]; - for (Field f : fs) { - Position p = f.getAnnotation(Position.class); - if (null == p) continue; - ts[p.value()] = f.getGenericType(); - } - Container.putTypeCache(type, ts); - } - - // recurse over struct contents - parameter = deSerializeParameters((Object[]) parameter, ts, conn); - for (Constructor con: ((Class) type).getDeclaredConstructors()) { - try { - parameter = con.newInstance((Object[]) parameter); - break; - } catch (IllegalArgumentException IAe) {} - } - } - - // recurse over arrays - if (parameter instanceof Object[]) { - Type[] ts = new Type[((Object[]) parameter).length]; - Arrays.fill(ts, parameter.getClass().getComponentType()); - parameter = deSerializeParameters((Object[]) parameter, - ts, conn); - } - if (parameter instanceof List) { - Type type2; - if (type instanceof ParameterizedType) - type2 = ((ParameterizedType) type).getActualTypeArguments()[0]; - else if (type instanceof GenericArrayType) - type2 = ((GenericArrayType) type).getGenericComponentType(); - else if (type instanceof Class && ((Class) type).isArray()) - type2 = ((Class) type).getComponentType(); - else - type2 = null; - if (null != type2) - parameter = deSerializeParameters((List) parameter, type2, conn); - } - - // correct floats if appropriate - if (type.equals(Float.class) || type.equals(Float.TYPE)) - if (!(parameter instanceof Float)) - parameter = ((Number) parameter).floatValue(); - - // make sure arrays are in the correct format - if (parameter instanceof Object[] || - parameter instanceof List || - parameter.getClass().isArray()) { - if (type instanceof ParameterizedType) - parameter = ArrayFrob.convert(parameter, - (Class) ((ParameterizedType) type).getRawType()); - else if (type instanceof GenericArrayType) { - Type ct = ((GenericArrayType) type).getGenericComponentType(); - Class cc = null; - if (ct instanceof Class) - cc = (Class) ct; - if (ct instanceof ParameterizedType) - cc = (Class) ((ParameterizedType) ct).getRawType(); - Object o = Array.newInstance(cc, 0); - parameter = ArrayFrob.convert(parameter, - o.getClass()); - } else if (type instanceof Class && - ((Class) type).isArray()) { - Class cc = ((Class) type).getComponentType(); - if ((cc.equals(Float.class) || cc.equals(Float.TYPE)) - && (parameter instanceof double[])) { - double[] tmp1 = (double[]) parameter; - float[] tmp2 = new float[tmp1.length]; - for (int i = 0; i < tmp1.length; i++) - tmp2[i] = (float) tmp1[i]; - parameter = tmp2; - } - Object o = Array.newInstance(cc, 0); - parameter = ArrayFrob.convert(parameter, - o.getClass()); - } - } - if (parameter instanceof DBusMap) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing a Map"); - DBusMap dmap = (DBusMap) parameter; - Type[] maptypes = ((ParameterizedType) type).getActualTypeArguments(); - for (int i = 0; i < dmap.entries.length; i++) { - dmap.entries[i][0] = deSerializeParameter(dmap.entries[i][0], maptypes[0], conn); - dmap.entries[i][1] = deSerializeParameter(dmap.entries[i][1], maptypes[1], conn); - } - } - return parameter; - } - static List deSerializeParameters(List parameters, Type type, AbstractConnection conn) throws Exception - { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing from "+parameters+" to "+type); - if (null == parameters) return null; - for (int i = 0; i < parameters.size(); i++) { - if (null == parameters.get(i)) continue; - - /* DO NOT DO THIS! IT'S REALLY NOT SUPPORTED! - * if (type instanceof Class && - DBusSerializable.class.isAssignableFrom((Class) types[i])) { - for (Method m: ((Class) types[i]).getDeclaredMethods()) - if (m.getName().equals("deserialize")) { - Type[] newtypes = m.getGenericParameterTypes(); - try { - Object[] sub = new Object[newtypes.length]; - System.arraycopy(parameters, i, sub, 0, newtypes.length); - sub = deSerializeParameters(sub, newtypes, conn); - DBusSerializable sz = (DBusSerializable) ((Class) types[i]).newInstance(); - m.invoke(sz, sub); - Object[] compress = new Object[parameters.length - newtypes.length + 1]; - System.arraycopy(parameters, 0, compress, 0, i); - compress[i] = sz; - System.arraycopy(parameters, i + newtypes.length, compress, i+1, parameters.length - i - newtypes.length); - parameters = compress; - } catch (ArrayIndexOutOfBoundsException AIOOBe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe); - throw new DBusException("Not enough elements to create custom object from serialized data ("+(parameters.size()-i)+" < "+(newtypes.length)+")"); - } - } - } else*/ - parameters.set(i, deSerializeParameter(parameters.get(i), type, conn)); - } - return parameters; - } - - @SuppressWarnings("unchecked") - static Object[] deSerializeParameters(Object[] parameters, Type[] types, AbstractConnection conn) throws Exception - { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing from "+Arrays.deepToString(parameters)+" to "+Arrays.deepToString(types)); - if (null == parameters) return null; - - if (types.length == 1 && types[0] instanceof ParameterizedType - && Tuple.class.isAssignableFrom((Class) ((ParameterizedType) types[0]).getRawType())) { - types = ((ParameterizedType) types[0]).getActualTypeArguments(); - } - - for (int i = 0; i < parameters.length; i++) { - // CHECK IF ARRAYS HAVE THE SAME LENGTH <-- has to happen after expanding parameters - if (i >= types.length) { - if (Debug.debug) { - for (int j = 0; j < parameters.length; j++) { - Debug.print(Debug.ERR, String.format("Error, Parameters difference (%1d, '%2s')", j, parameters[j].toString())); - } - } - throw new DBusException($_("Error deserializing message: number of parameters didn't match receiving signature")); - } - if (null == parameters[i]) continue; - - if ((types[i] instanceof Class && - DBusSerializable.class.isAssignableFrom((Class) types[i])) || - (types[i] instanceof ParameterizedType && - DBusSerializable.class.isAssignableFrom((Class) ((ParameterizedType) types[i]).getRawType()))) { - Class dsc; - if (types[i] instanceof Class) - dsc = (Class) types[i]; - else - dsc = (Class) ((ParameterizedType) types[i]).getRawType(); - for (Method m: dsc.getDeclaredMethods()) - if (m.getName().equals("deserialize")) { - Type[] newtypes = m.getGenericParameterTypes(); - try { - Object[] sub = new Object[newtypes.length]; - System.arraycopy(parameters, i, sub, 0, newtypes.length); - sub = deSerializeParameters(sub, newtypes, conn); - DBusSerializable sz = dsc.newInstance(); - m.invoke(sz, sub); - Object[] compress = new Object[parameters.length - newtypes.length + 1]; - System.arraycopy(parameters, 0, compress, 0, i); - compress[i] = sz; - System.arraycopy(parameters, i + newtypes.length, compress, i+1, parameters.length - i - newtypes.length); - parameters = compress; - } catch (ArrayIndexOutOfBoundsException AIOOBe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe); - throw new DBusException(MessageFormat.format($_("Not enough elements to create custom object from serialized data ({0} < {1})."), - new Object[] { parameters.length-i, newtypes.length })); - } - } - } else - parameters[i] = deSerializeParameter(parameters[i], types[i], conn); - } - return parameters; - } -} - - diff --git a/app/src/main/java/org/freedesktop/dbus/Message.java b/app/src/main/java/org/freedesktop/dbus/Message.java deleted file mode 100644 index c7762330..00000000 --- a/app/src/main/java/org/freedesktop/dbus/Message.java +++ /dev/null @@ -1,1132 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.lang.reflect.Array; -import java.lang.reflect.Type; -import java.io.UnsupportedEncodingException; -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Vector; - -import cx.ath.matthew.debug.Debug; -import cx.ath.matthew.utils.Hexdump; - -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.MarshallingException; -import org.freedesktop.dbus.exceptions.UnknownTypeCodeException; - -/** - * Superclass of all messages which are sent over the Bus. - * This class deals with all the marshalling to/from the wire format. - */ -public class Message -{ - /** Defines constants representing the endianness of the message. */ - public static interface Endian { - public static final byte BIG = 'B'; - public static final byte LITTLE = 'l'; - } - /** Defines constants representing the flags which can be set on a message. */ - public static interface Flags { - public static final byte NO_REPLY_EXPECTED = 0x01; - public static final byte NO_AUTO_START = 0x02; - public static final byte ASYNC = 0x40; - } - /** Defines constants for each message type. */ - public static interface MessageType { - public static final byte METHOD_CALL = 1; - public static final byte METHOD_RETURN = 2; - public static final byte ERROR = 3; - public static final byte SIGNAL = 4; - } - /** The current protocol major version. */ - public static final byte PROTOCOL = 1; - /** Defines constants for each valid header field type. */ - public static interface HeaderField { - public static final byte PATH = 1; - public static final byte INTERFACE = 2; - public static final byte MEMBER = 3; - public static final byte ERROR_NAME = 4; - public static final byte REPLY_SERIAL = 5; - public static final byte DESTINATION = 6; - public static final byte SENDER = 7; - public static final byte SIGNATURE = 8; - } - /** Defines constants for each argument type. - * There are two constants for each argument type, - * as a byte or as a String (the _STRING version) */ - public static interface ArgumentType { - public static final String BYTE_STRING="y"; - public static final String BOOLEAN_STRING="b"; - public static final String INT16_STRING="n"; - public static final String UINT16_STRING="q"; - public static final String INT32_STRING="i"; - public static final String UINT32_STRING="u"; - public static final String INT64_STRING="x"; - public static final String UINT64_STRING="t"; - public static final String DOUBLE_STRING="d"; - public static final String FLOAT_STRING="f"; - public static final String STRING_STRING="s"; - public static final String OBJECT_PATH_STRING="o"; - public static final String SIGNATURE_STRING="g"; - public static final String ARRAY_STRING="a"; - public static final String VARIANT_STRING="v"; - public static final String STRUCT_STRING="r"; - public static final String STRUCT1_STRING="("; - public static final String STRUCT2_STRING=")"; - public static final String DICT_ENTRY_STRING="e"; - public static final String DICT_ENTRY1_STRING="{"; - public static final String DICT_ENTRY2_STRING="}"; - - public static final byte BYTE='y'; - public static final byte BOOLEAN='b'; - public static final byte INT16='n'; - public static final byte UINT16='q'; - public static final byte INT32='i'; - public static final byte UINT32='u'; - public static final byte INT64='x'; - public static final byte UINT64='t'; - public static final byte DOUBLE='d'; - public static final byte FLOAT='f'; - public static final byte STRING='s'; - public static final byte OBJECT_PATH='o'; - public static final byte SIGNATURE='g'; - public static final byte ARRAY='a'; - public static final byte VARIANT='v'; - public static final byte STRUCT='r'; - public static final byte STRUCT1='('; - public static final byte STRUCT2=')'; - public static final byte DICT_ENTRY='e'; - public static final byte DICT_ENTRY1='{'; - public static final byte DICT_ENTRY2='}'; - } - /** Keep a static reference to each size of padding array to prevent allocation. */ - private static byte[][] padding; - static { - padding = new byte[][] { - null, - new byte[1], - new byte[2], - new byte[3], - new byte[4], - new byte[5], - new byte[6], - new byte[7] }; - } - /** Steps to increment the buffer array. */ - private static final int BUFFERINCREMENT = 20; - - private boolean big; - protected byte[][] wiredata; - protected long bytecounter; - protected Map headers; - protected static long globalserial = 0; - protected long serial; - protected byte type; - protected byte flags; - protected byte protover; - private Object[] args; - private byte[] body; - private long bodylen = 0; - private int preallocated = 0; - private int paofs = 0; - private byte[] pabuf; - private int bufferuse = 0; - - /** - * Returns the name of the given header field. - */ - public static String getHeaderFieldName(byte field) - { - switch (field) { - case HeaderField.PATH: return "Path"; - case HeaderField.INTERFACE: return "Interface"; - case HeaderField.MEMBER: return "Member"; - case HeaderField.ERROR_NAME: return "Error Name"; - case HeaderField.REPLY_SERIAL: return "Reply Serial"; - case HeaderField.DESTINATION: return "Destination"; - case HeaderField.SENDER: return "Sender"; - case HeaderField.SIGNATURE: return "Signature"; - default: return "Invalid"; - } - } - - /** - * Create a message; only to be called by sub-classes. - * @param endian The endianness to create the message. - * @param type The message type. - * @param flags Any message flags. - */ - protected Message(byte endian, byte type, byte flags) throws DBusException - { - wiredata = new byte[BUFFERINCREMENT][]; - headers = new HashMap(); - big = (Endian.BIG == endian); - bytecounter = 0; - synchronized (Message.class) { - serial = ++globalserial; - } - if (Debug.debug) Debug.print(Debug.DEBUG, "Creating message with serial "+serial); - this.type = type; - this.flags = flags; - preallocate(4); - append("yyyy", endian, type, flags, Message.PROTOCOL); - } - /** - * Create a blank message. Only to be used when calling populate. - */ - protected Message() - { - wiredata = new byte[BUFFERINCREMENT][]; - headers = new HashMap(); - bytecounter = 0; - } - /** - * Create a message from wire-format data. - * @param msg D-Bus serialized data of type yyyuu - * @param headers D-Bus serialized data of type a(yv) - * @param body D-Bus serialized data of the signature defined in headers. - */ - @SuppressWarnings("unchecked") - void populate(byte[] msg, byte[] headers, byte[] body) throws DBusException - { - big = (msg[0] == Endian.BIG); - type = msg[1]; - flags = msg[2]; - protover = msg[3]; - wiredata[0] = msg; - wiredata[1] = headers; - wiredata[2] = body; - this.body = body; - bufferuse = 3; - bodylen = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 4)[0]).longValue(); - serial = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 8)[0]).longValue(); - bytecounter = msg.length+headers.length+body.length; - if (Debug.debug) Debug.print(Debug.VERBOSE, headers); - Object[] hs = extract("a(yv)", headers, 0); - if (Debug.debug) Debug.print(Debug.VERBOSE, Arrays.deepToString(hs)); - for (Object o: (Vector) hs[0]) { - this.headers.put((Byte) ((Object[])o)[0], ((Variant)((Object[])o)[1]).getValue()); - } - } - /** - * Create a buffer of num bytes. - * Data is copied to this rather than added to the buffer list. - */ - private void preallocate(int num) - { - preallocated = 0; - pabuf = new byte[num]; - appendBytes(pabuf); - preallocated = num; - paofs = 0; - } - /** - * Ensures there are enough free buffers. - * @param num number of free buffers to create. - */ - private void ensureBuffers(int num) - { - int increase = num - wiredata.length + bufferuse; - if (increase > 0) { - if (increase < BUFFERINCREMENT) increase = BUFFERINCREMENT; - if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing "+bufferuse); - byte[][] temp = new byte[wiredata.length+increase][]; - System.arraycopy(wiredata, 0, temp, 0, wiredata.length); - wiredata = temp; - } - } - /** - * Appends a buffer to the buffer list. - */ - protected void appendBytes(byte[] buf) - { - if (null == buf) return; - if (preallocated > 0) { - if (paofs+buf.length > pabuf.length) - throw new ArrayIndexOutOfBoundsException(MessageFormat.format($_("Array index out of bounds, paofs={0}, pabuf.length={1}, buf.length={2}."), new Object[] { paofs, pabuf.length, buf.length })); - System.arraycopy(buf, 0, pabuf, paofs, buf.length); - paofs += buf.length; - preallocated -= buf.length; - } else { - if (bufferuse == wiredata.length) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing "+bufferuse); - byte[][] temp = new byte[wiredata.length+BUFFERINCREMENT][]; - System.arraycopy(wiredata, 0, temp, 0, wiredata.length); - wiredata = temp; - } - wiredata[bufferuse++] = buf; - bytecounter += buf.length; - } - } - /** - * Appends a byte to the buffer list. - */ - protected void appendByte(byte b) - { - if (preallocated > 0) { - pabuf[paofs++] = b; - preallocated--; - } else { - if (bufferuse == wiredata.length) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing "+bufferuse); - byte[][] temp = new byte[wiredata.length+BUFFERINCREMENT][]; - System.arraycopy(wiredata, 0, temp, 0, wiredata.length); - wiredata = temp; - } - wiredata[bufferuse++] = new byte[] { b }; - bytecounter++; - } - } - /** - * Demarshalls an integer of a given width from a buffer. - * Endianness is determined from the format of the message. - * @param buf The buffer to demarshall from. - * @param ofs The offset to demarshall from. - * @param width The byte-width of the int. - */ - public long demarshallint(byte[] buf, int ofs, int width) - { return big ? demarshallintBig(buf,ofs,width) : demarshallintLittle(buf,ofs,width); } - /** - * Demarshalls an integer of a given width from a buffer. - * @param buf The buffer to demarshall from. - * @param ofs The offset to demarshall from. - * @param endian The endianness to use in demarshalling. - * @param width The byte-width of the int. - */ - public static long demarshallint(byte[] buf, int ofs, byte endian, int width) - { return endian==Endian.BIG ? demarshallintBig(buf,ofs,width) : demarshallintLittle(buf,ofs,width); } - /** - * Demarshalls an integer of a given width from a buffer using big-endian format. - * @param buf The buffer to demarshall from. - * @param ofs The offset to demarshall from. - * @param width The byte-width of the int. - */ - public static long demarshallintBig(byte[] buf, int ofs, int width) - { - long l = 0; - for (int i = 0; i < width; i++) { - l <<=8; - l |= (buf[ofs+i] & 0xFF); - } - return l; - } - /** - * Demarshalls an integer of a given width from a buffer using little-endian format. - * @param buf The buffer to demarshall from. - * @param ofs The offset to demarshall from. - * @param width The byte-width of the int. - */ - public static long demarshallintLittle(byte[] buf, int ofs, int width) - { - long l = 0; - for (int i = (width-1); i >= 0; i--) { - l <<=8; - l |= (buf[ofs+i] & 0xFF); - } - return l; - } - /** - * Marshalls an integer of a given width and appends it to the message. - * Endianness is determined from the message. - * @param l The integer to marshall. - * @param width The byte-width of the int. - */ - public void appendint(long l, int width) - { - byte[] buf = new byte[width]; - marshallint(l, buf, 0, width); - appendBytes(buf); - } - /** - * Marshalls an integer of a given width into a buffer. - * Endianness is determined from the message. - * @param l The integer to marshall. - * @param buf The buffer to marshall to. - * @param ofs The offset to marshall to. - * @param width The byte-width of the int. - */ - public void marshallint(long l, byte[] buf, int ofs, int width) - { - if (big) marshallintBig(l, buf, ofs, width); else marshallintLittle(l, buf, ofs, width); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Marshalled int "+l+" to "+Hexdump.toHex(buf,ofs,width)); - } - /** - * Marshalls an integer of a given width into a buffer using big-endian format. - * @param l The integer to marshall. - * @param buf The buffer to marshall to. - * @param ofs The offset to marshall to. - * @param width The byte-width of the int. - */ - public static void marshallintBig(long l, byte[] buf, int ofs, int width) - { - for (int i = (width-1); i >= 0; i--) { - buf[i+ofs] = (byte) (l & 0xFF); - l >>= 8; - } - } - /** - * Marshalls an integer of a given width into a buffer using little-endian format. - * @param l The integer to marshall. - * @param buf The buffer to demarshall to. - * @param ofs The offset to demarshall to. - * @param width The byte-width of the int. - */ - public static void marshallintLittle(long l, byte[] buf, int ofs, int width) - { - for (int i = 0; i < width; i++) { - buf[i+ofs] = (byte) (l & 0xFF); - l >>= 8; - } - } - public byte[][] getWireData() - { - return wiredata; - } - /** - * Formats the message in a human-readable format. - */ - public String toString() - { - StringBuffer sb = new StringBuffer(); - sb.append(getClass().getSimpleName()); - sb.append ('('); - sb.append (flags); - sb.append (','); - sb.append(serial); - sb.append (')'); - sb.append (' '); - sb.append ('{'); - sb.append(' '); - if (headers.size() == 0) - sb.append('}'); - else { - for (Byte field: headers.keySet()) { - sb.append(getHeaderFieldName(field)); - sb.append('='); - sb.append('>'); - sb.append(headers.get(field).toString()); - sb.append(','); - sb.append(' '); - } - sb.setCharAt(sb.length()-2,' '); - sb.setCharAt(sb.length()-1,'}'); - } - sb.append(' '); - sb.append('{'); - sb.append(' '); - Object[] args = null; - try { - args = getParameters(); - } catch (DBusException DBe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); - } - if (null == args || 0 == args.length) - sb.append('}'); - else { - for (Object o: args) { - if (o instanceof Object[]) - sb.append(Arrays.deepToString((Object[]) o)); - else if (o instanceof byte[]) - sb.append(Arrays.toString((byte[]) o)); - else if (o instanceof int[]) - sb.append(Arrays.toString((int[]) o)); - else if (o instanceof short[]) - sb.append(Arrays.toString((short[]) o)); - else if (o instanceof long[]) - sb.append(Arrays.toString((long[]) o)); - else if (o instanceof boolean[]) - sb.append(Arrays.toString((boolean[]) o)); - else if (o instanceof double[]) - sb.append(Arrays.toString((double[]) o)); - else if (o instanceof float[]) - sb.append(Arrays.toString((float[]) o)); - else - sb.append(o.toString()); - sb.append(','); - sb.append(' '); - } - sb.setCharAt(sb.length()-2,' '); - sb.setCharAt(sb.length()-1,'}'); - } - return sb.toString(); - } - /** - * Returns the value of the header field of a given field. - * @param type The field to return. - * @return The value of the field or null if unset. - */ - public Object getHeader(byte type) { return headers.get(type); } - /** - * Appends a value to the message. - * The type of the value is read from a D-Bus signature and used to marshall - * the value. - * @param sigb A buffer of the D-Bus signature. - * @param sigofs The offset into the signature corresponding to this value. - * @param data The value to marshall. - * @return The offset into the signature of the end of this value's type. - */ - @SuppressWarnings("unchecked") - private int appendone(byte[] sigb, int sigofs, Object data) throws DBusException - { - try { - int i = sigofs; - if (Debug.debug) Debug.print(Debug.VERBOSE, (Object) bytecounter); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending type: "+((char)sigb[i])+" value: "+data); - - // pad to the alignment of this type. - pad(sigb[i]); - switch (sigb[i]) { - case ArgumentType.BYTE: - appendByte(((Number) data).byteValue()); - break; - case ArgumentType.BOOLEAN: - appendint(((Boolean) data).booleanValue() ? 1 : 0, 4); - break; - case ArgumentType.DOUBLE: - long l = Double.doubleToLongBits(((Number) data).doubleValue()); - appendint(l, 8); - break; - case ArgumentType.FLOAT: - int rf = Float.floatToIntBits(((Number) data).floatValue()); - appendint(rf, 4); - break; - case ArgumentType.UINT32: - appendint(((Number) data).longValue(), 4); - break; - case ArgumentType.INT64: - appendint(((Number) data).longValue(), 8); - break; - case ArgumentType.UINT64: - if (big) { - appendint(((UInt64) data).top(), 4); - appendint(((UInt64) data).bottom(), 4); - } else { - appendint(((UInt64) data).bottom(), 4); - appendint(((UInt64) data).top(), 4); - } - break; - case ArgumentType.INT32: - appendint(((Number) data).intValue(), 4); - break; - case ArgumentType.UINT16: - appendint(((Number) data).intValue(), 2); - break; - case ArgumentType.INT16: - appendint(((Number) data).shortValue(), 2); - break; - case ArgumentType.STRING: - case ArgumentType.OBJECT_PATH: - // Strings are marshalled as a UInt32 with the length, - // followed by the String, followed by a null byte. - String payload = data.toString(); - byte[] payloadbytes = null; - try { - payloadbytes = payload.getBytes("UTF-8"); - } catch (UnsupportedEncodingException UEe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(UEe); - throw new DBusException($_("System does not support UTF-8 encoding")); - } - if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending String of length "+payloadbytes.length); - appendint(payloadbytes.length, 4); - appendBytes(payloadbytes); - appendBytes(padding[1]); - //pad(ArgumentType.STRING);? do we need this? - break; - case ArgumentType.SIGNATURE: - // Signatures are marshalled as a byte with the length, - // followed by the String, followed by a null byte. - // Signatures are generally short, so preallocate the array - // for the string, length and null byte. - if (data instanceof Type[]) - payload = Marshalling.getDBusType((Type[]) data); - else - payload = (String) data; - byte[] pbytes = payload.getBytes(); - preallocate(2+pbytes.length); - appendByte((byte) pbytes.length); - appendBytes(pbytes); - appendByte((byte) 0); - break; - case ArgumentType.ARRAY: - // Arrays are given as a UInt32 for the length in bytes, - // padding to the element alignment, then elements in - // order. The length is the length from the end of the - // initial padding to the end of the last element. - if (Debug.debug) { - if (data instanceof Object[]) - Debug.print(Debug.VERBOSE, "Appending array: "+Arrays.deepToString((Object[])data)); - } - - byte[] alen = new byte[4]; - appendBytes(alen); - pad(sigb[++i]); - long c = bytecounter; - - // optimise primatives - if (data.getClass().isArray() && - data.getClass().getComponentType().isPrimitive()) { - byte[] primbuf; - int algn = getAlignment(sigb[i]); - int len = Array.getLength(data); - switch (sigb[i]) { - case ArgumentType.BYTE: - primbuf = (byte[]) data; - break; - case ArgumentType.INT16: - case ArgumentType.INT32: - case ArgumentType.INT64: - primbuf = new byte[len*algn]; - for (int j = 0, k = 0; j < len; j++, k += algn) - marshallint(Array.getLong(data, j), primbuf, k, algn); - break; - case ArgumentType.BOOLEAN: - primbuf = new byte[len*algn]; - for (int j = 0, k = 0; j < len; j++, k += algn) - marshallint(Array.getBoolean(data, j)?1:0, primbuf, k, algn); - break; - case ArgumentType.DOUBLE: - primbuf = new byte[len*algn]; - if (data instanceof float[]) - for (int j = 0, k = 0; j < len; j++, k += algn) - marshallint(Double.doubleToRawLongBits(((float[])data)[j]), - primbuf, k, algn); - else - for (int j = 0, k = 0; j < len; j++, k += algn) - marshallint(Double.doubleToRawLongBits(((double[])data)[j]), - primbuf, k, algn); - break; - case ArgumentType.FLOAT: - primbuf = new byte[len*algn]; - for (int j = 0, k = 0; j < len; j++, k += algn) - marshallint( - Float.floatToRawIntBits(((float[])data)[j]), - primbuf, k, algn); - break; - default: - throw new MarshallingException($_("Primative array being sent as non-primative array.")); - } - appendBytes(primbuf); - } else if (data instanceof List) { - Object[] contents = ((List) data).toArray(); - int diff = i; - ensureBuffers(contents.length*4); - for (Object o: contents) - diff = appendone(sigb, i, o); - i = diff; - } else if (data instanceof Map) { - int diff = i; - ensureBuffers(((Map) data).size()*6); - for (Map.Entry o: ((Map) data).entrySet()) - diff = appendone(sigb, i, o); - if (i == diff) { - // advance the type parser even on 0-size arrays. - Vector temp = new Vector(); - byte[] temp2 = new byte[sigb.length-diff]; - System.arraycopy(sigb, diff, temp2, 0, temp2.length); - String temp3 = new String(temp2); - int temp4 = Marshalling.getJavaType(temp3, temp, 1); - diff += temp4; - } - i = diff; - } else { - Object[] contents = (Object[]) data; - ensureBuffers(contents.length*4); - int diff = i; - for (Object o: contents) - diff = appendone(sigb, i, o); - i = diff; - } - if (Debug.debug) Debug.print(Debug.VERBOSE, "start: "+c+" end: "+bytecounter+" length: "+(bytecounter-c)); - marshallint(bytecounter-c, alen, 0, 4); - break; - case ArgumentType.STRUCT1: - // Structs are aligned to 8 bytes - // and simply contain each element marshalled in order - Object[] contents; - if (data instanceof Container) - contents = ((Container) data).getParameters(); - else - contents = (Object[]) data; - ensureBuffers(contents.length*4); - int j = 0; - for (i++; sigb[i] != ArgumentType.STRUCT2; i++) - i = appendone(sigb, i, contents[j++]); - break; - case ArgumentType.DICT_ENTRY1: - // Dict entries are the same as structs. - if (data instanceof Map.Entry) { - i++; - i = appendone(sigb, i, ((Map.Entry) data).getKey()); - i++; - i = appendone(sigb, i, ((Map.Entry) data).getValue()); - i++; - } else { - contents = (Object[]) data; - j = 0; - for (i++; sigb[i] != ArgumentType.DICT_ENTRY2; i++) - i = appendone(sigb, i, contents[j++]); - } - break; - case ArgumentType.VARIANT: - // Variants are marshalled as a signature - // followed by the value. - if (data instanceof Variant) { - Variant var = (Variant) data; - appendone(new byte[] {ArgumentType.SIGNATURE}, 0, var.getSig()); - appendone((var.getSig()).getBytes(), 0, var.getValue()); - } else if (data instanceof Object[]) { - contents = (Object[]) data; - appendone(new byte[] {ArgumentType.SIGNATURE}, 0, contents[0]); - appendone(((String) contents[0]).getBytes(), 0, contents[1]); - } else { - String sig = Marshalling.getDBusType(data.getClass())[0]; - appendone(new byte[] {ArgumentType.SIGNATURE}, 0, sig); - appendone((sig).getBytes(), 0, data); - } - break; - } - return i; - } catch (ClassCastException CCe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, CCe); - throw new MarshallingException(MessageFormat.format($_("Trying to marshall to unconvertable type (from {0} to {1})."), new Object[] { data.getClass().getName(), sigb[sigofs] })); - } - } - /** - * Pad the message to the proper alignment for the given type. - */ - public void pad(byte type) - { - if (Debug.debug) Debug.print(Debug.VERBOSE, "padding for "+(char)type); - int a = getAlignment(type); - if (Debug.debug) Debug.print(Debug.VERBOSE, preallocated+" "+paofs+" "+bytecounter+" "+a); - int b = (int) ((bytecounter-preallocated)%a); - if (0 == b) return; - a = (a-b); - if (preallocated > 0) { - paofs += a; - preallocated -= a; - } else - appendBytes(padding[a]); - if (Debug.debug) Debug.print(Debug.VERBOSE, preallocated+" "+paofs+" "+bytecounter+" "+a); - } - /** - * Return the alignment for a given type. - */ - public static int getAlignment(byte type) - { - switch (type) { - case 2: - case ArgumentType.INT16: - case ArgumentType.UINT16: - return 2; - case 4: - case ArgumentType.BOOLEAN: - case ArgumentType.FLOAT: - case ArgumentType.INT32: - case ArgumentType.UINT32: - case ArgumentType.STRING: - case ArgumentType.OBJECT_PATH: - case ArgumentType.ARRAY: - return 4; - case 8: - case ArgumentType.INT64: - case ArgumentType.UINT64: - case ArgumentType.DOUBLE: - case ArgumentType.STRUCT: - case ArgumentType.DICT_ENTRY: - case ArgumentType.STRUCT1: - case ArgumentType.DICT_ENTRY1: - case ArgumentType.STRUCT2: - case ArgumentType.DICT_ENTRY2: - return 8; - case 1: - case ArgumentType.BYTE: - case ArgumentType.SIGNATURE: - case ArgumentType.VARIANT: - default: - return 1; - } - } - /** - * Append a series of values to the message. - * @param sig The signature(s) of the value(s). - * @param data The value(s). - */ - public void append(String sig, Object... data) throws DBusException - { - if (Debug.debug) Debug.print(Debug.DEBUG, "Appending sig: "+sig+" data: "+Arrays.deepToString(data)); - byte[] sigb = sig.getBytes(); - int j = 0; - for (int i = 0; i < sigb.length; i++) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending item: "+i+" "+((char)sigb[i])+" "+j); - i = appendone(sigb, i, data[j++]); - } - } - /** - * Align a counter to the given type. - * @param current The current counter. - * @param type The type to align to. - * @return The new, aligned, counter. - */ - public int align(int current, byte type) - { - if (Debug.debug) Debug.print(Debug.VERBOSE, "aligning to "+(char)type); - int a = getAlignment(type); - if (0 == (current%a)) return current; - return current+(a-(current%a)); - } - /** - * Demarshall one value from a buffer. - * @param sigb A buffer of the D-Bus signature. - * @param buf The buffer to demarshall from. - * @param ofs An array of two ints, the offset into the signature buffer - * and the offset into the data buffer. These values will be - * updated to the start of the next value ofter demarshalling. - * @param contained converts nested arrays to Lists - * @return The demarshalled value. - */ - private Object extractone(byte[] sigb, byte[] buf, int[] ofs, boolean contained) throws DBusException - { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Extracting type: "+((char)sigb[ofs[0]])+" from offset "+ofs[1]); - Object rv = null; - ofs[1] = align(ofs[1], sigb[ofs[0]]); - switch (sigb[ofs[0]]) { - case ArgumentType.BYTE: - rv = buf[ofs[1]++]; - break; - case ArgumentType.UINT32: - rv = new UInt32(demarshallint(buf, ofs[1], 4)); - ofs[1] += 4; - break; - case ArgumentType.INT32: - rv = (int) demarshallint(buf, ofs[1], 4); - ofs[1] += 4; - break; - case ArgumentType.INT16: - rv = (short) demarshallint(buf, ofs[1], 2); - ofs[1] += 2; - break; - case ArgumentType.UINT16: - rv = new UInt16((int) demarshallint(buf, ofs[1], 2)); - ofs[1] += 2; - break; - case ArgumentType.INT64: - rv = demarshallint(buf, ofs[1], 8); - ofs[1] += 8; - break; - case ArgumentType.UINT64: - long top; - long bottom; - if (big) { - top = demarshallint(buf, ofs[1], 4); - ofs[1] += 4; - bottom = demarshallint(buf, ofs[1], 4); - } else { - bottom = demarshallint(buf, ofs[1], 4); - ofs[1] += 4; - top = demarshallint(buf, ofs[1], 4); - } - rv = new UInt64(top, bottom); - ofs[1] += 4; - break; - case ArgumentType.DOUBLE: - long l = demarshallint(buf, ofs[1], 8); - ofs[1] += 8; - rv = Double.longBitsToDouble(l); - break; - case ArgumentType.FLOAT: - int rf = (int) demarshallint(buf, ofs[1], 4); - ofs[1] += 4; - rv = Float.intBitsToFloat(rf); - break; - case ArgumentType.BOOLEAN: - rf = (int) demarshallint(buf, ofs[1], 4); - ofs[1] += 4; - rv = (1==rf)?Boolean.TRUE:Boolean.FALSE; - break; - case ArgumentType.ARRAY: - long size = demarshallint(buf, ofs[1], 4); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Reading array of size: "+size); - ofs[1] += 4; - byte algn = (byte) getAlignment(sigb[++ofs[0]]); - ofs[1] = align(ofs[1], sigb[ofs[0]]); - int length = (int) (size / algn); - if (length > DBusConnection.MAX_ARRAY_LENGTH) - throw new MarshallingException($_("Arrays must not exceed ")+DBusConnection.MAX_ARRAY_LENGTH); - // optimise primatives - switch (sigb[ofs[0]]) { - case ArgumentType.BYTE: - rv = new byte[length]; - System.arraycopy(buf, ofs[1], rv, 0, length); - ofs[1] += size; - break; - case ArgumentType.INT16: - rv = new short[length]; - for (int j = 0; j < length; j++, ofs[1] += algn) - ((short[]) rv)[j] = (short) demarshallint(buf, ofs[1], algn); - break; - case ArgumentType.INT32: - rv = new int[length]; - for (int j = 0; j < length; j++, ofs[1] += algn) - ((int[]) rv)[j] = (int) demarshallint(buf, ofs[1], algn); - break; - case ArgumentType.INT64: - rv = new long[length]; - for (int j = 0; j < length; j++, ofs[1] += algn) - ((long[]) rv)[j] = demarshallint(buf, ofs[1], algn); - break; - case ArgumentType.BOOLEAN: - rv = new boolean[length]; - for (int j = 0; j < length; j++, ofs[1] += algn) - ((boolean[]) rv)[j] = (1 == demarshallint(buf, ofs[1], algn)); - break; - case ArgumentType.FLOAT: - rv = new float[length]; - for (int j = 0; j < length; j++, ofs[1] += algn) - ((float[]) rv)[j] = - Float.intBitsToFloat((int)demarshallint(buf, ofs[1], algn)); - break; - case ArgumentType.DOUBLE: - rv = new double[length]; - for (int j = 0; j < length; j++, ofs[1] += algn) - ((double[]) rv)[j] = - Double.longBitsToDouble(demarshallint(buf, ofs[1], algn)); - break; - case ArgumentType.DICT_ENTRY1: - if (0 == size) { - // advance the type parser even on 0-size arrays. - Vector temp = new Vector(); - byte[] temp2 = new byte[sigb.length-ofs[0]]; - System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length); - String temp3 = new String(temp2); - // ofs[0] gets incremented anyway. Leave one character on the stack - int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1; - ofs[0] += temp4; - if (Debug.debug) Debug.print(Debug.VERBOSE, "Aligned type: "+temp3+" "+temp4+" "+ofs[0]); - } - int ofssave = ofs[0]; - long end = ofs[1]+size; - Vector entries = new Vector(); - while (ofs[1] < end) { - ofs[0] = ofssave; - entries.add((Object[]) extractone(sigb, buf, ofs, true)); - } - rv = new DBusMap(entries.toArray(new Object[0][])); - break; - default: - if (0 == size) { - // advance the type parser even on 0-size arrays. - Vector temp = new Vector(); - byte[] temp2 = new byte[sigb.length-ofs[0]]; - System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length); - String temp3 = new String(temp2); - // ofs[0] gets incremented anyway. Leave one character on the stack - int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1; - ofs[0] += temp4; - if (Debug.debug) Debug.print(Debug.VERBOSE, "Aligned type: "+temp3+" "+temp4+" "+ofs[0]); - } - ofssave = ofs[0]; - end = ofs[1]+size; - Vector contents = new Vector(); - while (ofs[1] < end) { - ofs[0] = ofssave; - contents.add(extractone(sigb, buf, ofs, true)); - } - rv = contents; - } - if (contained && !(rv instanceof List) && !(rv instanceof Map)) - rv = ArrayFrob.listify(rv); - break; - case ArgumentType.STRUCT1: - Vector contents = new Vector(); - while (sigb[++ofs[0]] != ArgumentType.STRUCT2) - contents.add(extractone(sigb, buf, ofs, true)); - rv = contents.toArray(); - break; - case ArgumentType.DICT_ENTRY1: - Object[] decontents = new Object[2]; - if (Debug.debug) Debug.print(Debug.VERBOSE, "Extracting Dict Entry ("+Hexdump.toAscii(sigb,ofs[0],sigb.length-ofs[0])+") from: "+Hexdump.toHex(buf,ofs[1],buf.length-ofs[1])); - ofs[0]++; - decontents[0] = extractone(sigb, buf, ofs, true); - ofs[0]++; - decontents[1] = extractone(sigb, buf, ofs, true); - ofs[0]++; - rv = decontents; - break; - case ArgumentType.VARIANT: - int[] newofs = new int[] { 0, ofs[1] }; - String sig = (String) extract(ArgumentType.SIGNATURE_STRING, buf, newofs)[0]; - newofs[0] = 0; - rv = new Variant(extract(sig, buf, newofs)[0] , sig); - ofs[1] = newofs[1]; - break; - case ArgumentType.STRING: - length = (int) demarshallint(buf, ofs[1], 4); - ofs[1] += 4; - try { - rv = new String(buf, ofs[1], length, "UTF-8"); - } catch (UnsupportedEncodingException UEe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(UEe); - throw new DBusException($_("System does not support UTF-8 encoding")); - } - ofs[1] += length + 1; - break; - case ArgumentType.OBJECT_PATH: - length = (int) demarshallint(buf, ofs[1], 4); - ofs[1] += 4; - rv = new ObjectPath(getSource(), new String(buf, ofs[1], length)); - ofs[1] += length + 1; - break; - case ArgumentType.SIGNATURE: - length = (buf[ofs[1]++] & 0xFF); - rv = new String(buf, ofs[1], length); - ofs[1] += length + 1; - break; - default: - throw new UnknownTypeCodeException(sigb[ofs[0]]); - } - if (Debug.debug) if (rv instanceof Object[]) - Debug.print(Debug.VERBOSE, "Extracted: "+Arrays.deepToString((Object[]) rv)+" (now at "+ofs[1]+")"); - else - Debug.print(Debug.VERBOSE, "Extracted: "+rv+" (now at "+ofs[1]+")"); - return rv; - } - /** - * Demarshall values from a buffer. - * @param sig The D-Bus signature(s) of the value(s). - * @param buf The buffer to demarshall from. - * @param ofs The offset into the data buffer to start. - * @return The demarshalled value(s). - */ - public Object[] extract(String sig, byte[] buf, int ofs) throws DBusException - { - return extract(sig, buf, new int[] { 0, ofs }); - } - /** - * Demarshall values from a buffer. - * @param sig The D-Bus signature(s) of the value(s). - * @param buf The buffer to demarshall from. - * @param ofs An array of two ints, the offset into the signature - * and the offset into the data buffer. These values will be - * updated to the start of the next value ofter demarshalling. - * @return The demarshalled value(s). - */ - public Object[] extract(String sig, byte[] buf, int[] ofs) throws DBusException - { - if (Debug.debug) Debug.print(Debug.VERBOSE, "extract("+sig+",#"+buf.length+", {"+ofs[0]+","+ofs[1]+"}"); - Vector rv = new Vector(); - byte[] sigb = sig.getBytes(); - for (int[] i = ofs; i[0] < sigb.length; i[0]++) { - rv.add(extractone(sigb, buf, i, false)); - } - return rv.toArray(); - } - /** - * Returns the Bus ID that sent the message. - */ - public String getSource() { return (String) headers.get(HeaderField.SENDER); } - /** - * Returns the destination of the message. - */ - public String getDestination() { return (String) headers.get(HeaderField.DESTINATION); } - /** - * Returns the interface of the message. - */ - public String getInterface() { return (String) headers.get(HeaderField.INTERFACE); } - /** - * Returns the object path of the message. - */ - public String getPath() - { - Object o = headers.get(HeaderField.PATH); - if (null == o) return null; - return o.toString(); - } - /** - * Returns the member name or error name this message represents. - */ - public String getName() - { - if (this instanceof Error) - return (String) headers.get(HeaderField.ERROR_NAME); - else - return (String) headers.get(HeaderField.MEMBER); - } - /** - * Returns the dbus signature of the parameters. - */ - public String getSig() { return (String) headers.get(HeaderField.SIGNATURE); } - /** - * Returns the message flags. - */ - public int getFlags() { return flags; } - /** - * Returns the message serial ID (unique for this connection) - * @return the message serial. - */ - public long getSerial() { return serial; } - /** - * If this is a reply to a message, this returns its serial. - * @return The reply serial, or 0 if it is not a reply. - */ - public long getReplySerial() - { - Number l = (Number) headers.get(HeaderField.REPLY_SERIAL); - if (null == l) return 0; - return l.longValue(); - } - /** - * Parses and returns the parameters to this message as an Object array. - */ - public Object[] getParameters() throws DBusException - { - if (null == args && null != body) { - String sig = (String) headers.get(HeaderField.SIGNATURE); - if (null != sig && 0 != body.length) { - args = extract(sig, body, 0); - } else args = new Object[0]; - } - return args; - } - protected void setArgs(Object[] args) { this.args = args; } - /** - * Warning, do not use this method unless you really know what you are doing. - */ - public void setSource(String source) throws DBusException - { - if (null != body) { - wiredata = new byte[BUFFERINCREMENT][]; - bufferuse = 0; - bytecounter = 0; - preallocate(12); - append("yyyyuu", big ? Endian.BIG : Endian.LITTLE, type, flags, protover, bodylen, serial); - headers.put(HeaderField.SENDER, source); - Object[][] newhead = new Object[headers.size()][]; - int i = 0; - for (Byte b: headers.keySet()) { - newhead[i] = new Object[2]; - newhead[i][0] = b; - newhead[i][1] = headers.get(b); - i++; - } - append("a(yv)", (Object) newhead); - pad((byte) 8); - appendBytes(body); - } - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/MessageReader.java b/app/src/main/java/org/freedesktop/dbus/MessageReader.java deleted file mode 100644 index 6ce7090a..00000000 --- a/app/src/main/java/org/freedesktop/dbus/MessageReader.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.io.BufferedInputStream; -import java.io.EOFException; -import java.io.InputStream; -import java.io.IOException; -import java.net.SocketTimeoutException; -import java.text.MessageFormat; - -import cx.ath.matthew.debug.Debug; -import cx.ath.matthew.utils.Hexdump; - -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.MessageTypeException; -import org.freedesktop.dbus.exceptions.MessageProtocolVersionException; - -public class MessageReader -{ - private InputStream in; - private byte[] buf = null; - private byte[] tbuf = null; - private byte[] header = null; - private byte[] body = null; - private int[] len = new int[4]; - public MessageReader(InputStream in) - { - this.in = new BufferedInputStream(in); - } - public Message readMessage() throws IOException, DBusException - { - int rv; - /* Read the 12 byte fixed header, retrying as neccessary */ - if (null == buf) { buf = new byte[12]; len[0] = 0; } - if (len[0] < 12) { - try { rv = in.read(buf, len[0], 12-len[0]); } - catch (SocketTimeoutException STe) { return null; } - if (-1 == rv) throw new EOFException($_("Underlying transport returned EOF")); - len[0] += rv; - } - if (len[0] == 0) return null; - if (len[0] < 12) { - if (Debug.debug) Debug.print(Debug.DEBUG, "Only got "+len[0]+" of 12 bytes of header"); - return null; - } - - /* Parse the details from the header */ - byte endian = buf[0]; - byte type = buf[1]; - byte protover = buf[3]; - if (protover > Message.PROTOCOL) { - buf = null; - throw new MessageProtocolVersionException(MessageFormat.format($_("Protocol version {0} is unsupported"), new Object[] { protover })); - } - - /* Read the length of the variable header */ - if (null == tbuf) { tbuf = new byte[4]; len[1] = 0; } - if (len[1] < 4) { - try { rv = in.read(tbuf, len[1], 4-len[1]); } - catch (SocketTimeoutException STe) { return null; } - if (-1 == rv) throw new EOFException($_("Underlying transport returned EOF")); - len[1] += rv; - } - if (len[1] < 4) { - if (Debug.debug) Debug.print(Debug.DEBUG, "Only got "+len[1]+" of 4 bytes of header"); - return null; - } - - /* Parse the variable header length */ - int headerlen = 0; - if (null == header) { - headerlen = (int) Message.demarshallint(tbuf, 0, endian, 4); - if (0 != headerlen % 8) - headerlen += 8-(headerlen%8); - } else - headerlen = header.length-8; - - /* Read the variable header */ - if (null == header) { - header = new byte[headerlen+8]; - System.arraycopy(tbuf, 0, header, 0, 4); - len[2] = 0; - } - if (len[2] < headerlen) { - try { rv = in.read(header, 8+len[2], headerlen-len[2]); } - catch (SocketTimeoutException STe) { return null; } - if (-1 == rv) throw new EOFException($_("Underlying transport returned EOF")); - len[2] += rv; - } - if (len[2] < headerlen) { - if (Debug.debug) Debug.print(Debug.DEBUG, "Only got "+len[2]+" of "+headerlen+" bytes of header"); - return null; - } - - /* Read the body */ - int bodylen = 0; - if (null == body) bodylen = (int) Message.demarshallint(buf, 4, endian, 4); - if (null == body) { body=new byte[bodylen]; len[3] = 0; } - if (len[3] < body.length) { - try { rv = in.read(body, len[3], body.length-len[3]); } - catch (SocketTimeoutException STe) { return null; } - if (-1 == rv) throw new EOFException($_("Underlying transport returned EOF")); - len[3] += rv; - } - if (len[3] < body.length) { - if (Debug.debug) Debug.print(Debug.DEBUG, "Only got "+len[3]+" of "+body.length+" bytes of body"); - return null; - } - - Message m; - switch (type) { - case Message.MessageType.METHOD_CALL: - m = new MethodCall(); - break; - case Message.MessageType.METHOD_RETURN: - m = new MethodReturn(); - break; - case Message.MessageType.SIGNAL: - m = new DBusSignal(); - break; - case Message.MessageType.ERROR: - m = new Error(); - break; - default: - throw new MessageTypeException(MessageFormat.format($_("Message type {0} unsupported"), new Object[] {type})); - } - if (Debug.debug) { - Debug.print(Debug.VERBOSE, Hexdump.format(buf)); - Debug.print(Debug.VERBOSE, Hexdump.format(tbuf)); - Debug.print(Debug.VERBOSE, Hexdump.format(header)); - Debug.print(Debug.VERBOSE, Hexdump.format(body)); - } - try { - m.populate(buf, header, body); - } catch (DBusException DBe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); - buf = null; - tbuf = null; - body = null; - header = null; - throw DBe; - } catch (RuntimeException Re) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Re); - buf = null; - tbuf = null; - body = null; - header = null; - throw Re; - } - if (Debug.debug) { - Debug.print(Debug.INFO, "=> "+m); - } - buf = null; - tbuf = null; - body = null; - header = null; - return m; - } - public void close() throws IOException - { - if (Debug.debug) Debug.print(Debug.INFO, "Closing Message Reader"); - in.close(); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/MessageWriter.java b/app/src/main/java/org/freedesktop/dbus/MessageWriter.java deleted file mode 100644 index e95bf78a..00000000 --- a/app/src/main/java/org/freedesktop/dbus/MessageWriter.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import java.io.BufferedOutputStream; -import java.io.OutputStream; -import java.io.IOException; - -import cx.ath.matthew.debug.Debug; -import cx.ath.matthew.unix.USOutputStream; -import cx.ath.matthew.utils.Hexdump; - -public class MessageWriter -{ - private OutputStream out; - private boolean isunix; - public MessageWriter(OutputStream out) - { - this.out = out; - this.isunix = false; - try { - if (out instanceof USOutputStream) - this.isunix = true; - } catch (Throwable t) { - } - if (!this.isunix) - this.out = new BufferedOutputStream(this.out); - } - public void writeMessage(Message m) throws IOException - { - if (Debug.debug) { - Debug.print(Debug.INFO, "<= "+m); - } - if (null == m) return; - if (null == m.getWireData()) { - if (Debug.debug) Debug.print(Debug.WARN, "Message "+m+" wire-data was null!"); - return; - } - if (isunix) { - if (Debug.debug) { - Debug.print(Debug.DEBUG, "Writing all "+m.getWireData().length+" buffers simultaneously to Unix Socket"); - for (byte[] buf: m.getWireData()) - Debug.print(Debug.VERBOSE, "("+buf+"):"+ (null==buf? "": Hexdump.format(buf))); - } - ((USOutputStream) out).write(m.getWireData()); - } else - for (byte[] buf: m.getWireData()) { - if (Debug.debug) - Debug.print(Debug.VERBOSE, "("+buf+"):"+ (null==buf? "": Hexdump.format(buf))); - if (null == buf) break; - out.write(buf); - } - out.flush(); - } - public void close() throws IOException - { - if (Debug.debug) Debug.print(Debug.INFO, "Closing Message Writer"); - out.close(); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/MethodCall.java b/app/src/main/java/org/freedesktop/dbus/MethodCall.java deleted file mode 100644 index 41b688f7..00000000 --- a/app/src/main/java/org/freedesktop/dbus/MethodCall.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.util.Vector; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.MessageFormatException; -import cx.ath.matthew.debug.Debug; -import cx.ath.matthew.utils.Hexdump; - -public class MethodCall extends Message -{ - MethodCall() { } - public MethodCall(String dest, String path, String iface, String member, byte flags, String sig, Object... args) throws DBusException - { - this(null, dest, path, iface, member, flags, sig, args); - } - public MethodCall(String source, String dest, String path, String iface, String member, byte flags, String sig, Object... args) throws DBusException - { - super(Message.Endian.BIG, Message.MessageType.METHOD_CALL, flags); - - if (null == member || null == path) - throw new MessageFormatException($_("Must specify destination, path and function name to MethodCalls.")); - headers.put(Message.HeaderField.PATH,path); - headers.put(Message.HeaderField.MEMBER,member); - - Vector hargs = new Vector(); - - hargs.add(new Object[] { Message.HeaderField.PATH, new Object[] { ArgumentType.OBJECT_PATH_STRING, path } }); - - if (null != source) { - headers.put(Message.HeaderField.SENDER,source); - hargs.add(new Object[] { Message.HeaderField.SENDER, new Object[] { ArgumentType.STRING_STRING, source } }); - } - - if (null != dest) { - headers.put(Message.HeaderField.DESTINATION,dest); - hargs.add(new Object[] { Message.HeaderField.DESTINATION, new Object[] { ArgumentType.STRING_STRING, dest } }); - } - - if (null != iface) { - hargs.add(new Object[] { Message.HeaderField.INTERFACE, new Object[] { ArgumentType.STRING_STRING, iface } }); - headers.put(Message.HeaderField.INTERFACE,iface); - } - - hargs.add(new Object[] { Message.HeaderField.MEMBER, new Object[] { ArgumentType. STRING_STRING, member } }); - - if (null != sig) { - if (Debug.debug) Debug.print(Debug.DEBUG, "Appending arguments with signature: "+sig); - hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); - headers.put(Message.HeaderField.SIGNATURE,sig); - setArgs(args); - } - - byte[] blen = new byte[4]; - appendBytes(blen); - append("ua(yv)", serial, hargs.toArray()); - pad((byte)8); - - long c = bytecounter; - if (null != sig) append(sig, args); - if (Debug.debug) Debug.print(Debug.DEBUG, "Appended body, type: "+sig+" start: "+c+" end: "+bytecounter+" size: "+(bytecounter-c)); - marshallint(bytecounter-c, blen, 0, 4); - if (Debug.debug) Debug.print("marshalled size ("+blen+"): "+Hexdump.format(blen)); - } - private static long REPLY_WAIT_TIMEOUT = 20000; - /** - * Set the default timeout for method calls. - * Default is 20s. - * @param timeout New timeout in ms. - */ - public static void setDefaultTimeout(long timeout) - { - REPLY_WAIT_TIMEOUT = timeout; - } - Message reply = null; - public synchronized boolean hasReply() - { - return null != reply; - } - /** - * Block (if neccessary) for a reply. - * @return The reply to this MethodCall, or null if a timeout happens. - * @param timeout The length of time to block before timing out (ms). - */ - public synchronized Message getReply(long timeout) - { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking on "+this); - if (null != reply) return reply; - try { - wait(timeout); - return reply; - } catch (InterruptedException Ie) { return reply; } - } - /** - * Block (if neccessary) for a reply. - * Default timeout is 20s, or can be configured with setDefaultTimeout() - * @return The reply to this MethodCall, or null if a timeout happens. - */ - public synchronized Message getReply() - { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking on "+this); - if (null != reply) return reply; - try { - wait(REPLY_WAIT_TIMEOUT); - return reply; - } catch (InterruptedException Ie) { return reply; } - } - protected synchronized void setReply(Message reply) - { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting reply to "+this+" to "+reply); - this.reply = reply; - notifyAll(); - } - -} diff --git a/app/src/main/java/org/freedesktop/dbus/MethodReturn.java b/app/src/main/java/org/freedesktop/dbus/MethodReturn.java deleted file mode 100644 index b0f719ae..00000000 --- a/app/src/main/java/org/freedesktop/dbus/MethodReturn.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import java.util.Vector; -import org.freedesktop.dbus.exceptions.DBusException; - -public class MethodReturn extends Message -{ - MethodReturn() { } - public MethodReturn(String dest, long replyserial, String sig, Object... args) throws DBusException - { - this(null, dest, replyserial, sig, args); - } - public MethodReturn(String source, String dest, long replyserial, String sig, Object... args) throws DBusException - { - super(Message.Endian.BIG, Message.MessageType.METHOD_RETURN, (byte) 0); - - headers.put(Message.HeaderField.REPLY_SERIAL,replyserial); - - Vector hargs = new Vector(); - hargs.add(new Object[] { Message.HeaderField.REPLY_SERIAL, new Object[] { ArgumentType.UINT32_STRING, replyserial } }); - - if (null != source) { - headers.put(Message.HeaderField.SENDER,source); - hargs.add(new Object[] { Message.HeaderField.SENDER, new Object[] { ArgumentType.STRING_STRING, source } }); - } - - if (null != dest) { - headers.put(Message.HeaderField.DESTINATION,dest); - hargs.add(new Object[] { Message.HeaderField.DESTINATION, new Object[] { ArgumentType.STRING_STRING, dest } }); - } - - if (null != sig) { - hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); - headers.put(Message.HeaderField.SIGNATURE,sig); - setArgs(args); - } - - byte[] blen = new byte[4]; - appendBytes(blen); - append("ua(yv)", serial, hargs.toArray()); - pad((byte)8); - - long c = bytecounter; - if (null != sig) append(sig, args); - marshallint(bytecounter-c, blen, 0, 4); - } - public MethodReturn(MethodCall mc, String sig, Object... args) throws DBusException - { - this(null, mc, sig, args); - } - public MethodReturn(String source, MethodCall mc, String sig, Object... args) throws DBusException - { - this(source, mc.getSource(), mc.getSerial(), sig, args); - this.call = mc; - } - MethodCall call; - public MethodCall getCall() { return call; } - protected void setCall(MethodCall call) { this.call = call; } -} diff --git a/app/src/main/java/org/freedesktop/dbus/MethodTuple.java b/app/src/main/java/org/freedesktop/dbus/MethodTuple.java deleted file mode 100644 index dd2c4a98..00000000 --- a/app/src/main/java/org/freedesktop/dbus/MethodTuple.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import cx.ath.matthew.debug.Debug; - -class MethodTuple -{ - String name; - String sig; - public MethodTuple(String name, String sig) - { - this.name = name; - if (null != sig) - this.sig = sig; - else - this.sig = ""; - if (Debug.debug) Debug.print(Debug.VERBOSE, "new MethodTuple("+this.name+", "+this.sig+")"); - } - public boolean equals(Object o) - { - return o.getClass().equals(MethodTuple.class) - && ((MethodTuple) o).name.equals(this.name) - && ((MethodTuple) o).sig.equals(this.sig); - } - public int hashCode() - { - return name.hashCode()+sig.hashCode(); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/ObjectPath.java b/app/src/main/java/org/freedesktop/dbus/ObjectPath.java deleted file mode 100644 index 409d26da..00000000 --- a/app/src/main/java/org/freedesktop/dbus/ObjectPath.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -class ObjectPath extends Path -{ - public String source; -// public DBusConnection conn; - public ObjectPath(String source, String path/*, DBusConnection conn*/) - { - super(path); - this.source = source; - // this.conn = conn; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/ObjectTree.java b/app/src/main/java/org/freedesktop/dbus/ObjectTree.java deleted file mode 100644 index f7d05916..00000000 --- a/app/src/main/java/org/freedesktop/dbus/ObjectTree.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import cx.ath.matthew.debug.Debug; - -import java.util.regex.Pattern; - -/** - * Keeps track of the exported objects for introspection data */ -class ObjectTree -{ - class TreeNode - { - String name; - ExportedObject object; - String data; - TreeNode right; - TreeNode down; - public TreeNode(String name) - { - this.name = name; - } - public TreeNode(String name, ExportedObject object, String data) - { - this.name = name; - this.object = object; - this.data = data; - } - } - private TreeNode root; - public ObjectTree() - { - root = new TreeNode(""); - } - public static final Pattern slashpattern = Pattern.compile("/"); - - private TreeNode recursiveFind(TreeNode current, String path) - { - if ("/".equals(path)) return current; - String[] elements = path.split("/", 2); - // this is us or a parent node - if (path.startsWith(current.name)) { - // this is us - if (path.equals(current.name)) { - return current; - } - // recurse down - else { - if (current.down == null) - return null; - else return recursiveFind(current.down, elements[1]); - } - } - else if (current.right == null) { - return null; - } - else if (0 > current.right.name.compareTo(elements[0])) { - return null; - } - // recurse right - else { - return recursiveFind(current.right, path); - } - } - private TreeNode recursiveAdd(TreeNode current, String path, ExportedObject object, String data) - { - String[] elements = slashpattern.split(path, 2); - // this is us or a parent node - if (path.startsWith(current.name)) { - // this is us - if (1 == elements.length || "".equals(elements[1])) { - current.object = object; - current.data = data; - } - // recurse down - else { - if (current.down == null) { - String[] el = elements[1].split("/", 2); - current.down = new TreeNode(el[0]); - } - current.down = recursiveAdd(current.down, elements[1], object, data); - } - } - // need to create a new sub-tree on the end - else if (current.right == null) { - current.right = new TreeNode(elements[0]); - current.right = recursiveAdd(current.right, path, object, data); - } - // need to insert here - else if (0 > current.right.name.compareTo(elements[0])) { - TreeNode t = new TreeNode(elements[0]); - t.right = current.right; - current.right = t; - current.right = recursiveAdd(current.right, path, object, data); - } - // recurse right - else { - current.right = recursiveAdd(current.right, path, object, data); - } - return current; - } - public void add(String path, ExportedObject object, String data) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "Adding "+path+" to object tree"); - root = recursiveAdd(root, path, object, data); - } - public void remove(String path) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "Removing "+path+" from object tree"); - TreeNode t = recursiveFind(root, path); - t.object = null; - t.data = null; - } - - public String Introspect(String path) - { - TreeNode t = recursiveFind(root, path); - if (null == t) return null; - StringBuilder sb = new StringBuilder(); - sb.append("\n"); - if (null != t.data) sb.append(t.data); - t = t.down; - while (null != t) { - sb.append("\n"); - t = t.right; - } - sb.append(""); - return sb.toString(); - } - - private String recursivePrint(TreeNode current) - { - String s = ""; - if (null != current) { - s += current.name; - if (null != current.object) - s += "*"; - if (null != current.down) - s += "/{"+recursivePrint(current.down)+"}"; - if (null != current.right) - s += ", "+recursivePrint(current.right); - } - return s; - } - - public String toString() - { - return recursivePrint(root); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/Path.java b/app/src/main/java/org/freedesktop/dbus/Path.java deleted file mode 100644 index baaa7a62..00000000 --- a/app/src/main/java/org/freedesktop/dbus/Path.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -public class Path implements Comparable -{ - protected String path; - public Path(String path) - { - this.path = path; - } - public String getPath() - { - return path; - } - public String toString() - { - return path; - } - public boolean equals(Object other) - { - return (other instanceof Path) && path.equals(((Path) other).path); - } - public int hashCode() - { - return path.hashCode(); - } - public int compareTo(Path that) - { - return path.compareTo(that.path); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/Position.java b/app/src/main/java/org/freedesktop/dbus/Position.java deleted file mode 100644 index 45e68d88..00000000 --- a/app/src/main/java/org/freedesktop/dbus/Position.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Position annotation, to annotate Struct fields - * to be sent over DBus. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -public @interface Position -{ - /** The order of this field in the Struct. */ - int value(); -} diff --git a/app/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java b/app/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java deleted file mode 100644 index e0302999..00000000 --- a/app/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.lang.reflect.Type; -import java.text.MessageFormat; -import java.util.Arrays; - -import org.freedesktop.DBus; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; -import org.freedesktop.dbus.exceptions.NotConnected; - -import cx.ath.matthew.debug.Debug; - -class RemoteInvocationHandler implements InvocationHandler -{ - public static final int CALL_TYPE_SYNC = 0; - public static final int CALL_TYPE_ASYNC = 1; - public static final int CALL_TYPE_CALLBACK = 2; - public static Object convertRV(String sig, Object[] rp, Method m, AbstractConnection conn) throws DBusException - { - Class c = m.getReturnType(); - - if (null == rp) { - if(null == c || Void.TYPE.equals(c)) return null; - else throw new DBusExecutionException($_("Wrong return type (got void, expected a value)")); - } else { - try { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Converting return parameters from "+Arrays.deepToString(rp)+" to type "+m.getGenericReturnType()); - rp = Marshalling.deSerializeParameters(rp, - new Type[] { m.getGenericReturnType() }, conn); - } - catch (Exception e) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - throw new DBusExecutionException(MessageFormat.format($_("Wrong return type (failed to de-serialize correct types: {0} )"), new Object[] { e.getMessage() })); - } - } - - switch (rp.length) { - case 0: - if (null == c || Void.TYPE.equals(c)) - return null; - else throw new DBusExecutionException($_("Wrong return type (got void, expected a value)")); - case 1: - return rp[0]; - default: - - // check we are meant to return multiple values - if (!Tuple.class.isAssignableFrom(c)) - throw new DBusExecutionException($_("Wrong return type (not expecting Tuple)")); - - Constructor cons = c.getConstructors()[0]; - try { - return cons.newInstance(rp); - } catch (Exception e) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - throw new DBusException(e.getMessage()); - } - } - } - @SuppressWarnings("unchecked") - public static Object executeRemoteMethod(RemoteObject ro, Method m, AbstractConnection conn, int syncmethod, CallbackHandler callback, Object... args) throws DBusExecutionException - { - Type[] ts = m.getGenericParameterTypes(); - String sig = null; - if (ts.length > 0) try { - sig = Marshalling.getDBusType(ts); - args = Marshalling.convertParameters(args, ts, conn); - } catch (DBusException DBe) { - throw new DBusExecutionException($_("Failed to construct D-Bus type: ")+DBe.getMessage()); - } - MethodCall call; - byte flags = 0; - if (!ro.autostart) flags |= Message.Flags.NO_AUTO_START; - if (syncmethod == CALL_TYPE_ASYNC) flags |= Message.Flags.ASYNC; - if (m.isAnnotationPresent(DBus.Method.NoReply.class)) flags |= Message.Flags.NO_REPLY_EXPECTED; - try { - String name; - if (m.isAnnotationPresent(DBusMemberName.class)) - name = m.getAnnotation(DBusMemberName.class).value(); - else - name = m.getName(); - if (null == ro.iface) - call = new MethodCall(ro.busname, ro.objectpath, null, name,flags, sig, args); - else { - if (null != ro.iface.getAnnotation(DBusInterfaceName.class)) { - call = new MethodCall(ro.busname, ro.objectpath, ro.iface.getAnnotation(DBusInterfaceName.class).value(), name, flags, sig, args); - } else - call = new MethodCall(ro.busname, ro.objectpath, AbstractConnection.dollar_pattern.matcher(ro.iface.getName()).replaceAll("."), name, flags, sig, args); - } - } catch (DBusException DBe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); - throw new DBusExecutionException($_("Failed to construct outgoing method call: ")+DBe.getMessage()); - } - if (null == conn.outgoing) throw new NotConnected($_("Not Connected")); - - switch (syncmethod) { - case CALL_TYPE_ASYNC: - conn.queueOutgoing(call); - return new DBusAsyncReply(call, m, conn); - case CALL_TYPE_CALLBACK: - synchronized (conn.pendingCallbacks) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Queueing Callback "+callback+" for "+call); - conn.pendingCallbacks.put(call, callback); - conn.pendingCallbackReplys.put(call, new DBusAsyncReply(call, m, conn)); - } - conn.queueOutgoing(call); - return null; - case CALL_TYPE_SYNC: - conn.queueOutgoing(call); - break; - } - - // get reply - if (m.isAnnotationPresent(DBus.Method.NoReply.class)) return null; - - Message reply = call.getReply(); - if (null == reply) throw new DBus.Error.NoReply($_("No reply within specified time")); - - if (reply instanceof Error) - ((Error) reply).throwException(); - - try { - return convertRV(reply.getSig(), reply.getParameters(), m, conn); - } catch (DBusException e) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); - throw new DBusExecutionException(e.getMessage()); - } - } - - AbstractConnection conn; - RemoteObject remote; - public RemoteInvocationHandler(AbstractConnection conn, RemoteObject remote) - { - this.remote = remote; - this.conn = conn; - } - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable - { - if (method.getName().equals("isRemote")) return true; - else if (method.getName().equals("clone")) return null; - else if (method.getName().equals("equals")) { - try { - if (1 == args.length) - return new Boolean(remote.equals(((RemoteInvocationHandler) Proxy.getInvocationHandler(args[0])).remote)); - } catch (IllegalArgumentException IAe) { - return Boolean.FALSE; - } - } - else if (method.getName().equals("finalize")) return null; - else if (method.getName().equals("getClass")) return DBusInterface.class; - else if (method.getName().equals("hashCode")) return remote.hashCode(); - else if (method.getName().equals("notify")) { - remote.notify(); - return null; - } else if (method.getName().equals("notifyAll")) { - remote.notifyAll(); - return null; - } else if (method.getName().equals("wait")) { - if (0 == args.length) remote.wait(); - else if (1 == args.length - && args[0] instanceof Long) remote.wait((Long) args[0]); - else if (2 == args.length - && args[0] instanceof Long - && args[1] instanceof Integer) - remote.wait((Long) args[0], (Integer) args[1]); - if (args.length <= 2) - return null; - } - else if (method.getName().equals("toString")) - return remote.toString(); - - return executeRemoteMethod(remote, method, conn, CALL_TYPE_SYNC, null, args); - } -} - diff --git a/app/src/main/java/org/freedesktop/dbus/RemoteObject.java b/app/src/main/java/org/freedesktop/dbus/RemoteObject.java deleted file mode 100644 index 8a1f620b..00000000 --- a/app/src/main/java/org/freedesktop/dbus/RemoteObject.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -class RemoteObject -{ - String busname; - String objectpath; - Class iface; - boolean autostart; - public RemoteObject(String busname, String objectpath, Class iface, boolean autostart) - { - this.busname = busname; - this.objectpath = objectpath; - this.iface = iface; - this.autostart = autostart; - } - public boolean equals(Object o) - { - if (!(o instanceof RemoteObject)) return false; - RemoteObject them = (RemoteObject) o; - - if (!them.objectpath.equals(this.objectpath)) return false; - - if (null == this.busname && null != them.busname) return false; - if (null != this.busname && null == them.busname) return false; - if (null != them.busname && !them.busname.equals(this.busname)) return false; - - if (null == this.iface && null != them.iface) return false; - if (null != this.iface && null == them.iface) return false; - if (null != them.iface && !them.iface.equals(this.iface)) return false; - - return true; - } - public int hashCode() - { - return (null == busname ? 0 : busname.hashCode()) + objectpath.hashCode() + - (null == iface ? 0 : iface.hashCode()); - } - public boolean autoStarting() { return autostart; } - public String getBusName() { return busname; } - public String getObjectPath() { return objectpath; } - public Class getInterface() { return iface; } - public String toString() - { - return busname+":"+objectpath+":"+iface; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/SignalTuple.java b/app/src/main/java/org/freedesktop/dbus/SignalTuple.java deleted file mode 100644 index 94cadeed..00000000 --- a/app/src/main/java/org/freedesktop/dbus/SignalTuple.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; -class SignalTuple -{ - String type; - String name; - String object; - String source; - public SignalTuple(String type, String name, String object, String source) - { - this.type = type; - this.name = name; - this.object = object; - this.source = source; - } - public boolean equals(Object o) - { - if (!(o instanceof SignalTuple)) return false; - SignalTuple other = (SignalTuple) o; - if (null == this.type && null != other.type) return false; - if (null != this.type && !this.type.equals(other.type)) return false; - if (null == this.name && null != other.name) return false; - if (null != this.name && !this.name.equals(other.name)) return false; - if (null == this.object && null != other.object) return false; - if (null != this.object && !this.object.equals(other.object)) return false; - if (null == this.source && null != other.source) return false; - if (null != this.source && !this.source.equals(other.source)) return false; - return true; - } - public int hashCode() - { - return (null == type ? 0 : type.hashCode()) - + (null == name ? 0 : name.hashCode()) - + (null == source ? 0 : source.hashCode()) - + (null == object ? 0 : object.hashCode()); - } - public String toString() - { - return "SignalTuple("+type+","+name+","+object+","+source+")"; - } -} - diff --git a/app/src/main/java/org/freedesktop/dbus/StrongReference.java b/app/src/main/java/org/freedesktop/dbus/StrongReference.java deleted file mode 100644 index c4d142dc..00000000 --- a/app/src/main/java/org/freedesktop/dbus/StrongReference.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import java.lang.ref.WeakReference; - -/** - * An alternative to a WeakReference when you don't want - * that behaviour. - */ -public class StrongReference extends WeakReference -{ - T referant; - public StrongReference(T referant) - { - super(referant); - this.referant = referant; - } - public void clear() - { - referant = null; - } - public boolean enqueue() - { - return false; - } - public T get() - { - return referant; - } - public boolean isEnqueued() - { - return false; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/Struct.java b/app/src/main/java/org/freedesktop/dbus/Struct.java deleted file mode 100644 index 7d37ed07..00000000 --- a/app/src/main/java/org/freedesktop/dbus/Struct.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -/** - * This class should be extended to create Structs. - * Any such class may be sent over DBus to a method which takes a Struct. - * All fields in the Struct which you wish to be serialized and sent to the - * remote method should be annotated with the org.freedesktop.dbus.Position - * annotation, in the order they should appear in to Struct to DBus. - */ -public abstract class Struct extends Container -{ - public Struct() {} -} diff --git a/app/src/main/java/org/freedesktop/dbus/Transport.java b/app/src/main/java/org/freedesktop/dbus/Transport.java deleted file mode 100644 index a7689a7d..00000000 --- a/app/src/main/java/org/freedesktop/dbus/Transport.java +++ /dev/null @@ -1,827 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.lang.reflect.Method; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.text.ParseException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Random; -import java.util.Vector; -import cx.ath.matthew.unix.UnixSocket; -import cx.ath.matthew.unix.UnixServerSocket; -import cx.ath.matthew.unix.UnixSocketAddress; -import cx.ath.matthew.utils.Hexdump; -import cx.ath.matthew.debug.Debug; - -import android.icu.text.Collator; - -public class Transport -{ - public static class SASL - { - public static class Command - { - private int command; - private int mechs; - private String data; - private String response; - public Command() - { - } - public Command(String s) throws IOException - { - String[] ss = s.split(" "); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Creating command from: "+Arrays.toString(ss)); - if (0 == col.compare(ss[0], "OK")) { - command = COMMAND_OK; - data = ss[1]; - } else if (0 == col.compare(ss[0], "AUTH")) { - command = COMMAND_AUTH; - if (ss.length > 1) { - if (0 == col.compare(ss[1], "EXTERNAL")) - mechs = AUTH_EXTERNAL; - else if (0 == col.compare(ss[1], "DBUS_COOKIE_SHA1")) - mechs = AUTH_SHA; - else if (0 == col.compare(ss[1], "ANONYMOUS")) - mechs = AUTH_ANON; - } - if (ss.length > 2) - data = ss[2]; - } else if (0 == col.compare(ss[0], "DATA")) { - command = COMMAND_DATA; - data = ss[1]; - } else if (0 == col.compare(ss[0], "REJECTED")) { - command = COMMAND_REJECTED; - for (int i = 1; i < ss.length; i++) - if (0 == col.compare(ss[i], "EXTERNAL")) - mechs |= AUTH_EXTERNAL; - else if (0 == col.compare(ss[i], "DBUS_COOKIE_SHA1")) - mechs |= AUTH_SHA; - else if (0 == col.compare(ss[i], "ANONYMOUS")) - mechs |= AUTH_ANON; - } else if (0 == col.compare(ss[0], "BEGIN")) { - command = COMMAND_BEGIN; - } else if (0 == col.compare(ss[0], "CANCEL")) { - command = COMMAND_CANCEL; - } else if (0 == col.compare(ss[0], "ERROR")) { - command = COMMAND_ERROR; - data = ss[1]; - } else { - throw new IOException($_("Invalid Command ")+ss[0]); - } - if (Debug.debug) Debug.print(Debug.VERBOSE, "Created command: "+this); - } - public int getCommand() { return command; } - public int getMechs() { return mechs; } - public String getData() { return data; } - public String getResponse() { return response; } - public void setResponse(String s) { response = s; } - public String toString() - { - return "Command("+command+", "+mechs+", "+data+", "+null+")"; - } - } - private static Collator col = Collator.getInstance(); - static { - col.setStrength(Collator.PRIMARY); - } - public static final int LOCK_TIMEOUT = 1000; - public static final int NEW_KEY_TIMEOUT_SECONDS = 60 * 5; - public static final int EXPIRE_KEYS_TIMEOUT_SECONDS = NEW_KEY_TIMEOUT_SECONDS + (60 * 2); - public static final int MAX_TIME_TRAVEL_SECONDS = 60 * 5; - public static final int COOKIE_TIMEOUT = 240; - public static final String COOKIE_CONTEXT = "org_freedesktop_java"; - - private String findCookie(String context, String ID) throws IOException - { - String homedir = System.getProperty("user.home"); - File f = new File(homedir+"/.dbus-keyrings/"+context); - BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f))); - String s = null; - String cookie = null; - long now = System.currentTimeMillis()/1000; - while (null != (s = r.readLine())) { - String[] line = s.split(" "); - long timestamp = Long.parseLong(line[1]); - if (line[0].equals(ID) && (! (timestamp < 0 || - (now + MAX_TIME_TRAVEL_SECONDS) < timestamp || - (now - EXPIRE_KEYS_TIMEOUT_SECONDS) > timestamp))) { - cookie = line[2]; - break; - } - } - r.close(); - return cookie; - } - private void addCookie(String context, String ID, long timestamp, String cookie) throws IOException - { - String homedir = System.getProperty("user.home"); - File keydir = new File(homedir+"/.dbus-keyrings/"); - File cookiefile = new File(homedir+"/.dbus-keyrings/"+context); - File lock = new File(homedir+"/.dbus-keyrings/"+context+".lock"); - File temp = new File(homedir+"/.dbus-keyrings/"+context+".temp"); - - // ensure directory exists - if (!keydir.exists()) keydir.mkdirs(); - - // acquire lock - long start = System.currentTimeMillis(); - while (!lock.createNewFile() && LOCK_TIMEOUT > (System.currentTimeMillis()-start)); - - // read old file - Vector lines = new Vector(); - if (cookiefile.exists()) { - BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(cookiefile))); - String s = null; - while (null != (s = r.readLine())) { - String[] line = s.split(" "); - long time = Long.parseLong(line[1]); - // expire stale cookies - if ((timestamp - time) < COOKIE_TIMEOUT) - lines.add(s); - } - r.close(); - } - - // add cookie - lines.add(ID+" "+timestamp+" "+cookie); - - // write temp file - PrintWriter w = new PrintWriter(new FileOutputStream(temp)); - for (String l: lines) - w.println(l); - w.close(); - - // atomically move to old file - if (!temp.renameTo(cookiefile)) { - cookiefile.delete(); - temp.renameTo(cookiefile); - } - - // remove lock - lock.delete(); - } - /** - * Takes the string, encodes it as hex and then turns it into a string again. - * No, I don't know why either. - */ - private String stupidlyEncode(String data) - { - return Hexdump.toHex(data.getBytes()).replaceAll(" ",""); - } - private String stupidlyEncode(byte[] data) - { - return Hexdump.toHex(data).replaceAll(" ",""); - } - private byte getNibble(char c) - { - switch (c) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return (byte) (c-'0'); - case 'A': - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': - return (byte) (c-'A'+10); - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - return (byte) (c-'a'+10); - default: - return 0; - } - } - private String stupidlyDecode(String data) - { - char[] cs = new char[data.length()]; - char[] res = new char[cs.length/2]; - data.getChars(0, data.length(), cs, 0); - for (int i = 0, j = 0; j < res.length; i += 2, j++) { - int b = 0; - b |= getNibble(cs[i])<<4; - b |= getNibble(cs[i+1]); - res[j] = (char) b; - } - return new String(res); - } - - public static final int MODE_SERVER=1; - public static final int MODE_CLIENT=2; - - public static final int AUTH_NONE=0; - public static final int AUTH_EXTERNAL=1; - public static final int AUTH_SHA=2; - public static final int AUTH_ANON=4; - - public static final int COMMAND_AUTH=1; - public static final int COMMAND_DATA=2; - public static final int COMMAND_REJECTED=3; - public static final int COMMAND_OK=4; - public static final int COMMAND_BEGIN=5; - public static final int COMMAND_CANCEL=6; - public static final int COMMAND_ERROR=7; - - public static final int INITIAL_STATE=0; - public static final int WAIT_DATA=1; - public static final int WAIT_OK=2; - public static final int WAIT_REJECT=3; - public static final int WAIT_AUTH=4; - public static final int WAIT_BEGIN=5; - public static final int AUTHENTICATED=6; - public static final int FAILED=7; - - public static final int OK=1; - public static final int CONTINUE=2; - public static final int ERROR=3; - public static final int REJECT=4; - - public Command receive(InputStream s) throws IOException - { - StringBuffer sb = new StringBuffer(); - top: while (true) { - int c = s.read(); - switch (c) { - case -1: - throw new IOException("Stream unexpectedly short (broken pipe)"); - case 0: - case '\r': - continue; - case '\n': - break top; - default: - sb.append((char) c); - } - } - if (Debug.debug) Debug.print(Debug.VERBOSE, "received: "+sb); - try { - return new Command(sb.toString()); - } catch (Exception e) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, e); - return new Command(); - } - } - public void send(OutputStream out, int command, String... data) throws IOException - { - StringBuffer sb = new StringBuffer(); - switch (command) { - case COMMAND_AUTH: - sb.append("AUTH"); - break; - case COMMAND_DATA: - sb.append("DATA"); - break; - case COMMAND_REJECTED: - sb.append("REJECTED"); - break; - case COMMAND_OK: - sb.append("OK"); - break; - case COMMAND_BEGIN: - sb.append("BEGIN"); - break; - case COMMAND_CANCEL: - sb.append("CANCEL"); - break; - case COMMAND_ERROR: - sb.append("ERROR"); - break; - default: - return; - } - for (String s: data) { - sb.append(' '); - sb.append(s); - } - sb.append('\r'); - sb.append('\n'); - if (Debug.debug) Debug.print(Debug.VERBOSE, "sending: "+sb); - out.write(sb.toString().getBytes()); - } - public int do_challenge(int auth, Command c) throws IOException - { - switch (auth) { - case AUTH_SHA: - String[] reply=stupidlyDecode(c.getData()).split(" "); - if (Debug.debug) Debug.print(Debug.VERBOSE, Arrays.toString(reply)); - if (3 != reply.length) { - if (Debug.debug) Debug.print(Debug.DEBUG, "Reply is not length 3"); - return ERROR; - } - String context = reply[0]; - String ID = reply[1]; - String serverchallenge = reply[2]; - MessageDigest md = null; - try { - md = MessageDigest.getInstance("SHA"); - } catch (NoSuchAlgorithmException NSAe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe); - return ERROR; - } - byte[] buf = new byte[8]; - Message.marshallintBig(System.currentTimeMillis(), buf, 0, 8); - String clientchallenge = stupidlyEncode(md.digest(buf)); - md.reset(); - long start = System.currentTimeMillis(); - String cookie = null; - while (null == cookie && (System.currentTimeMillis()-start) < LOCK_TIMEOUT) - cookie = findCookie(context, ID); - if (null == cookie) { - if (Debug.debug) Debug.print(Debug.DEBUG, "Did not find a cookie in context "+context+" with ID "+ID); - return ERROR; - } - String response = serverchallenge+":"+clientchallenge+":"+cookie; - buf = md.digest(response.getBytes()); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Response: "+response+" hash: "+Hexdump.format(buf)); - response = stupidlyEncode(buf); - c.setResponse(stupidlyEncode(clientchallenge+" "+response)); - return OK; - default: - if (Debug.debug) Debug.print(Debug.DEBUG, "Not DBUS_COOKIE_SHA1 authtype."); - return ERROR; - } - } - public String challenge = ""; - public String cookie = ""; - public int do_response(int auth, String Uid, String kernelUid, Command c) - { - MessageDigest md = null; - try { - md = MessageDigest.getInstance("SHA"); - } catch (NoSuchAlgorithmException NSAe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe); - return ERROR; - } - switch (auth) { - case AUTH_NONE: - switch (c.getMechs()) { - case AUTH_ANON: - return OK; - case AUTH_EXTERNAL: - if (0 == col.compare(Uid, c.getData()) && - (null == kernelUid || 0 == col.compare(Uid, kernelUid))) - return OK; - else - return ERROR; - case AUTH_SHA: - String context = COOKIE_CONTEXT; - long id = System.currentTimeMillis(); - byte[] buf = new byte[8]; - Message.marshallintBig(id, buf, 0, 8); - challenge = stupidlyEncode(md.digest(buf)); - Random r = new Random(); - r.nextBytes(buf); - cookie = stupidlyEncode(md.digest(buf)); - try { addCookie(context, ""+id, id/1000, cookie); - } catch (IOException IOe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe); - } - if (Debug.debug) Debug.print(Debug.DEBUG, "Sending challenge: "+context+' '+id+' '+challenge); - c.setResponse(stupidlyEncode(context+' '+id+' '+challenge)); - return CONTINUE; - default: - return ERROR; - } - case AUTH_SHA: - String[] response = stupidlyDecode(c.getData()).split(" "); - if (response.length < 2) return ERROR; - String cchal = response[0]; - String hash = response[1]; - String prehash = challenge+":"+cchal+":"+cookie; - byte[] buf = md.digest(prehash.getBytes()); - String posthash = stupidlyEncode(buf); - if (Debug.debug) Debug.print(Debug.DEBUG, "Authenticating Hash; data="+prehash+" remote hash="+hash+" local hash="+posthash); - if (0 == col.compare(posthash, hash)) - return OK; - else - return ERROR; - default: - return ERROR; - } - } - public String[] getTypes(int types) - { - switch (types) { - case AUTH_EXTERNAL: - return new String[] { "EXTERNAL" }; - case AUTH_SHA: - return new String[] { "DBUS_COOKIE_SHA1" }; - case AUTH_ANON: - return new String[] { "ANONYMOUS" }; - case AUTH_SHA+AUTH_EXTERNAL: - return new String[] { "EXTERNAL", "DBUS_COOKIE_SHA1" }; - case AUTH_SHA+AUTH_ANON: - return new String[] { "ANONYMOUS", "DBUS_COOKIE_SHA1" }; - case AUTH_EXTERNAL+AUTH_ANON: - return new String[] { "ANONYMOUS", "EXTERNAL" }; - case AUTH_EXTERNAL+AUTH_ANON+AUTH_SHA: - return new String[] { "ANONYMOUS", "EXTERNAL", "DBUS_COOKIE_SHA1" }; - default: - return new String[] { }; - } - } - /** - * performs SASL auth on the given streams. - * Mode selects whether to run as a SASL server or client. - * Types is a bitmask of the available auth types. - * Returns true if the auth was successful and false if it failed. - */ - @SuppressWarnings("unchecked") - public boolean auth(int mode, int types, String guid, OutputStream out, InputStream in, UnixSocket us) throws IOException - { - String username = System.getProperty("user.name"); - String Uid = null; - String kernelUid = null; - try { - Class c = Class.forName("com.sun.security.auth.module.UnixSystem"); - Method m = c.getMethod("getUid"); - Object o = c.newInstance(); - long uid = (Long) m.invoke(o); - Uid = stupidlyEncode(""+uid); - } catch (Exception e) { - Uid = stupidlyEncode(username); - } - Command c; - int failed = 0; - int current = 0; - int state = INITIAL_STATE; - - while (state != AUTHENTICATED && state != FAILED) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "AUTH state: "+state); - switch (mode) { - case MODE_CLIENT: - switch (state) { - case INITIAL_STATE: - if (null == us) - out.write(new byte[] { 0 }); - else - us.sendCredentialByte((byte) 0); - send(out, COMMAND_AUTH); - state = WAIT_DATA; - break; - case WAIT_DATA: - c = receive(in); - switch (c.getCommand()) { - case COMMAND_DATA: - switch (do_challenge(current, c)) { - case CONTINUE: - send(out, COMMAND_DATA, c.getResponse()); - break; - case OK: - send(out, COMMAND_DATA, c.getResponse()); - state = WAIT_OK; - break; - case ERROR: - send(out, COMMAND_ERROR, c.getResponse()); - break; - } - break; - case COMMAND_REJECTED: - failed |= current; - int available = c.getMechs() & (~failed); -// if (0 != (available & AUTH_EXTERNAL)){ -// send(out, COMMAND_AUTH, "EXTERNAL", Uid); -// current = AUTH_EXTERNAL; -// } else if (0 != (available & AUTH_SHA)) { -// send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid); -// current = AUTH_SHA; -// } else - if (0 != (available & AUTH_ANON)) { - send(out, COMMAND_AUTH, "ANONYMOUS"); - current = AUTH_ANON; - } - else state = FAILED; - break; - case COMMAND_ERROR: - send(out, COMMAND_CANCEL); - state = WAIT_REJECT; - break; - case COMMAND_OK: - send(out, COMMAND_BEGIN); - state = AUTHENTICATED; - break; - default: - send(out, COMMAND_ERROR, "Got invalid command"); - break; - } - break; - case WAIT_OK: - c = receive(in); - switch (c.getCommand()) { - case COMMAND_OK: - send(out, COMMAND_BEGIN); - state = AUTHENTICATED; - break; - case COMMAND_ERROR: - case COMMAND_DATA: - send(out, COMMAND_CANCEL); - state = WAIT_REJECT; - break; - case COMMAND_REJECTED: - failed |= current; - int available = c.getMechs() & (~failed); - state = WAIT_DATA; - if (0 != (available & AUTH_EXTERNAL)) { - send(out, COMMAND_AUTH, "EXTERNAL", Uid); - current = AUTH_EXTERNAL; - } else if (0 != (available & AUTH_SHA)) { - send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid); - current = AUTH_SHA; - } else if (0 != (available & AUTH_ANON)) { - send(out, COMMAND_AUTH, "ANONYMOUS"); - current = AUTH_ANON; - } - else state = FAILED; - break; - default: - send(out, COMMAND_ERROR, "Got invalid command"); - break; - } - break; - case WAIT_REJECT: - c = receive(in); - switch (c.getCommand()) { - case COMMAND_REJECTED: - failed |= current; - int available = c.getMechs() & (~failed); - if (0 != (available & AUTH_EXTERNAL)) { - send(out, COMMAND_AUTH, "EXTERNAL", Uid); - current = AUTH_EXTERNAL; - } else if (0 != (available & AUTH_SHA)) { - send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid); - current = AUTH_SHA; - } else if (0 != (available & AUTH_ANON)) { - send(out, COMMAND_AUTH, "ANONYMOUS"); - current = AUTH_ANON; - } - else state = FAILED; - break; - default: - state = FAILED; - break; - } - break; - default: - state = FAILED; - } - break; - case MODE_SERVER: - switch (state) { - case INITIAL_STATE: - byte[] buf = new byte[1]; - if (null == us) { - in.read(buf); - } else { - buf[0] = us.recvCredentialByte(); - int kuid = us.getPeerUID(); - if (kuid >= 0) - kernelUid = stupidlyEncode(""+kuid); - } - if (0 != buf[0]) state = FAILED; - else state = WAIT_AUTH; - break; - case WAIT_AUTH: - c = receive(in); - switch (c.getCommand()) { - case COMMAND_AUTH: - if (null == c.getData()) { - send(out, COMMAND_REJECTED, getTypes(types)); - } else { - switch (do_response(current, Uid, kernelUid, c)) { - case CONTINUE: - send(out, COMMAND_DATA, c.getResponse()); - current = c.getMechs(); - state = WAIT_DATA; - break; - case OK: - send(out, COMMAND_OK, guid); - state = WAIT_BEGIN; - current = 0; - break; - case REJECT: - send(out, COMMAND_REJECTED, getTypes(types)); - current = 0; - break; - } - } - break; - case COMMAND_ERROR: - send(out, COMMAND_REJECTED, getTypes(types)); - break; - case COMMAND_BEGIN: - state = FAILED; - break; - default: - send(out, COMMAND_ERROR, "Got invalid command"); - break; - } - break; - case WAIT_DATA: - c = receive(in); - switch (c.getCommand()) { - case COMMAND_DATA: - switch (do_response(current, Uid, kernelUid, c)) { - case CONTINUE: - send(out, COMMAND_DATA, c.getResponse()); - state = WAIT_DATA; - break; - case OK: - send(out, COMMAND_OK, guid); - state = WAIT_BEGIN; - current = 0; - break; - case REJECT: - send(out, COMMAND_REJECTED, getTypes(types)); - current = 0; - break; - } - break; - case COMMAND_ERROR: - case COMMAND_CANCEL: - send(out, COMMAND_REJECTED, getTypes(types)); - state = WAIT_AUTH; - break; - case COMMAND_BEGIN: - state = FAILED; - break; - default: - send(out, COMMAND_ERROR, "Got invalid command"); - break; - } - break; - case WAIT_BEGIN: - c = receive(in); - switch (c.getCommand()) { - case COMMAND_ERROR: - case COMMAND_CANCEL: - send(out, COMMAND_REJECTED, getTypes(types)); - state = WAIT_AUTH; - break; - case COMMAND_BEGIN: - state = AUTHENTICATED; - break; - default: - send(out, COMMAND_ERROR, "Got invalid command"); - break; - } - break; - default: - state = FAILED; - } - break; - default: - return false; - } - } - - return state == AUTHENTICATED; - } - } - public MessageReader min; - public MessageWriter mout; - public Transport() {} - public static String genGUID() - { - Random r = new Random(); - byte[] buf = new byte[16]; - r.nextBytes(buf); - String guid = Hexdump.toHex(buf); - return guid.replaceAll(" ", ""); - } - public Transport(BusAddress address) throws IOException - { - connect(address); - } - public Transport(String address) throws IOException, ParseException - { - connect(new BusAddress(address)); - } - public Transport(String address, int timeout) throws IOException, ParseException - { - connect(new BusAddress(address), timeout); - } - public void connect(String address) throws IOException, ParseException - { - connect(new BusAddress(address), 0); - } - public void connect(String address, int timeout) throws IOException, ParseException - { - connect(new BusAddress(address), timeout); - } - public void connect(BusAddress address) throws IOException - { - connect(address, 0); - } - public void connect(BusAddress address, int timeout) throws IOException - { - if (Debug.debug) Debug.print(Debug.INFO, "Connecting to "+address); - OutputStream out = null; - InputStream in = null; - UnixSocket us = null; - Socket s = null; - int mode = 0; - int types = 0; - if ("unix".equals(address.getType())) { - types = SASL.AUTH_EXTERNAL; - if (null != address.getParameter("listen")) { - mode = SASL.MODE_SERVER; - UnixServerSocket uss = new UnixServerSocket(); - if (null != address.getParameter("abstract")) - uss.bind(new UnixSocketAddress(address.getParameter("abstract"), true)); - else if (null != address.getParameter("path")) - uss.bind(new UnixSocketAddress(address.getParameter("path"), false)); - us = uss.accept(); - } else { - mode = SASL.MODE_CLIENT; - us = new UnixSocket(); - if (null != address.getParameter("abstract")) - us.connect(new UnixSocketAddress(address.getParameter("abstract"), true)); - else if (null != address.getParameter("path")) - us.connect(new UnixSocketAddress(address.getParameter("path"), false)); - } - us.setPassCred(true); - in = us.getInputStream(); - out = us.getOutputStream(); - } else if ("tcp".equals(address.getType())) { - types = SASL.AUTH_SHA; - if (null != address.getParameter("listen")) { - mode = SASL.MODE_SERVER; - ServerSocket ss = new ServerSocket(); - ss.bind(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port")))); - s = ss.accept(); - } else { - types = SASL.AUTH_ANON; - mode = SASL.MODE_CLIENT; - s = new Socket(); - s.connect(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port")))); - } - in = s.getInputStream(); - out = s.getOutputStream(); - } else { - throw new IOException($_("unknown address type ")+address.getType()); - } - - if (!(new SASL()).auth(mode, types, address.getParameter("guid"), out, in, us)) { - out.close(); - throw new IOException($_("Failed to auth")); - } - if (null != us) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting timeout to "+timeout+" on Socket"); - if (timeout == 1) - us.setBlocking(false); - else - us.setSoTimeout(timeout); - } - if (null != s) { - if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting timeout to "+timeout+" on Socket"); - s.setSoTimeout(timeout); - } - mout = new MessageWriter(out); - min = new MessageReader(in); - } - public void disconnect() throws IOException - { - if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting Transport"); - min.close(); - mout.close(); - } -} - - diff --git a/app/src/main/java/org/freedesktop/dbus/Tuple.java b/app/src/main/java/org/freedesktop/dbus/Tuple.java deleted file mode 100644 index becfc60c..00000000 --- a/app/src/main/java/org/freedesktop/dbus/Tuple.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -/** - * This class should be extended to create Tuples. - * Any such class may be used as the return type for a method - * which returns multiple values. - * All fields in the Tuple which you wish to be serialized and sent to the - * remote method should be annotated with the org.freedesktop.dbus.Position - * annotation, in the order they should appear to DBus. - */ -public abstract class Tuple extends Container -{ - public Tuple() {} -} diff --git a/app/src/main/java/org/freedesktop/dbus/TypeSignature.java b/app/src/main/java/org/freedesktop/dbus/TypeSignature.java deleted file mode 100644 index c297821d..00000000 --- a/app/src/main/java/org/freedesktop/dbus/TypeSignature.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import java.lang.reflect.Type; -import org.freedesktop.dbus.exceptions.DBusException; - -public class TypeSignature -{ - String sig; - public TypeSignature(String sig) - { - this.sig = sig; - } - public TypeSignature(Type[] types) throws DBusException - { - StringBuffer sb = new StringBuffer(); - for (Type t: types) { - String[] ts = Marshalling.getDBusType(t); - for (String s: ts) - sb.append(s); - } - this.sig = sb.toString(); - } - public String getSig() - { - return sig; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/UInt16.java b/app/src/main/java/org/freedesktop/dbus/UInt16.java deleted file mode 100644 index a47e2dcc..00000000 --- a/app/src/main/java/org/freedesktop/dbus/UInt16.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.text.MessageFormat; - -/** - * Class to represent 16-bit unsigned integers. - */ -@SuppressWarnings("serial") -public class UInt16 extends Number implements Comparable -{ - /** Maximum possible value. */ - public static final int MAX_VALUE = 65535; - /** Minimum possible value. */ - public static final int MIN_VALUE = 0; - private int value; - /** Create a UInt16 from an int. - * @param value Must be within MIN_VALUE–MAX_VALUE - * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE - */ - public UInt16(int value) - { - if (value < MIN_VALUE || value > MAX_VALUE) - throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_VALUE})); - this.value = value; - } - /** Create a UInt16 from a String. - * @param value Must parse to a valid integer within MIN_VALUE–MAX_VALUE - * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_VALUE - */ - public UInt16(String value) - { - this(Integer.parseInt(value)); - } - /** The value of this as a byte. */ - public byte byteValue() { return (byte) value; } - /** The value of this as a double. */ - public double doubleValue() { return (double) value; } - /** The value of this as a float. */ - public float floatValue() { return (float) value; } - /** The value of this as a int. */ - public int intValue() { return /*(int)*/ value; } - /** The value of this as a long. */ - public long longValue() { return (long) value; } - /** The value of this as a short. */ - public short shortValue(){ return (short) value; } - /** Test two UInt16s for equality. */ - public boolean equals(Object o) - { - return o instanceof UInt16 && ((UInt16) o).value == this.value; - } - public int hashCode() - { - return /*(int)*/ value; - } - /** Compare two UInt16s. - * @return 0 if equal, -ve or +ve if they are different. - */ - public int compareTo(UInt16 other) - { - return /*(int)*/ (this.value - other.value); - } - /** The value of this as a string. */ - public String toString() - { - return ""+value; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/UInt32.java b/app/src/main/java/org/freedesktop/dbus/UInt32.java deleted file mode 100644 index f2afc3dd..00000000 --- a/app/src/main/java/org/freedesktop/dbus/UInt32.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.text.MessageFormat; - -/** - * Class to represent unsigned 32-bit numbers. - */ -@SuppressWarnings("serial") -public class UInt32 extends Number implements Comparable -{ - /** Maximum allowed value */ - public static final long MAX_VALUE = 4294967295L; - /** Minimum allowed value */ - public static final long MIN_VALUE = 0; - private long value; - /** Create a UInt32 from a long. - * @param value Must be a valid integer within MIN_VALUE–MAX_VALUE - * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE - */ - public UInt32(long value) - { - if (value < MIN_VALUE || value > MAX_VALUE) - throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_VALUE})); - this.value = value; - } - /** Create a UInt32 from a String. - * @param value Must parse to a valid integer within MIN_VALUE–MAX_VALUE - * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_VALUE - */ - public UInt32(String value) - { - this(Long.parseLong(value)); - } - - public static UInt32 fromUnsignedInt(int value) { - return new UInt32(Long.parseUnsignedLong(Integer.toUnsignedString(value, 16), 16)); - } - /** The value of this as a byte. */ - public byte byteValue() { return (byte) value; } - /** The value of this as a double. */ - public double doubleValue() { return (double) value; } - /** The value of this as a float. */ - public float floatValue() { return (float) value; } - /** The value of this as a int. */ - public int intValue() { return (int) value; } - /** The value of this as a long. */ - public long longValue() { return /*(long)*/ value; } - /** The value of this as a short. */ - public short shortValue(){ return (short) value; } - /** Test two UInt32s for equality. */ - public boolean equals(Object o) - { - return o instanceof UInt32 && ((UInt32) o).value == this.value; - } - public int hashCode() - { - return (int) value; - } - /** Compare two UInt32s. - * @return 0 if equal, -ve or +ve if they are different. - */ - public int compareTo(UInt32 other) - { - return (int) (this.value - other.value); - } - /** The value of this as a string */ - public String toString() - { - return ""+value; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/UInt64.java b/app/src/main/java/org/freedesktop/dbus/UInt64.java deleted file mode 100644 index 1fe745f9..00000000 --- a/app/src/main/java/org/freedesktop/dbus/UInt64.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.math.BigInteger; - -import java.text.MessageFormat; - -/** - * Class to represent unsigned 64-bit numbers. - * Warning: Any functions which take or return a long - * are restricted to the range of a signed 64bit number. - * Use the BigInteger methods if you wish access to the full - * range. - */ -@SuppressWarnings("serial") -public class UInt64 extends Number implements Comparable -{ - /** Maximum allowed value (when accessed as a long) */ - public static final long MAX_LONG_VALUE = Long.MAX_VALUE; - /** Maximum allowed value (when accessed as a BigInteger) */ - public static final BigInteger MAX_BIG_VALUE = new BigInteger("18446744073709551615"); - /** Minimum allowed value */ - public static final long MIN_VALUE = 0; - private BigInteger value; - private long top; - private long bottom; - /** Create a UInt64 from a long. - * @param value Must be a valid integer within MIN_VALUE–MAX_VALUE - * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE - */ - public UInt64(long value) - { - if (value < MIN_VALUE || value > MAX_LONG_VALUE) - throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_LONG_VALUE})); - this.value = new BigInteger(""+value); - this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue(); - this.bottom = this.value.and(new BigInteger("4294967295")).longValue(); - } - /** - * Create a UInt64 from two longs. - * @param top Most significant 4 bytes. - * @param bottom Least significant 4 bytes. - */ - public UInt64(long top, long bottom) - { - BigInteger a = new BigInteger(""+top); - a = a.shiftLeft(32); - a = a.add(new BigInteger(""+bottom)); - if (0 > a.compareTo(BigInteger.ZERO)) - throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { a, MIN_VALUE, MAX_BIG_VALUE})); - if (0 < a.compareTo(MAX_BIG_VALUE)) - throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { a, MIN_VALUE, MAX_BIG_VALUE})); - this.value = a; - this.top = top; - this.bottom = bottom; - } - /** Create a UInt64 from a BigInteger - * @param value Must be a valid BigInteger between MIN_VALUE–MAX_BIG_VALUE - * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_BIG_VALUE - */ - public UInt64(BigInteger value) - { - if (null == value) - throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); - if (0 > value.compareTo(BigInteger.ZERO)) - throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); - if (0 < value.compareTo(MAX_BIG_VALUE)) - throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); - this.value = value; - this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue(); - this.bottom = this.value.and(new BigInteger("4294967295")).longValue(); - } - /** Create a UInt64 from a String. - * @param value Must parse to a valid integer within MIN_VALUE–MAX_BIG_VALUE - * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_BIG_VALUE - */ - public UInt64(String value) - { - if (null == value) - throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); - BigInteger a = new BigInteger(value); - if (0 > a.compareTo(BigInteger.ZERO)) - throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); - if (0 < a.compareTo(MAX_BIG_VALUE)) - throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); - this.value = a; - this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue(); - this.bottom = this.value.and(new BigInteger("4294967295")).longValue(); - } - /** The value of this as a BigInteger. */ - public BigInteger value() { return value; } - /** The value of this as a byte. */ - public byte byteValue() { return value.byteValue(); } - /** The value of this as a double. */ - public double doubleValue() { return value.doubleValue(); } - /** The value of this as a float. */ - public float floatValue() { return value.floatValue(); } - /** The value of this as a int. */ - public int intValue() { return value.intValue(); } - /** The value of this as a long. */ - public long longValue() { return value.longValue(); } - /** The value of this as a short. */ - public short shortValue(){ return value.shortValue(); } - /** Test two UInt64s for equality. */ - public boolean equals(Object o) - { - return o instanceof UInt64 && this.value.equals(((UInt64) o).value); - } - public int hashCode() - { - return value.hashCode(); - } - /** Compare two UInt32s. - * @return 0 if equal, -ve or +ve if they are different. - */ - public int compareTo(UInt64 other) - { - return this.value.compareTo(other.value); - } - /** The value of this as a string. */ - public String toString() - { - return value.toString(); - } - /** - * Most significant 4 bytes. - */ - public long top() - { - return top; - } - /** - * Least significant 4 bytes. - */ - public long bottom() - { - return bottom; - } -} - diff --git a/app/src/main/java/org/freedesktop/dbus/Variant.java b/app/src/main/java/org/freedesktop/dbus/Variant.java deleted file mode 100644 index c729685c..00000000 --- a/app/src/main/java/org/freedesktop/dbus/Variant.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus; - -import static org.freedesktop.dbus.Gettext.$_; - -import java.lang.reflect.Type; -import java.text.MessageFormat; -import java.util.Vector; -import org.freedesktop.dbus.exceptions.DBusException; - -import cx.ath.matthew.debug.Debug; - -/** - * A Wrapper class for Variant values. - * A method on DBus can send or receive a Variant. - * This will wrap another value whose type is determined at runtime. - * The Variant may be parameterized to restrict the types it may accept. - */ -public class Variant -{ - private final T o; - private final Type type; - private final String sig; - /** - * Create a Variant from a basic type object. - * @param o The wrapped value. - * @throws IllegalArugmentException If you try and wrap Null or an object of a non-basic type. - */ - public Variant(T o) throws IllegalArgumentException - { - if (null == o) throw new IllegalArgumentException($_("Can't wrap Null in a Variant")); - type = o.getClass(); - try { - String[] ss = Marshalling.getDBusType(o.getClass(), true); - if (ss.length != 1) - throw new IllegalArgumentException($_("Can't wrap a multi-valued type in a Variant: ")+type); - this.sig = ss[0]; - } catch (DBusException DBe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); - throw new IllegalArgumentException(MessageFormat.format($_("Can't wrap {0} in an unqualified Variant ({1})."), new Object[] { o.getClass(), DBe.getMessage() })); - } - this.o = o; - } - /** - * Create a Variant. - * @param o The wrapped value. - * @param type The explicit type of the value. - * @throws IllegalArugmentException If you try and wrap Null or an object which cannot be sent over DBus. - */ - public Variant(T o, Type type) throws IllegalArgumentException - { - if (null == o) throw new IllegalArgumentException($_("Can't wrap Null in a Variant")); - this.type = type; - try { - String[] ss = Marshalling.getDBusType(type); - if (ss.length != 1) - throw new IllegalArgumentException($_("Can't wrap a multi-valued type in a Variant: ")+type); - this.sig = ss[0]; - } catch (DBusException DBe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); - throw new IllegalArgumentException(MessageFormat.format($_("Can't wrap {0} in an unqualified Variant ({1})."), new Object[] { type, DBe.getMessage() })); - } - this.o = o; - } - /** - * Create a Variant. - * @param o The wrapped value. - * @param sig The explicit type of the value, as a dbus type string. - * @throws IllegalArugmentException If you try and wrap Null or an object which cannot be sent over DBus. - */ - public Variant(T o, String sig) throws IllegalArgumentException - { - if (null == o) throw new IllegalArgumentException($_("Can't wrap Null in a Variant")); - this.sig = sig; - try { - Vector ts = new Vector(); - Marshalling.getJavaType(sig, ts,1); - if (ts.size() != 1) - throw new IllegalArgumentException($_("Can't wrap multiple or no types in a Variant: ")+sig); - this.type = ts.get(0); - } catch (DBusException DBe) { - if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); - throw new IllegalArgumentException(MessageFormat.format($_("Can't wrap {0} in an unqualified Variant ({1})."), new Object[] { sig, DBe.getMessage() })); - } - this.o = o; - } - /** Return the wrapped value. */ - public T getValue() { return o; } - /** Return the type of the wrapped value. */ - public Type getType() { return type; } - /** Return the dbus signature of the wrapped value. */ - public String getSig() { return sig; } - /** Format the Variant as a string. */ - public String toString() { return "["+o+"]"; } - /** Compare this Variant with another by comparing contents */ - @SuppressWarnings("unchecked") - public boolean equals(Object other) - { - if (null == other) return false; - if (!(other instanceof Variant)) return false; - return this.o.equals(((Variant)other).o); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/Caller.java b/app/src/main/java/org/freedesktop/dbus/bin/Caller.java deleted file mode 100644 index ecd6002f..00000000 --- a/app/src/main/java/org/freedesktop/dbus/bin/Caller.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.bin; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.Vector; -import java.io.File; -import org.freedesktop.dbus.BusAddress; -import org.freedesktop.dbus.Error; -import org.freedesktop.dbus.Marshalling; -import org.freedesktop.dbus.Message; -import org.freedesktop.dbus.MethodCall; -import org.freedesktop.dbus.Transport; -import cx.ath.matthew.debug.Debug; - -public class Caller -{ - @SuppressWarnings("unchecked") - public static void main(String[] args) - { - try { - if (Debug.debug) { - Debug.setHexDump(true); - Debug.loadConfig(new File("debug.conf")); - } - if (args.length < 4) { - System.out.println ("Syntax: Caller [ ]"); - System.exit(1); - } - String addr = System.getenv("DBUS_SESSION_BUS_ADDRESS"); - BusAddress address = new BusAddress(addr); - Transport conn = new Transport(address); - - Message m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello", (byte) 0, null);; - conn.mout.writeMessage(m); - - if ("".equals(args[2])) args[2] = null; - if (args.length == 4) - m = new MethodCall(args[0], args[1], args[2], args[3], (byte) 0, null); - else { - Vector lts = new Vector(); - Marshalling.getJavaType(args[4],lts, -1); - Type[] ts = lts.toArray(new Type[0]); - Object[] os = new Object[args.length-5]; - for (int i = 5; i < args.length; i++) { - if (ts[i-5] instanceof Class) { - try { - Constructor c = ((Class) ts[i-5]).getConstructor(String.class); - os[i-5] = c.newInstance(args[i]); - } catch (Exception e) { - os[i-5] = args[i]; - } - } else - os[i-5] = args[i]; - } - m = new MethodCall(args[0], args[1], args[2], args[3], (byte) 0, args[4], os); - } - long serial = m.getSerial(); - conn.mout.writeMessage(m); - do { - m = conn.min.readMessage(); - } while (serial != m.getReplySerial()); - if (m instanceof Error) ((Error) m).throwException(); - else { - Object[] os = m.getParameters(); - System.out.println(Arrays.deepToString(os)); - } - } catch (Exception e) { - System.out.println (e.getClass().getSimpleName()+": "+e.getMessage()); - System.exit(1); - } - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/CreateInterface.java b/app/src/main/java/org/freedesktop/dbus/bin/CreateInterface.java deleted file mode 100644 index cfadcc62..00000000 --- a/app/src/main/java/org/freedesktop/dbus/bin/CreateInterface.java +++ /dev/null @@ -1,711 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.bin; - -import static org.freedesktop.dbus.Gettext.$_; -import static org.freedesktop.dbus.bin.IdentifierMangler.mangle; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintStream; -import java.io.Reader; -import java.io.StringReader; -import java.text.MessageFormat; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.Vector; - -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.freedesktop.DBus.Introspectable; -import org.freedesktop.dbus.DBusConnection; -import org.freedesktop.dbus.Marshalling; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; -import org.freedesktop.dbus.types.DBusStructType; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -/** - * Converts a DBus XML file into Java interface definitions. - */ -public class CreateInterface -{ - @SuppressWarnings("unchecked") - private static String collapseType(Type t, Set imports, Map structs, boolean container, boolean fullnames) throws DBusException - { - if (t instanceof ParameterizedType) { - String s; - Class c = (Class) ((ParameterizedType) t).getRawType(); - if (null != structs && t instanceof DBusStructType) { - int num = 1; - String name = "Struct"; - while (null != structs.get(new StructStruct(name+num))) num++; - name = name+num; - structs.put(new StructStruct(name), ((ParameterizedType) t).getActualTypeArguments()); - return name; - } - if (null != imports) imports.add(c.getName()); - if (fullnames) return c.getName(); - else s = c.getSimpleName(); - s += '<'; - Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); - for (Type st: ts) - s += collapseType(st, imports, structs, true, fullnames)+','; - s = s.replaceAll(",$", ">"); - return s; - } else if (t instanceof Class) { - Class c = (Class) t; - if (c.isArray()) { - return collapseType(c.getComponentType(), imports, structs, container, fullnames)+"[]"; - } else { - Package p = c.getPackage(); - if (null != imports && - !"java.lang".equals(p.getName())) imports.add(c.getName()); - if (container) { - if (fullnames) return c.getName(); - else return c.getSimpleName(); - } else { - try { - Field f = c.getField("TYPE"); - Class d = (Class) f.get(c); - return d.getSimpleName(); - } catch (Exception e) { - return c.getSimpleName(); - } - } - } - } else return ""; - } - private static String getJavaType(String dbus, Set imports, Map structs, boolean container, boolean fullnames) throws DBusException - { - if (null == dbus || "".equals(dbus)) return ""; - Vector v = new Vector(); - int c = Marshalling.getJavaType(dbus, v, 1); - Type t = v.get(0); - return collapseType(t, imports, structs, container, fullnames); - } - public String comment = ""; - boolean builtin; - - public CreateInterface(PrintStreamFactory factory, boolean builtin) - { - this.factory = factory; - this.builtin = builtin; - } - @SuppressWarnings("fallthrough") - String parseReturns(Vector out, Set imports, Map tuples, Map structs) throws DBusException - { - String[] names = new String[] { "Pair", "Triplet", "Quad", "Quintuple", "Sextuple", "Septuple" }; - String sig = ""; - String name = null; - switch (out.size()) { - case 0: - sig += "void "; - break; - case 1: - sig += getJavaType(out.get(0).getAttribute("type"), imports, structs, false, false)+" "; - break; - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - name = names[out.size() - 2]; - default: - if (null == name) - name = "NTuple"+out.size(); - - tuples.put(name, out.size()); - sig += name + "<"; - for (Element arg: out) - sig += getJavaType(arg.getAttribute("type"), imports, structs, true, false)+", "; - sig = sig.replaceAll(", $","> "); - break; - } - return sig; - } - String parseMethod(Element meth, Set imports, Map tuples, Map structs, Set exceptions, Set anns) throws DBusException - { - Vector in = new Vector(); - Vector out = new Vector(); - if (null == meth.getAttribute("name") || - "".equals(meth.getAttribute("name"))) { - System.err.println($_("ERROR: Method name was blank, failed")); - System.exit(1); - } - String annotations = ""; - String throwses = null; - - for (Node a: new IterableNodeList(meth.getChildNodes())) { - - if (Node.ELEMENT_NODE != a.getNodeType()) continue; - - checkNode(a, "arg", "annotation"); - - if ("arg".equals(a.getNodeName())) { - Element arg = (Element) a; - - // methods default to in - if ("out".equals(arg.getAttribute("direction"))) - out.add(arg); - else - in.add(arg); - } - else if ("annotation".equals(a.getNodeName())) { - Element e = (Element) a; - if (e.getAttribute("name").equals("org.freedesktop.DBus.Method.Error")) { - if (null == throwses) - throwses = e.getAttribute("value"); - else - throwses += ", " + e.getAttribute("value"); - exceptions.add(e.getAttribute("value")); - } else - annotations += parseAnnotation(e, imports, anns); - } - } - - String sig = ""; - comment = ""; - sig += parseReturns(out, imports, tuples, structs); - - sig += mangle(meth.getAttribute("name"))+"("; - - char defaultname = 'a'; - String params = ""; - for (Element arg: in) { - String type = getJavaType(arg.getAttribute("type"), imports, structs, false, false); - String name = arg.getAttribute("name"); - if (null == name || "".equals(name)) name = ""+(defaultname++); - params += type+" "+mangle(name)+", "; - } - return ("".equals(comment) ? "" : " /**\n" + comment + " */\n") - + annotations + " public " + sig + - params.replaceAll("..$", "")+")"+ - (null == throwses? "": " throws "+throwses)+";"; - } - String parseSignal(Element signal, Set imports, Map structs, Set anns) throws DBusException - { - Map params = new HashMap(); - Vector porder = new Vector(); - char defaultname = 'a'; - imports.add("org.freedesktop.dbus.DBusSignal"); - imports.add("org.freedesktop.dbus.exceptions.DBusException"); - String annotations = ""; - for (Node a: new IterableNodeList(signal.getChildNodes())) { - - if (Node.ELEMENT_NODE != a.getNodeType()) continue; - - checkNode(a, "arg", "annotation"); - - if ("annotation".equals(a.getNodeName())) - annotations += parseAnnotation((Element) a, imports, anns); - else { - Element arg = (Element) a; - String type = getJavaType(arg.getAttribute("type"), imports, structs, false, false); - String name = arg.getAttribute("name"); - if (null == name || "".equals(name)) name = ""+(defaultname++); - params.put(mangle(name), type); - porder.add(mangle(name)); - } - } - - String out = ""; - out += annotations; - out += " public static class "+signal.getAttribute("name"); - out += " extends DBusSignal\n {\n"; - for (String name: porder) - out += " public final "+params.get(name)+" "+name+";\n"; - out += " public "+signal.getAttribute("name")+"(String path"; - for (String name: porder) - out += ", "+params.get(name)+" "+name; - out += ") throws DBusException\n {\n super(path"; - for (String name: porder) - out += ", "+name; - out += ");\n"; - for (String name: porder) - out += " this."+name+" = "+name+";\n"; - out += " }\n"; - - out += " }\n"; - return out; - } - - String parseAnnotation(Element ann, Set imports, Set annotations) - { - String s = " @"+ann.getAttribute("name").replaceAll(".*\\.([^.]*)$","$1")+"("; - if (null != ann.getAttribute("value") - && !"".equals(ann.getAttribute("value"))) - s += '"'+ann.getAttribute("value")+'"'; - imports.add(ann.getAttribute("name")); - annotations.add(ann.getAttribute("name")); - return s += ")\n"; - } - - void parseInterface(Element iface, PrintStream out, Map tuples, Map structs, Set exceptions, Set anns) throws DBusException - { - if (null == iface.getAttribute("name") || - "".equals(iface.getAttribute("name"))) { - System.err.println($_("ERROR: Interface name was blank, failed")); - System.exit(1); - } - - out.println("package "+iface.getAttribute("name").replaceAll("\\.[^.]*$","")+";"); - - String methods = ""; - String signals = ""; - String annotations = ""; - Set imports = new TreeSet(); - imports.add("org.freedesktop.dbus.DBusInterface"); - for (Node meth: new IterableNodeList(iface.getChildNodes())) { - - if (Node.ELEMENT_NODE != meth.getNodeType()) continue; - - checkNode(meth, "method", "signal", "property", "annotation"); - - if ("method".equals(meth.getNodeName())) - methods += parseMethod((Element) meth, imports, tuples, structs, exceptions, anns) + "\n"; - else if ("signal".equals(meth.getNodeName())) - signals += parseSignal((Element) meth, imports, structs, anns); - else if ("property".equals(meth.getNodeName())) - System.err.println("WARNING: Ignoring property"); - else if ("annotation".equals(meth.getNodeName())) - annotations += parseAnnotation((Element) meth, imports, anns); - } - - if (imports.size() > 0) - for (String i: imports) - out.println("import "+i+";"); - - out.print(annotations); - out.print("public interface "+iface.getAttribute("name").replaceAll("^.*\\.([^.]*)$","$1")); - out.println(" extends DBusInterface"); - out.println("{"); - out.println(signals); - out.println(methods); - out.println("}"); - } - void createException(String name, String pack, PrintStream out) throws DBusException - { - out.println("package "+pack+";"); - out.println("import org.freedesktop.dbus.DBusExecutionException;"); - out.print("public class "+name); - out.println(" extends DBusExecutionException"); - out.println("{"); - out.println(" public "+name+"(String message)"); - out.println(" {"); - out.println(" super(message);"); - out.println(" }"); - out.println("}"); - } - void createAnnotation(String name, String pack, PrintStream out) throws DBusException - { - out.println("package "+pack+";"); - out.println("import java.lang.annotation.Retention;"); - out.println("import java.lang.annotation.RetentionPolicy;"); - out.println("@Retention(RetentionPolicy.RUNTIME)"); - out.println("public @interface "+name); - out.println("{"); - out.println(" String value();"); - out.println("}"); - } - void createStruct(String name, Type[] type, String pack, PrintStream out, Map existing) throws DBusException, IOException - { - out.println("package "+pack+";"); - - Set imports = new TreeSet(); - imports.add("org.freedesktop.dbus.Position"); - imports.add("org.freedesktop.dbus.Struct"); - Map structs = new HashMap(existing); - String[] types = new String[type.length]; - for (int i = 0; i < type.length; i++) - types[i] = collapseType(type[i], imports, structs, false, false); - - for (String im: imports) out.println("import "+im+";"); - - out.println("public final class "+name+" extends Struct"); - out.println("{"); - int i = 0; - char c = 'a'; - String params = ""; - for (String t: types) { - out.println(" @Position("+i++ +")"); - out.println(" public final "+t+" "+c+";"); - params += t+" "+c+", "; - c++; - } - out.println(" public "+name+"("+params.replaceAll("..$", "")+")"); - out.println(" {"); - for (char d = 'a'; d < c; d++) - out.println(" this."+d+" = "+d+";"); - - out.println(" }"); - out.println("}"); - - structs = StructStruct.fillPackages(structs, pack); - Map tocreate = new HashMap(structs); - for (StructStruct ss: existing.keySet()) tocreate.remove(ss); - createStructs(tocreate, structs); - } - void createTuple(String name, int num, String pack, PrintStream out) throws DBusException - { - out.println("package "+pack+";"); - out.println("import org.freedesktop.dbus.Position;"); - out.println("import org.freedesktop.dbus.Tuple;"); - out.println("/** Just a typed container class */"); - out.print("public final class "+name); - String types = " <"; - for (char v = 'A'; v < 'A'+num; v++) - types += v + ","; - out.print(types.replaceAll(",$","> ")); - out.println("extends Tuple"); - out.println("{"); - - char t = 'A'; - char n = 'a'; - for (int i = 0; i < num; i++,t++,n++) { - out.println(" @Position("+i+")"); - out.println(" public final "+t+" "+n+";"); - } - - out.print(" public "+name+"("); - String sig = ""; - t = 'A'; - n = 'a'; - for (int i = 0; i < num; i++,t++,n++) - sig += t+" "+n+", "; - out.println(sig.replaceAll(", $", ")")); - out.println(" {"); - for (char v = 'a'; v < 'a'+num; v++) - out.println(" this."+v+" = "+v+";"); - out.println(" }"); - - out.println("}"); - } - void parseRoot(Element root) throws DBusException, IOException - { - Map structs = new HashMap(); - Set exceptions = new TreeSet(); - Set annotations = new TreeSet(); - - for (Node iface: new IterableNodeList(root.getChildNodes())) { - - if (Node.ELEMENT_NODE != iface.getNodeType()) continue; - - checkNode(iface, "interface", "node"); - - if ("interface".equals(iface.getNodeName())) { - - Map tuples = new HashMap(); - String name = ((Element) iface).getAttribute("name"); - String file = name.replaceAll("\\.","/")+".java"; - String path = file.replaceAll("/[^/]*$", ""); - String pack = name.replaceAll("\\.[^.]*$",""); - - // don't create interfaces in org.freedesktop.DBus by default - if (pack.startsWith("org.freedesktop.DBus") && !builtin) continue; - - factory.init(file, path); - parseInterface((Element) iface, - factory.createPrintStream(file), tuples, structs, exceptions, annotations); - - structs = StructStruct.fillPackages(structs, pack); - createTuples(tuples, pack); - } - else if ("node".equals(iface.getNodeName())) - parseRoot((Element) iface); - else { - System.err.println($_("ERROR: Unknown node: ")+iface.getNodeName()); - System.exit(1); - } - } - - createStructs(structs, structs); - createExceptions(exceptions); - createAnnotations(annotations); - } - private void createAnnotations(Set annotations) throws DBusException, IOException - { - for (String fqn: annotations) { - String name = fqn.replaceAll("^.*\\.([^.]*)$", "$1"); - String pack = fqn.replaceAll("\\.[^.]*$",""); - // don't create things in org.freedesktop.DBus by default - if (pack.startsWith("org.freedesktop.DBus") && !builtin) - continue; - String path = pack.replaceAll("\\.", "/"); - String file = name.replaceAll("\\.","/")+".java"; - factory.init(file, path); - createAnnotation(name, pack, - factory.createPrintStream(path, name)); - } - } - private void createExceptions(Set exceptions) throws DBusException, IOException - { - for (String fqn: exceptions) { - String name = fqn.replaceAll("^.*\\.([^.]*)$", "$1"); - String pack = fqn.replaceAll("\\.[^.]*$",""); - // don't create things in org.freedesktop.DBus by default - if (pack.startsWith("org.freedesktop.DBus") && !builtin) - continue; - String path = pack.replaceAll("\\.", "/"); - String file = name.replaceAll("\\.","/")+".java"; - factory.init(file, path); - createException(name, pack, - factory.createPrintStream(path, name)); - } - } - private void createStructs(Map structs, Map existing) throws DBusException, IOException - { - for (StructStruct ss: structs.keySet()) { - String file = ss.name.replaceAll("\\.","/")+".java"; - String path = ss.pack.replaceAll("\\.", "/"); - factory.init(file, path); - createStruct(ss.name, structs.get(ss), ss.pack, - factory.createPrintStream(path, ss.name), existing); - } - } - - private void createTuples(Map typeMap, String pack) throws DBusException, IOException - { - for (String tname: typeMap.keySet()) - createTuple(tname, typeMap.get(tname), pack, - factory.createPrintStream(pack.replaceAll("\\.","/"), tname)); - } - - public static abstract class PrintStreamFactory - { - - public abstract void init(String file, String path); - - /** - * @param path - * @param tname - * @return PrintStream - * @throws IOException - */ - public PrintStream createPrintStream(String path, String tname) throws IOException - { - final String file = path+"/"+tname+".java"; - return createPrintStream(file); - } - - /** - * @param file - * @return PrintStream - * @throws IOException - */ - public abstract PrintStream createPrintStream(final String file) throws IOException; - - } - static class ConsoleStreamFactory extends PrintStreamFactory - { - - @Override - public - void init(String file, String path) - { - } - - @Override - public - PrintStream createPrintStream(String file) throws IOException - { - System.out.println("/* File: "+file+" */"); - return System.out; - } - - public PrintStream createPrintStream(String path, String tname) throws IOException - { - return super.createPrintStream(path, tname); - } - - } - - static class FileStreamFactory extends PrintStreamFactory - { - public void init(String file, String path) - { - new File(path).mkdirs(); - } - - - /** - * @param file - * @return - * @throws IOException - */ - public PrintStream createPrintStream(final String file) throws IOException - { - return new PrintStream(new FileOutputStream(file)); - } - - } - - static void checkNode(Node n, String... names) - { - String expected = ""; - for (String name: names) { - if (name.equals(n.getNodeName())) return; - expected += name + " or "; - } - System.err.println(MessageFormat.format($_("ERROR: Expected {0}, got {1}, failed."), new Object[] { expected.replaceAll("....$", ""), n.getNodeName() })); - System.exit(1); - } - - private final PrintStreamFactory factory; - - static class Config - { - int bus = DBusConnection.SESSION; - String busname = null; - String object = null; - File datafile = null; - boolean printtree = false; - boolean fileout = false; - boolean builtin = false; - } - - static void printSyntax() - { - printSyntax(System.err); - } - static void printSyntax(PrintStream o) - { - o.println("Syntax: CreateInterface [file | busname object]"); - o.println(" Options: --no-ignore-builtin --system -y --session -s --create-files -f --help -h --version -v"); - } - public static void version() - { - System.out.println("Java D-Bus Version "+System.getProperty("Version")); - System.exit(1); - } - - static Config parseParams(String[] args) - { - Config config = new Config(); - for (String p: args) { - if ("--system".equals(p) || "-y".equals(p)) - config.bus = DBusConnection.SYSTEM; - else if ("--session".equals(p) || "-s".equals(p)) - config.bus = DBusConnection.SESSION; - else if ("--no-ignore-builtin".equals(p)) - config.builtin = true; - else if ("--create-files".equals(p) || "-f".equals(p)) - config.fileout = true; - else if ("--print-tree".equals(p) || "-p".equals(p)) - config.printtree = true; - else if ("--help".equals(p) || "-h".equals(p)) { - printSyntax(System.out); - System.exit(0); - } else if ("--version".equals(p) || "-v".equals(p)) { - version(); - System.exit(0); - } else if (p.startsWith("-")) { - System.err.println($_("ERROR: Unknown option: ")+p); - printSyntax(); - System.exit(1); - } - else { - if (null == config.busname) config.busname = p; - else if (null == config.object) config.object = p; - else { - printSyntax(); - System.exit(1); - } - } - } - if (null == config.busname) { - printSyntax(); - System.exit(1); - } - else if (null == config.object) { - config.datafile = new File(config.busname); - config.busname = null; - } - return config; - } - - public static void main(String[] args) throws Exception - { - Config config = parseParams(args); - - Reader introspectdata = null; - - if (null != config.busname) try { - DBusConnection conn = DBusConnection.getConnection(config.bus); - Introspectable in = conn.getRemoteObject(config.busname, config.object, Introspectable.class); - String id = in.Introspect(); - if (null == id) { - System.err.println($_("ERROR: Failed to get introspection data")); - System.exit(1); - } - introspectdata = new StringReader(id); - conn.disconnect(); - } catch (DBusException DBe) { - System.err.println($_("ERROR: Failure in DBus Communications: ")+DBe.getMessage()); - System.exit(1); - } catch (DBusExecutionException DEe) { - System.err.println($_("ERROR: Failure in DBus Communications: ")+DEe.getMessage()); - System.exit(1); - - } else if (null != config.datafile) try { - introspectdata = new InputStreamReader(new FileInputStream(config.datafile)); - } catch (FileNotFoundException FNFe) { - System.err.println($_("ERROR: Could not find introspection file: ")+FNFe.getMessage()); - System.exit(1); - } - try { - PrintStreamFactory factory = config.fileout ? new FileStreamFactory() : new ConsoleStreamFactory(); - CreateInterface createInterface = new CreateInterface(factory, config.builtin); - createInterface.createInterface(introspectdata); - } catch (DBusException DBe) { - System.err.println("ERROR: "+DBe.getMessage()); - System.exit(1); - } - } - /** Output the interface for the supplied xml reader - * @param introspectdata The introspect data reader - * @throws ParserConfigurationException If the xml parser could not be configured - * @throws SAXException If a problem occurs reading the xml data - * @throws IOException If an IO error occurs - * @throws DBusException If the dbus related error occurs - */ - public void createInterface(Reader introspectdata) throws ParserConfigurationException, SAXException, IOException, DBusException - { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document document = builder.parse(new InputSource(introspectdata)); - - Element root = document.getDocumentElement(); - checkNode(root, "node"); - parseRoot(root); - - } -} - - diff --git a/app/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java b/app/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java deleted file mode 100644 index 8595fe07..00000000 --- a/app/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java +++ /dev/null @@ -1,878 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.bin; - -import static org.freedesktop.dbus.Gettext.$_; - -import org.freedesktop.DBus; -import org.freedesktop.dbus.AbstractConnection; -import org.freedesktop.dbus.BusAddress; -import org.freedesktop.dbus.DBusSignal; -import org.freedesktop.dbus.DirectConnection; -import org.freedesktop.dbus.Error; -import org.freedesktop.dbus.Marshalling; -import org.freedesktop.dbus.Message; -import org.freedesktop.dbus.MessageReader; -import org.freedesktop.dbus.MessageWriter; -import org.freedesktop.dbus.MethodCall; -import org.freedesktop.dbus.MethodReturn; -import org.freedesktop.dbus.Transport; -import org.freedesktop.dbus.UInt32; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; -import org.freedesktop.dbus.exceptions.FatalException; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; -import java.lang.ref.WeakReference; -import java.lang.reflect.InvocationTargetException; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.text.MessageFormat; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Vector; - -import cx.ath.matthew.debug.Debug; -import cx.ath.matthew.unix.UnixServerSocket; -import cx.ath.matthew.unix.UnixSocket; -import cx.ath.matthew.unix.UnixSocketAddress; - -/** - * A replacement DBusDaemon - */ -public class DBusDaemon extends Thread -{ - public static final int QUEUE_POLL_WAIT = 500; - static class Connstruct - { - public UnixSocket usock; - public Socket tsock; - public MessageReader min; - public MessageWriter mout; - public String unique; - public Connstruct(UnixSocket sock) - { - this.usock = sock; - min = new MessageReader(sock.getInputStream()); - mout = new MessageWriter(sock.getOutputStream()); - } - public Connstruct(Socket sock) throws IOException - { - this.tsock = sock; - min = new MessageReader(sock.getInputStream()); - mout = new MessageWriter(sock.getOutputStream()); - } - public String toString() - { - return null == unique ? ":?-?" : unique; - } - } - static class MagicMap - { - private Map> m; - private LinkedList q; - private String name; - public MagicMap(String name) - { - m = new HashMap>(); - q = new LinkedList(); - this.name = name; - } - public A head() - { - return q.getFirst(); - } - public void putFirst(A a, B b) - { - if (Debug.debug) Debug.print("<"+name+"> Queueing {"+a+" => "+b+"}"); - if (m.containsKey(a)) - m.get(a).add(b); - else { - LinkedList l = new LinkedList(); - l.add(b); - m.put(a, l); - } - q.addFirst(a); - } - public void putLast(A a, B b) - { - if (Debug.debug) Debug.print("<"+name+"> Queueing {"+a+" => "+b+"}"); - if (m.containsKey(a)) - m.get(a).add(b); - else { - LinkedList l = new LinkedList(); - l.add(b); - m.put(a, l); - } - q.addLast(a); - } - public List remove(A a) - { - if (Debug.debug) Debug.print("<"+name+"> Removing {"+a+"}"); - q.remove(a); - return m.remove(a); - } - public int size() - { - return q.size(); - } - } - public class DBusServer extends Thread implements DBus, DBus.Introspectable, DBus.Peer - { - public DBusServer() - { - setName("Server"); - } - public Connstruct c; - public Message m; - public boolean isRemote() { return false; } - public String Hello() - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - synchronized (c) { - if (null != c.unique) - throw new org.freedesktop.DBus.Error.AccessDenied($_("Connection has already sent a Hello message")); - synchronized (unique_lock) { - c.unique = ":1."+(++next_unique); - } - } - synchronized (names) { - names.put(c.unique, c); - } - - if (Debug.debug) Debug.print(Debug.WARN, "Client "+c.unique+" registered"); - - try { - send(c, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameAcquired", "s", c.unique)); - DBusSignal s = new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", c.unique, "", c.unique); - send(null, s); - } catch (DBusException DBe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); - } - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return c.unique; - } - public String[] ListNames() - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - String[] ns; - synchronized (names) { - Set nss = names.keySet(); - ns = nss.toArray(new String[0]); - } - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return ns; - } - - public boolean NameHasOwner(String name) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - boolean rv; - synchronized (names) { - rv = names.containsKey(name); - } - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return rv; - } - public String GetNameOwner(String name) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - Connstruct owner = names.get(name); - String o; - if (null == owner) - o = ""; - else - o = owner.unique; - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return o; - } - - public UInt32 GetConnectionUnixUser(String connection_name) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return new UInt32(0); - } - public UInt32 StartServiceByName(String name, UInt32 flags) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return new UInt32(0); - } - public UInt32 RequestName(String name, UInt32 flags) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - - boolean exists = false; - synchronized (names) { - if (!(exists = names.containsKey(name))) - names.put(name, c); - } - - int rv; - if (exists) { - rv = DBus.DBUS_REQUEST_NAME_REPLY_EXISTS; - } else { - if (Debug.debug) Debug.print(Debug.WARN, "Client "+c.unique+" acquired name "+name); - rv = DBus.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER; - try { - send(c, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameAcquired", "s", name)); - send(null, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", name, "", c.unique)); - } catch (DBusException DBe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); - } - } - - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return new UInt32(rv); - } - - public UInt32 ReleaseName(String name) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - - boolean exists = false; - synchronized (names) { - if ((exists = (names.containsKey(name) && names.get(name).equals(c)))) - names.remove(name); - } - - int rv; - if (!exists) { - rv = DBus.DBUS_RELEASE_NAME_REPLY_NON_EXISTANT; - } else { - if (Debug.debug) Debug.print(Debug.WARN, "Client "+c.unique+" acquired name "+name); - rv = DBus.DBUS_RELEASE_NAME_REPLY_RELEASED; - try { - send(c, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameLost", "s", name)); - send(null, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", name, c.unique, "")); - } catch (DBusException DBe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); - } - } - - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return new UInt32(rv); - } - public void AddMatch(String matchrule) throws Error.MatchRuleInvalid - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding match rule: "+matchrule); - synchronized (sigrecips) { - if (!sigrecips.contains(c)) - sigrecips.add(c); - } - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return; - } - public void RemoveMatch(String matchrule) throws Error.MatchRuleInvalid - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Removing match rule: "+matchrule); - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return; - } - public String[] ListQueuedOwners(String name) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return new String[0]; - } - public UInt32 GetConnectionUnixProcessID(String connection_name) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return new UInt32(0); - } - public Byte[] GetConnectionSELinuxSecurityContext(String a) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return new Byte[0]; - } - public void ReloadConfig() - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return; - } - @SuppressWarnings("unchecked") - private void handleMessage(Connstruct c, Message m) throws DBusException - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - if (Debug.debug) Debug.print(Debug.VERBOSE, "Handling message "+m+" from "+c.unique); - if (!(m instanceof MethodCall)) return; - Object[] args = m.getParameters(); - - Class[] cs = new Class[args.length]; - - for (int i = 0; i < cs.length; i++) - cs[i] = args[i].getClass(); - - java.lang.reflect.Method meth = null; - Object rv = null; - - try { - meth = DBusServer.class.getMethod(m.getName(), cs); - try { - this.c = c; - this.m = m; - rv = meth.invoke(dbus_server, args); - if (null == rv) { - send(c, new MethodReturn("org.freedesktop.DBus", (MethodCall) m, null), true); - } else { - String sig = Marshalling.getDBusType(meth.getGenericReturnType())[0]; - send(c, new MethodReturn("org.freedesktop.DBus", (MethodCall) m, sig, rv), true); - } - } catch (InvocationTargetException ITe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, ITe); - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, ITe.getCause()); - send(c, new org.freedesktop.dbus.Error("org.freedesktop.DBus", m, ITe.getCause())); - } catch (DBusExecutionException DBEe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBEe); - send(c, new org.freedesktop.dbus.Error("org.freedesktop.DBus", m, DBEe)); - } catch (Exception e) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, e); - send(c,new org.freedesktop.dbus.Error("org.freedesktop.DBus", c.unique, "org.freedesktop.DBus.Error.GeneralError", m.getSerial(), "s", $_("An error occurred while calling ")+m.getName())); - } - } catch (NoSuchMethodException NSMe) { - send(c,new org.freedesktop.dbus.Error("org.freedesktop.DBus", c.unique, "org.freedesktop.DBus.Error.UnknownMethod", m.getSerial(), "s", $_("This service does not support ")+m.getName())); - } - - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - } - public String Introspect() - { - return "\n"+ - "\n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - " \n"+ - ""; - } - public void Ping() {} - - public void run() - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - while (_run) { - Message m; - List> wcs; - // block on outqueue - synchronized (localqueue) { - while (localqueue.size() == 0) try { - localqueue.wait(); - } catch (InterruptedException Ie) { } - m = localqueue.head(); - wcs = localqueue.remove(m); - } - if (null != wcs) try { - for (WeakReference wc: wcs) { - Connstruct c = wc.get(); - if (null != c) { - if (Debug.debug) Debug.print(Debug.VERBOSE, " Got message "+m+" from "+c); - handleMessage(c, m); - } - } - } catch (DBusException DBe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); - } - else if (Debug.debug) Debug.print(Debug.INFO, "Discarding "+m+" connection reaped"); - } - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - } - } - public class Sender extends Thread - { - public Sender() - { - setName("Sender"); - } - public void run() - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - while (_run) { - - if (Debug.debug) Debug.print(Debug.VERBOSE, "Acquiring lock on outqueue and blocking for data"); - Message m = null; - List> wcs = null; - // block on outqueue - synchronized (outqueue) { - while (outqueue.size() == 0) try { - outqueue.wait(); - } catch (InterruptedException Ie) { } - - m = outqueue.head(); - wcs = outqueue.remove(m); - } - if (null != wcs) { - for (WeakReference wc: wcs) { - Connstruct c = wc.get(); - if (null != c) { - if (Debug.debug) Debug.print(Debug.VERBOSE, " Got message "+m+" for "+c.unique); - if (Debug.debug) Debug.print(Debug.INFO, "Sending message "+m+" to "+c.unique); - try { - c.mout.writeMessage(m); - } - catch (IOException IOe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe); - removeConnection(c); - } - } - } - } - else if (Debug.debug) Debug.print(Debug.INFO, "Discarding "+m+" connection reaped"); - } - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - } - } - public class Reader extends Thread - { - private Connstruct conn; - private WeakReference weakconn; - private boolean _lrun = true; - public Reader(Connstruct conn) - { - this.conn = conn; - weakconn = new WeakReference(conn); - setName("Reader"); - } - public void stopRunning() - { - _lrun = false; - } - public void run() - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - while (_run && _lrun) { - - Message m = null; - try { - m = conn.min.readMessage(); - } catch (IOException IOe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe); - removeConnection(conn); - } catch (DBusException DBe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); - if (DBe instanceof FatalException) - removeConnection(conn); - } - - if (null != m) { - if (Debug.debug) Debug.print(Debug.INFO, "Read "+m+" from "+conn.unique); - synchronized (inqueue) { - inqueue.putLast(m, weakconn); - inqueue.notifyAll(); - } - } - } - conn = null; - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - } - } - - private Map conns = new HashMap(); - private HashMap names = new HashMap(); - private MagicMap> outqueue = new MagicMap>("out"); - private MagicMap> inqueue = new MagicMap>("in"); - private MagicMap> localqueue = new MagicMap>("local"); - private List sigrecips = new Vector(); - private boolean _run = true; - private int next_unique = 0; - private Object unique_lock = new Object(); - DBusServer dbus_server = new DBusServer(); - Sender sender = new Sender(); - - public DBusDaemon() - { - setName("Daemon"); - synchronized (names) { - names.put("org.freedesktop.DBus", null); - } - } - @SuppressWarnings("unchecked") - private void send(Connstruct c, Message m) - { - send (c, m, false); - } - private void send(Connstruct c, Message m, boolean head) - { - if (Debug.debug){ - Debug.print(Debug.DEBUG, "enter"); - if (null == c) - Debug.print(Debug.VERBOSE, "Queing message "+m+" for all connections"); - else - Debug.print(Debug.VERBOSE, "Queing message "+m+" for "+c.unique); - } - // send to all connections - if (null == c) { - synchronized (conns) { - synchronized (outqueue) { - for (Connstruct d: conns.keySet()) - if (head) - outqueue.putFirst(m, new WeakReference(d)); - else - outqueue.putLast(m, new WeakReference(d)); - outqueue.notifyAll(); - } - } - } else { - synchronized (outqueue) { - if (head) - outqueue.putFirst(m, new WeakReference(c)); - else - outqueue.putLast(m, new WeakReference(c)); - outqueue.notifyAll(); - } - } - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - } - @SuppressWarnings("unchecked") - private List findSignalMatches(DBusSignal sig) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - List l; - synchronized (sigrecips) { - l = new Vector(sigrecips); - } - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - return l; - } - @SuppressWarnings("unchecked") - public void run() - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - while (_run) { - try { - Message m; - List> wcs; - synchronized (inqueue) { - while (0 == inqueue.size()) try { - inqueue.wait(); - } catch (InterruptedException Ie) {} - - m = inqueue.head(); - wcs = inqueue.remove(m); - } - if (null != wcs) { - for (WeakReference wc: wcs) { - Connstruct c = wc.get(); - if (null != c) { - if (Debug.debug) Debug.print(Debug.INFO, " Got message "+m+" from "+c.unique); - // check if they have hello'd - if (null == c.unique - && (!(m instanceof MethodCall) - || !"org.freedesktop.DBus".equals(m.getDestination()) - || !"Hello".equals(m.getName()))) { - send(c,new Error("org.freedesktop.DBus", null, "org.freedesktop.DBus.Error.AccessDenied", m.getSerial(), "s", $_("You must send a Hello message"))); - } else { - try { - if (null != c.unique) m.setSource(c.unique); - } catch (DBusException DBe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); - send(c,new Error("org.freedesktop.DBus", null, "org.freedesktop.DBus.Error.GeneralError", m.getSerial(), "s", $_("Sending message failed"))); - } - - if ("org.freedesktop.DBus".equals(m.getDestination())) { - synchronized (localqueue) { - localqueue.putLast(m, wc); - localqueue.notifyAll(); - } - } else { - if (m instanceof DBusSignal) { - List list = findSignalMatches((DBusSignal) m); - for (Connstruct d: list) - send(d, m); - } else { - Connstruct dest = names.get(m.getDestination()); - - if (null == dest) { - send(c, new Error("org.freedesktop.DBus", null, "org.freedesktop.DBus.Error.ServiceUnknown", m.getSerial(), "s", MessageFormat.format($_("The name `{0}' does not exist"), new Object[] { m.getDestination() }))); - } else - send(dest, m); - } - } - } - } - } - } - } - catch (DBusException DBe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); - } - } - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - } - private void removeConnection(Connstruct c) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - boolean exists; - synchronized(conns) { - if ((exists = conns.containsKey(c))) { - Reader r = conns.get(c); - r.stopRunning(); - conns.remove(c); - } - } - if (exists) { - try { - if (null != c.usock) c.usock.close(); - if (null != c.tsock) c.tsock.close(); - } catch (IOException IOe) {} - synchronized(names) { - List toRemove = new Vector(); - for (String name: names.keySet()) - if (names.get(name) == c) { - toRemove.add(name); - try { - send(null, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", name, c.unique, "")); - } catch (DBusException DBe) { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); - } - } - for (String name: toRemove) - names.remove(name); - } - } - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - } - public void addSock(UnixSocket us) - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - if (Debug.debug) Debug.print(Debug.WARN, "New Client"); - Connstruct c = new Connstruct(us); - Reader r = new Reader(c); - synchronized (conns) { - conns.put(c, r); - } - r.start(); - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - } - public void addSock(Socket s) throws IOException - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - if (Debug.debug) Debug.print(Debug.WARN, "New Client"); - Connstruct c = new Connstruct(s); - Reader r = new Reader(c); - synchronized (conns) { - conns.put(c, r); - } - r.start(); - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - } - public static void syntax() - { - System.out.println("Syntax: DBusDaemon [--version] [-v] [--help] [-h] [--listen address] [-l address] [--print-address] [-r] [--pidfile file] [-p file] [--addressfile file] [-a file] [--unix] [-u] [--tcp] [-t] "); - System.exit(1); - } - public static void version() - { - System.out.println("D-Bus Java Version: "+System.getProperty("Version")); - System.exit(1); - } - public static void saveFile(String data, String file) throws IOException - { - PrintWriter w = new PrintWriter(new FileOutputStream(file)); - w.println(data); - w.close(); - } - public static void main(String args[]) throws Exception - { - if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.DEBUG, "enter"); - else if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - String addr = null; - String pidfile = null; - String addrfile = null; - boolean printaddress = false; - boolean unix = true; - boolean tcp = false; - - // parse options - try { - for (int i=0; i < args.length; i++) - if ("--help".equals(args[i]) || "-h".equals(args[i])) - syntax(); - else if ("--version".equals(args[i]) || "-v".equals(args[i])) - version(); - else if ("--listen".equals(args[i]) || "-l".equals(args[i])) - addr = args[++i]; - else if ("--pidfile".equals(args[i]) || "-p".equals(args[i])) - pidfile = args[++i]; - else if ("--addressfile".equals(args[i]) || "-a".equals(args[i])) - addrfile = args[++i]; - else if ("--print-address".equals(args[i]) || "-r".equals(args[i])) - printaddress = true; - else if ("--unix".equals(args[i]) || "-u".equals(args[i])) { - unix = true; - tcp = false; - } else if ("--tcp".equals(args[i]) || "-t".equals(args[i])) { - tcp = true; - unix = false; - } else syntax(); - } catch (ArrayIndexOutOfBoundsException AIOOBe) { - syntax(); - } - - // generate a random address if none specified - if (null == addr && unix) addr = DirectConnection.createDynamicSession(); - else if (null == addr && tcp) addr = DirectConnection.createDynamicTCPSession(); - - BusAddress address = new BusAddress(addr); - if (null == address.getParameter("guid")) { - addr += ",guid="+Transport.genGUID(); - address = new BusAddress(addr); - } - - // print address to stdout - if (printaddress) System.out.println(addr); - - // print address to file - if (null != addrfile) saveFile(addr, addrfile); - - // print PID to file - if (null != pidfile) saveFile(System.getProperty("Pid"), pidfile); - - // start the daemon - if (Debug.debug) Debug.print(Debug.WARN, "Binding to "+addr); - if ("unix".equals(address.getType())) - doUnix(address); - else if ("tcp".equals(address.getType())) - doTCP(address); - else throw new Exception("Unknown address type: "+address.getType()); - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - } - private static void doUnix(BusAddress address) throws IOException - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - UnixServerSocket uss; - if (null != address. getParameter("abstract")) - uss = new UnixServerSocket(new UnixSocketAddress(address.getParameter("abstract"), true)); - else - uss = new UnixServerSocket(new UnixSocketAddress(address.getParameter("path"), false)); - DBusDaemon d = new DBusDaemon(); - d.start(); - d.sender.start(); - d.dbus_server.start(); - - // accept new connections - while (d._run) { - UnixSocket s = uss.accept(); - if ((new Transport.SASL()).auth(Transport.SASL.MODE_SERVER, Transport.SASL.AUTH_EXTERNAL, address.getParameter("guid"), s.getOutputStream(), s.getInputStream(), s)) { - // s.setBlocking(false); - d.addSock(s); - } else - s.close(); - } - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - } - private static void doTCP(BusAddress address) throws IOException - { - if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); - ServerSocket ss = new ServerSocket(Integer.parseInt(address.getParameter("port")),10, InetAddress.getByName(address.getParameter("host"))); - DBusDaemon d = new DBusDaemon(); - d.start(); - d.sender.start(); - d.dbus_server.start(); - - // accept new connections - while (d._run) { - Socket s = ss.accept(); - boolean authOK=false; - try { - authOK = (new Transport.SASL()).auth(Transport.SASL.MODE_SERVER, Transport.SASL.AUTH_EXTERNAL, address.getParameter("guid"), s.getOutputStream(), s.getInputStream(), null); - } catch (Exception e) { - if (Debug.debug) Debug. print(Debug.DEBUG, e); - } - if (authOK) { - d.addSock(s); - } else - s.close(); - } - if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/IdentifierMangler.java b/app/src/main/java/org/freedesktop/dbus/bin/IdentifierMangler.java deleted file mode 100644 index db596f41..00000000 --- a/app/src/main/java/org/freedesktop/dbus/bin/IdentifierMangler.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.bin; - -import java.util.Arrays; - -/** - * Checks identifiers for keywords etc and mangles them if so. - */ -public class IdentifierMangler -{ - private static String[] keywords; - static { - keywords = new String[] { - "true","false","null", - "abstract","continue","for","new","switch", - "assert","default","goto","package","synchronized", - "boolean","do","if","private","this", - "break","double","implements","protected","throw", - "byte","else","import","public","throws", - "case","enum","instanceof","return","transient", - "catch","extends","int","short","try", - "char","final","interface","static","void", - "class","finally","long","strictfp","volatile", - "const","float","native","super","while" - }; - Arrays.sort(keywords); - } - public static String mangle(String name) - { - if (Arrays.binarySearch(keywords, name) >= 0) - name = "_"+name; - return name; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/IterableNodeList.java b/app/src/main/java/org/freedesktop/dbus/bin/IterableNodeList.java deleted file mode 100644 index ddd5884b..00000000 --- a/app/src/main/java/org/freedesktop/dbus/bin/IterableNodeList.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.bin; - -import java.util.Iterator; - -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -class IterableNodeList implements Iterable -{ - private NodeList nl; - public IterableNodeList(NodeList nl) - { - this.nl = nl; - } - public Iterator iterator() - { - return new NodeListIterator(nl); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/ListDBus.java b/app/src/main/java/org/freedesktop/dbus/bin/ListDBus.java deleted file mode 100644 index 93d4ad59..00000000 --- a/app/src/main/java/org/freedesktop/dbus/bin/ListDBus.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.bin; - -import org.freedesktop.DBus; -import org.freedesktop.dbus.DBusConnection; -import org.freedesktop.dbus.exceptions.DBusExecutionException; - -/** - * This class lists all the names currently connected on the bus - */ -public class ListDBus -{ - public static void syntax() - { - System.out.println("Syntax: ListDBus [--version] [-v] [--help] [-h] [--owners] [-o] [--uids] [-u] [--session] [-s] [--system] [-y]"); - System.exit(1); - } - public static void version() - { - System.out.println("Java D-Bus Version "+System.getProperty("Version")); - System.exit(1); - } - public static void main(String args[]) throws Exception - { - boolean owners = false; - boolean users = false; - int connection = DBusConnection.SESSION; - - for (String a: args) - if ("--help".equals(a)) syntax(); - else if ("-h".equals(a)) syntax(); - else if ("--version".equals(a)) version(); - else if ("-v".equals(a)) version(); - else if ("-u".equals(a)) users = true; - else if ("--uids".equals(a)) users = true; - else if ("-o".equals(a)) owners = true; - else if ("--owners".equals(a)) owners = true; - else if ("--session".equals(a)) connection = DBusConnection.SESSION; - else if ("-s".equals(a)) connection = DBusConnection.SESSION; - else if ("--system".equals(a)) connection = DBusConnection.SYSTEM; - else if ("-y".equals(a)) connection = DBusConnection.SYSTEM; - else syntax(); - - DBusConnection conn = DBusConnection.getConnection(connection); - DBus dbus = conn.getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); - String[] names = dbus.ListNames(); - for (String s: names) { - if (users) - try { - System.out.print(dbus.GetConnectionUnixUser(s)+"\t"); - } catch (DBusExecutionException DBEe) { - System.out.print("\t"); - } - System.out.print(s); - if (!s.startsWith(":") && owners) { - try { - System.out.print("\t"+dbus.GetNameOwner(s)); - } catch (DBusExecutionException DBEe) { - } - } - System.out.println(); - } - conn.disconnect(); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/NodeListIterator.java b/app/src/main/java/org/freedesktop/dbus/bin/NodeListIterator.java deleted file mode 100644 index ea2d23a1..00000000 --- a/app/src/main/java/org/freedesktop/dbus/bin/NodeListIterator.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.bin; - -import java.util.Iterator; - -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -class NodeListIterator implements Iterator -{ - NodeList nl; - int i; - NodeListIterator(NodeList nl) - { - this.nl = nl; - i = 0; - } - public boolean hasNext() - { - return i < nl.getLength(); - } - public Node next() - { - Node n = nl.item(i); - i++; - return n; - } - public void remove() {}; -} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/StructStruct.java b/app/src/main/java/org/freedesktop/dbus/bin/StructStruct.java deleted file mode 100644 index f302f6dc..00000000 --- a/app/src/main/java/org/freedesktop/dbus/bin/StructStruct.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.bin; - -import java.lang.reflect.Type; -import java.util.Map; -import java.util.HashMap; - -class StructStruct -{ - public static Map fillPackages(Map structs, String pack) - { - Map newmap = new HashMap(); - for (StructStruct ss: structs.keySet()) { - Type[] type = structs.get(ss); - if (null == ss.pack) ss.pack = pack; - newmap.put(ss, type); - } - return newmap; - } - public String name; - public String pack; - public StructStruct(String name) - { - this.name = name; - } - public StructStruct(String name, String pack) - { - this.name = name; - this.pack = pack; - } - public int hashCode() - { - return name.hashCode(); - } - public boolean equals(Object o) - { - if (!(o instanceof StructStruct)) return false; - if (!name.equals(((StructStruct) o).name)) return false; - return true; - } - public String toString() - { - return "<"+name+", "+pack+">"; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/connections/SASL.java b/app/src/main/java/org/freedesktop/dbus/connections/SASL.java new file mode 100644 index 00000000..992c68bb --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/connections/SASL.java @@ -0,0 +1,825 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.freedesktop.dbus.connections; + +import static org.freedesktop.dbus.connections.SASL.SaslCommand.*; + +import org.freedesktop.dbus.config.DBusSysProps; +import org.freedesktop.dbus.connections.config.SaslConfig; +import org.freedesktop.dbus.connections.transports.AbstractTransport; +import org.freedesktop.dbus.connections.transports.AbstractUnixTransport; +import org.freedesktop.dbus.exceptions.AuthenticationException; +import org.freedesktop.dbus.exceptions.SocketClosedException; +import org.freedesktop.dbus.messages.Message; +import org.freedesktop.dbus.utils.Hexdump; +import org.freedesktop.dbus.utils.LoggingHelper; +import org.freedesktop.dbus.utils.TimeMeasure; +import org.freedesktop.dbus.utils.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.channels.NetworkChannel; +import java.nio.channels.SocketChannel; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.PosixFilePermission; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.Collator; +import java.util.*; + +public class SASL { + public static final int AUTH_NONE = 0; + public static final int AUTH_EXTERNAL = 1; + public static final int AUTH_SHA = 2; + public static final int AUTH_ANON = 4; + + public static final int LOCK_TIMEOUT = 1000; + public static final int NEW_KEY_TIMEOUT_SECONDS = 60 * 5; + public static final int EXPIRE_KEYS_TIMEOUT_SECONDS = NEW_KEY_TIMEOUT_SECONDS + (60 * 2); + public static final int MAX_TIME_TRAVEL_SECONDS = 60 * 5; + public static final int COOKIE_TIMEOUT = 240; + public static final String COOKIE_CONTEXT = "org_freedesktop_java"; + + private static final String AUTH_TYPE_EXTERNAL = "EXTERNAL"; + private static final String AUTH_TYPE_DBUS_COOKIE_SHA1 = "DBUS_COOKIE_SHA1"; + private static final String AUTH_TYPE_ANONYMOUS = "ANONYMOUS"; + + private static final String INVALID_CMD_ERR = "Got invalid command"; + + // stop reading when reaching ~1MByte of data + private static final int MAX_READ_BYTES = 1024 * 1024; + + private static final Random RANDOM = new Random(); + + private static final Collator COL = Collator.getInstance(); + static { + COL.setStrength(Collator.PRIMARY); + } + + private static final String SYSPROP_USER_HOME = System.getProperty("user.home"); + private static final String DBUS_TEST_HOME_DIR = System.getProperty(DBusSysProps.SYSPROP_DBUS_TEST_HOME_DIR); + + private static final File DBUS_KEYRINGS_DIR = new File(SYSPROP_USER_HOME, ".dbus-keyrings"); + + private static final Set BAD_FILE_PERMISSIONS = + Collections.emptySet(); + + private String challenge = ""; + private String cookie = ""; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + /** whether file descriptor passing is supported on the current connection. */ + private boolean fileDescriptorSupported; + private final SaslConfig saslConfig; + + /** + * Create a new SASL auth handler. + * + * @param _saslConfig SASL configuration + */ + public SASL(SaslConfig _saslConfig) { + saslConfig = Objects.requireNonNull(_saslConfig, "Sasl Configuration required"); + } + + private String findCookie(String _context, String _id) throws IOException { + File keyringDir = DBUS_KEYRINGS_DIR; + if (!Util.isBlank(DBUS_TEST_HOME_DIR)) { + keyringDir = new File(DBUS_TEST_HOME_DIR); + } + + File f = new File(keyringDir, _context); + long currentTime = System.currentTimeMillis() / 1000; + try (BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f)))) { + String s = null; + String lCookie = null; + + while (null != (s = r.readLine())) { + String[] line = s.split(" "); + if (line.length != 3) { + continue; + } + long timestamp; + try { + timestamp = Long.parseLong(line[1]); + } catch (NumberFormatException _ex) { + continue; + } + if (line[0].equals(_id) && timestamp >= 0 && currentTime >= timestamp - MAX_TIME_TRAVEL_SECONDS && currentTime < timestamp + EXPIRE_KEYS_TIMEOUT_SECONDS) { + lCookie = line[2]; + break; + } + } + return lCookie; + } + } + + @SuppressWarnings("checkstyle:emptyblock") + private void addCookie(String _context, String _id, long _timestamp, String _cookie) throws IOException { + throw new IllegalArgumentException("Not implemented yet"); + } + + /** + * Takes the string, encodes it as hex and then turns it into a string again. + * No, I don't know why either. + */ + private String stupidlyEncode(String _data) { + return Hexdump.toHex(_data.getBytes(), false); + } + + private String stupidlyEncode(byte[] _data) { + return Hexdump.toHex(_data, false); + } + + private byte getNibble(char _c) { + return switch (_c) { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> (byte) (_c - '0'); + case 'A', 'B', 'C', 'D', 'E', 'F' -> (byte) (_c - 'A' + 10); + case 'a', 'b', 'c', 'd', 'e', 'f' -> (byte) (_c - 'a' + 10); + default -> 0; + }; + } + + private String stupidlyDecode(String _data) { + char[] cs = new char[_data.length()]; + char[] res = new char[cs.length / 2]; + _data.getChars(0, _data.length(), cs, 0); + for (int i = 0, j = 0; j < res.length; i += 2, j++) { + int b = 0; + b |= getNibble(cs[i]) << 4; + b |= getNibble(cs[i + 1]); + res[j] = (char) b; + } + return new String(res); + } + + public SASL.Command receive(SocketChannel _sock) throws IOException { + StringBuilder sb = new StringBuilder(); + ByteBuffer buf = ByteBuffer.allocate(1); // only read one byte at a time to avoid reading to much (which would break the next message) + + boolean runLoop = true; + int bytesRead = 0; + while (runLoop) { + + int read = _sock.read(buf); + bytesRead += read; + buf.position(0); + if (read == -1) { + throw new SocketClosedException("Stream unexpectedly short (broken pipe)"); + } + + for (int i = buf.position(); i < read; i++) { + byte c = buf.get(); + if (c == 0 || c == '\r') { + continue; + } else if (c == '\n') { + runLoop = false; + break; + } else { + sb.append((char) c); + } + } + buf.clear(); + + if (bytesRead > MAX_READ_BYTES) { // safe-guard to stop reading if no \n found + break; + } + } + + logger.trace("received: {}", sb); + try { + return new Command(sb.toString()); + } catch (Exception _ex) { + logger.error("Cannot create command.", _ex); + throw new AuthenticationException("Failed to authenticate.", _ex); + } + } + + public void send(SocketChannel _sock, SaslCommand _command, String... _data) throws IOException { + StringBuilder sb = new StringBuilder(); + sb.append(_command.name()); + + for (String s : _data) { + sb.append(' '); + sb.append(s); + } + sb.append('\r'); + sb.append('\n'); + logger.trace("sending: {}", sb); + _sock.write(ByteBuffer.wrap(sb.toString().getBytes())); + } + + SaslResult doChallenge(int _auth, SASL.Command _c) throws IOException { + switch (_auth) { + case AUTH_SHA: + String[] reply = stupidlyDecode(_c.getData()).split(" "); + LoggingHelper.logIf(logger.isTraceEnabled(), () -> logger.trace("Auth data: {}", Arrays.toString(reply))); + + if (3 != reply.length) { + logger.debug("Reply is not length 3"); + return SaslResult.ERROR; + } + + String context = reply[0]; + String id = reply[1]; + final String serverchallenge = reply[2]; + + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA"); + } catch (NoSuchAlgorithmException _ex) { + logger.debug("Could not find SHA algorithm", _ex); + return SaslResult.ERROR; + } + + byte[] buf = new byte[8]; + + // ensure we get a (more or less unique) positive long + long seed = Optional.of(System.nanoTime()).map(t -> t < 0 ? t * -1 : t).get(); + + Message.marshallintBig(seed, buf, 0, 8); + String clientchallenge = stupidlyEncode(md.digest(buf)); + md.reset(); + + TimeMeasure tm = new TimeMeasure(); + String lCookie = null; + + while (lCookie == null && tm.getElapsed() < LOCK_TIMEOUT) { + lCookie = findCookie(context, id); + } + + if (lCookie == null) { + logger.debug("Did not find a cookie in context {} with ID {}", context, id); + return SaslResult.ERROR; + } + + String response = serverchallenge + ":" + clientchallenge + ":" + lCookie; + buf = md.digest(response.getBytes()); + + if (logger.isTraceEnabled()) { + logger.trace("Response: {} hash: {}", response, Hexdump.format(buf)); + } + + response = stupidlyEncode(buf); + _c.setResponse(stupidlyEncode(clientchallenge + " " + response)); + return SaslResult.OK; + case AUTH_ANON: + // Pong back DATA if server wants it for anonymous auth + _c.setResponse(_c.getData() == null ? "" : _c.getData()); + return SaslResult.OK; + default: + logger.debug("Not DBUS_COOKIE_SHA1 authtype."); + return SaslResult.ERROR; + } + } + + SaslResult doResponse(int _auth, String _uid, String _kernelUid, SASL.Command _c) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA"); + } catch (NoSuchAlgorithmException _ex) { + logger.error("SHA hash algorithm not available", _ex); + return SaslResult.ERROR; + } + switch (_auth) { + case AUTH_NONE: + switch (_c.getMechs()) { + case AUTH_ANON: + return SaslResult.OK; + case AUTH_EXTERNAL: + if (0 == COL.compare(_uid, _c.getData()) && (null == _kernelUid || 0 == COL.compare(_uid, _kernelUid))) { + return SaslResult.OK; + } else { + return SaslResult.REJECT; + } + case AUTH_SHA: + String context = COOKIE_CONTEXT; + long id = System.currentTimeMillis(); + byte[] buf = new byte[8]; + Message.marshallintBig(id, buf, 0, 8); + challenge = stupidlyEncode(md.digest(buf)); + + RANDOM.nextBytes(buf); + cookie = stupidlyEncode(md.digest(buf)); + try { + addCookie(context, "" + id, id / 1000, cookie); + } catch (IOException _ex) { + logger.error("Error authenticating using cookie", _ex); + return SaslResult.ERROR; + } + + logger.debug("Sending challenge: {} {} {}", context, id, challenge); + + _c.setResponse(stupidlyEncode(context + ' ' + id + ' ' + challenge)); + return SaslResult.CONTINUE; + default: + return SaslResult.ERROR; + } + case AUTH_SHA: + String[] response = stupidlyDecode(_c.getData()).split(" "); + if (response.length < 2) { + return SaslResult.ERROR; + } + String cchal = response[0]; + String hash = response[1]; + String prehash = challenge + ":" + cchal + ":" + cookie; + byte[] buf = md.digest(prehash.getBytes()); + String posthash = stupidlyEncode(buf); + logger.debug("Authenticating Hash; data={} remote-hash={} local-hash={}", prehash, hash, posthash); + if (0 == COL.compare(posthash, hash)) { + return SaslResult.OK; + } else { + return SaslResult.ERROR; + } + default: + return SaslResult.ERROR; + } + } + + public String[] convertAuthTypes(int _types) { + return switch (_types) { + case AUTH_EXTERNAL -> new String[] { + AUTH_TYPE_EXTERNAL + }; + case AUTH_SHA -> new String[] { + AUTH_TYPE_DBUS_COOKIE_SHA1 + }; + case AUTH_ANON -> new String[] { + AUTH_TYPE_ANONYMOUS + }; + case AUTH_SHA + AUTH_EXTERNAL -> new String[] { + AUTH_TYPE_EXTERNAL, AUTH_TYPE_DBUS_COOKIE_SHA1 + }; + case AUTH_SHA + AUTH_ANON -> new String[] { + AUTH_TYPE_ANONYMOUS, AUTH_TYPE_DBUS_COOKIE_SHA1 + }; + case AUTH_EXTERNAL + AUTH_ANON -> new String[] { + AUTH_TYPE_ANONYMOUS, AUTH_TYPE_EXTERNAL + }; + case AUTH_EXTERNAL + AUTH_ANON + AUTH_SHA -> new String[] { + AUTH_TYPE_ANONYMOUS, AUTH_TYPE_EXTERNAL, AUTH_TYPE_DBUS_COOKIE_SHA1 + }; + default -> new String[] {}; + }; + } + + /** + * Performs SASL auth on the given socketchannel. + * Mode selects whether to run as a SASL server or client. + * Types is a bitmask of the available auth types. + * + * @param _sock socket channel + * @param _transport transport + * + * @return true if the auth was successful and false if it failed. + * @throws IOException on failure + */ + public boolean auth(SocketChannel _sock, AbstractTransport _transport) throws IOException { + String luid = null; + String kernelUid = null; + + long uid = saslConfig.getSaslUid().orElse(getUserId()); + luid = stupidlyEncode("" + uid); + + SASL.Command c; + int failed = 0; + int current = 0; + SaslAuthState state = SaslAuthState.INITIAL_STATE; + + while (state != SaslAuthState.FINISHED && state != SaslAuthState.FAILED) { + + logger.trace("Mode: {} AUTH state: {}", saslConfig.getMode(), state); + + switch (saslConfig.getMode()) { + case CLIENT: + switch (state) { + case INITIAL_STATE: + _sock.write(ByteBuffer.wrap(new byte[] {0})); + send(_sock, AUTH); + state = SaslAuthState.WAIT_DATA; + break; + case WAIT_DATA: + c = receive(_sock); + switch (c.getCommand()) { + case DATA: + switch (doChallenge(current, c)) { + case CONTINUE: + send(_sock, DATA, c.getResponse()); + break; + case OK: + send(_sock, DATA, c.getResponse()); + state = SaslAuthState.WAIT_OK; + break; + case ERROR: + default: + send(_sock, ERROR, c.getResponse()); + break; + } + break; + case REJECTED: + failed |= current; + int available = c.getMechs() & (~failed); + int retVal = handleReject(available, luid, _sock); + if (retVal == -1) { + state = SaslAuthState.FAILED; + } else { + current = retVal; + } + break; + case ERROR: + // when asking for file descriptor support, ERROR means FD support is not supported + if (state == SaslAuthState.NEGOTIATE_UNIX_FD) { + state = SaslAuthState.FINISHED; + logger.trace("File descriptors NOT supported by server"); + fileDescriptorSupported = false; + send(_sock, BEGIN); + } else { + send(_sock, CANCEL); + state = SaslAuthState.WAIT_REJECT; + } + break; + case OK: + logger.trace("Authenticated"); + + if (saslConfig.isFileDescriptorSupport()) { + state = SaslAuthState.WAIT_DATA; + logger.trace("Asking for file descriptor support"); + // if authentication was successful, ask remote end for file descriptor support + send(_sock, NEGOTIATE_UNIX_FD); + } else { + state = SaslAuthState.FINISHED; + send(_sock, BEGIN); + } + break; + case AGREE_UNIX_FD: + if (saslConfig.isFileDescriptorSupport()) { + state = SaslAuthState.FINISHED; + logger.trace("File descriptors supported by server"); + fileDescriptorSupported = true; + send(_sock, BEGIN); + } + break; + default: + send(_sock, ERROR, INVALID_CMD_ERR); + break; + } + break; + case WAIT_OK: + c = receive(_sock); + switch (c.getCommand()) { + case OK: + send(_sock, BEGIN); + state = SaslAuthState.FINISHED; + break; + case ERROR, DATA: + send(_sock, CANCEL); + state = SaslAuthState.WAIT_REJECT; + break; + case REJECTED: + failed |= current; + int available = c.getMechs() & (~failed); + state = SaslAuthState.WAIT_DATA; + if (0 != (available & AUTH_EXTERNAL)) { + send(_sock, AUTH, AUTH_TYPE_EXTERNAL, luid); + current = AUTH_EXTERNAL; + } else if (0 != (available & AUTH_SHA)) { + send(_sock, AUTH, AUTH_TYPE_DBUS_COOKIE_SHA1, luid); + current = AUTH_SHA; + } else if (0 != (available & AUTH_ANON)) { + send(_sock, AUTH, AUTH_TYPE_ANONYMOUS); + current = AUTH_ANON; + } else { + state = SaslAuthState.FAILED; + } + break; + default: + send(_sock, ERROR, INVALID_CMD_ERR); + break; + } + break; + case WAIT_REJECT: + c = receive(_sock); + if (c.getCommand() == REJECTED) { + failed |= current; + int available = c.getMechs() & (~failed); + int retVal = handleReject(available, luid, _sock); + if (retVal == -1) { + state = SaslAuthState.FAILED; + } else { + current = retVal; + } + } else { + state = SaslAuthState.FAILED; + } + break; + default: + state = SaslAuthState.FAILED; + } + break; + case SERVER: + switch (state) { + case INITIAL_STATE: + ByteBuffer buf = ByteBuffer.allocate(1); + if (_sock instanceof NetworkChannel) { + _sock.read(buf); // 0 + state = SaslAuthState.WAIT_AUTH; + } else { + try { + int kuid = -1; + if (_transport instanceof AbstractUnixTransport aut) { + kuid = aut.getUid(_sock); + } + if (kuid >= 0) { + kernelUid = stupidlyEncode("" + kuid); + } + state = SaslAuthState.WAIT_AUTH; + + } catch (SocketException _ex) { + state = SaslAuthState.FAILED; + } + } + break; + case WAIT_AUTH: + c = receive(_sock); + switch (c.getCommand()) { + case AUTH: + switch (doResponse(current, luid, kernelUid, c)) { + case CONTINUE: + send(_sock, DATA, c.getResponse()); + current = c.getMechs(); + state = SaslAuthState.WAIT_DATA; + break; + case OK: + send(_sock, OK, saslConfig.getGuid()); + state = SaslAuthState.WAIT_BEGIN; + current = 0; + break; + case REJECT: + default: + send(_sock, REJECTED, convertAuthTypes(saslConfig.getAuthMode())); + current = 0; + break; + } + break; + case ERROR: + send(_sock, REJECTED, convertAuthTypes(saslConfig.getAuthMode())); + break; + case BEGIN: + state = SaslAuthState.FAILED; + break; + default: + send(_sock, ERROR, INVALID_CMD_ERR); + break; + } + break; + case WAIT_DATA: + c = receive(_sock); + switch (c.getCommand()) { + case DATA: + switch (doResponse(current, luid, kernelUid, c)) { + case CONTINUE: + send(_sock, DATA, c.getResponse()); + state = SaslAuthState.WAIT_DATA; + break; + case OK: + send(_sock, OK, saslConfig.getGuid()); + state = SaslAuthState.WAIT_BEGIN; + current = 0; + break; + case REJECT: + default: + send(_sock, REJECTED, convertAuthTypes(saslConfig.getAuthMode())); + current = 0; + break; + } + break; + case ERROR, CANCEL: + send(_sock, REJECTED, convertAuthTypes(saslConfig.getAuthMode())); + state = SaslAuthState.WAIT_AUTH; + break; + case BEGIN: + state = SaslAuthState.FAILED; + break; + default: + send(_sock, ERROR, INVALID_CMD_ERR); + break; + } + break; + case WAIT_BEGIN: + c = receive(_sock); + switch (c.getCommand()) { + case ERROR, CANCEL: + send(_sock, REJECTED, convertAuthTypes(saslConfig.getAuthMode())); + state = SaslAuthState.WAIT_AUTH; + break; + case BEGIN: + state = SaslAuthState.FINISHED; + break; + case NEGOTIATE_UNIX_FD: + logger.debug("File descriptor negotiation requested"); + if (!saslConfig.isFileDescriptorSupport()) { + send(_sock, ERROR); + } else { + send(_sock, AGREE_UNIX_FD); + } + + break; + default: + send(_sock, ERROR, INVALID_CMD_ERR); + break; + } + break; + default: + state = SaslAuthState.FAILED; + } + break; + default: + return false; + } + } + + return state == SaslAuthState.FINISHED; + } + + public boolean isFileDescriptorSupported() { + return fileDescriptorSupported; + } + + /** + * Handle reject of authentication. + * + * @param _available + * @param _luid + * @param _sock socketchannel + * @return current state or -1 if failed + * @throws IOException when sending fails + */ + private int handleReject(int _available, String _luid, SocketChannel _sock) throws IOException { + int current = -1; + if (0 != (_available & AUTH_EXTERNAL)) { + send(_sock, AUTH, AUTH_TYPE_EXTERNAL, _luid); + current = AUTH_EXTERNAL; + } else if (0 != (_available & AUTH_SHA)) { + send(_sock, AUTH, AUTH_TYPE_DBUS_COOKIE_SHA1, _luid); + current = AUTH_SHA; + } else if (0 != (_available & AUTH_ANON)) { + send(_sock, AUTH, AUTH_TYPE_ANONYMOUS); + current = AUTH_ANON; + } + return current; + } + + /** + * Tries to get the UID (user ID) of the current JVM process. + * Will always return 0 on windows. + * @return long + */ + private long getUserId() { + return 0; + } + + public enum SaslMode { + SERVER, CLIENT; + } + + public enum SaslCommand { + AUTH, + DATA, + REJECTED, + OK, + BEGIN, + CANCEL, + ERROR, + NEGOTIATE_UNIX_FD, + AGREE_UNIX_FD; + } + + enum SaslAuthState { + INITIAL_STATE, + WAIT_DATA, + WAIT_OK, + WAIT_REJECT, + WAIT_AUTH, + WAIT_BEGIN, + NEGOTIATE_UNIX_FD, + FINISHED, + FAILED; + } + + public enum SaslResult { + OK, + CONTINUE, + ERROR, + REJECT; + } + + public static class Command { + private final Logger logger = LoggerFactory.getLogger(getClass()); + private SaslCommand command; + private int mechs; + private String data; + private String response; + + public Command() { + } + + public Command(String _s) throws IOException { + String[] ss = _s.split(" "); + LoggingHelper.logIf(logger.isTraceEnabled(), () -> logger.trace("Creating command from: {}", Arrays.toString(ss))); + if (0 == COL.compare(ss[0], "OK")) { + command = OK; + data = ss[1]; + } else if (0 == COL.compare(ss[0], "AUTH")) { + command = AUTH; + if (ss.length > 1) { + if (0 == COL.compare(ss[1], AUTH_TYPE_EXTERNAL)) { + mechs = AUTH_EXTERNAL; + } else if (0 == COL.compare(ss[1], AUTH_TYPE_DBUS_COOKIE_SHA1)) { + mechs = AUTH_SHA; + } else if (0 == COL.compare(ss[1], AUTH_TYPE_ANONYMOUS)) { + mechs = AUTH_ANON; + } + } + if (ss.length > 2) { + data = ss[2]; + } + } else if (0 == COL.compare(ss[0], "DATA")) { + command = DATA; + // ss[1] might be non-existing or empty (e.g. AUTH ANON) + data = ss.length < 2 ? null : ss[1]; + } else if (0 == COL.compare(ss[0], "REJECTED")) { + command = REJECTED; + for (int i = 1; i < ss.length; i++) { + if (0 == COL.compare(ss[i], AUTH_TYPE_EXTERNAL)) { + mechs |= AUTH_EXTERNAL; + } else if (0 == COL.compare(ss[i], AUTH_TYPE_DBUS_COOKIE_SHA1)) { + mechs |= AUTH_SHA; + } else if (0 == COL.compare(ss[i], AUTH_TYPE_ANONYMOUS)) { + mechs |= AUTH_ANON; + } + } + } else if (0 == COL.compare(ss[0], "BEGIN")) { + command = BEGIN; + } else if (0 == COL.compare(ss[0], "CANCEL")) { + command = CANCEL; + } else if (0 == COL.compare(ss[0], "ERROR")) { + command = ERROR; + data = ss[1]; + } else if (0 == COL.compare(ss[0], "NEGOTIATE_UNIX_FD")) { + command = NEGOTIATE_UNIX_FD; + } else if (0 == COL.compare(ss[0], "AGREE_UNIX_FD")) { + command = AGREE_UNIX_FD; + } else { + throw new IOException("Invalid Command " + ss[0]); + } + logger.trace("Created command: {}", this); + } + + public SaslCommand getCommand() { + return command; + } + + public int getMechs() { + return mechs; + } + + public String getData() { + return data; + } + + public String getResponse() { + return response; + } + + public void setResponse(String _s) { + response = _s; + } + + @Override + public String toString() { + return "Command(" + command + ", " + mechs + ", " + data + ")"; + } + } + +} diff --git a/app/src/main/java/org/freedesktop/dbus/connections/impl/AndroidDBusConnectionBuilder.java b/app/src/main/java/org/freedesktop/dbus/connections/impl/AndroidDBusConnectionBuilder.java new file mode 100644 index 00000000..ce9e51c2 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/connections/impl/AndroidDBusConnectionBuilder.java @@ -0,0 +1,227 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.freedesktop.dbus.connections.impl; + +import org.freedesktop.dbus.connections.BusAddress; +import org.freedesktop.dbus.connections.config.ReceivingServiceConfig; +import org.freedesktop.dbus.connections.config.TransportConfig; +import org.freedesktop.dbus.connections.transports.TransportBuilder; +import org.freedesktop.dbus.exceptions.AddressResolvingException; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.utils.AddressBuilder; +import org.freedesktop.dbus.utils.Util; + +/** + * Builder to create a new DBusConnection. + * + * @author hypfvieh + * @version 4.1.0 - 2022-02-04 + */ +public final class AndroidDBusConnectionBuilder extends BaseConnectionBuilder { + + private final String machineId; + private boolean shared = true; + + private AndroidDBusConnectionBuilder(BusAddress _address, String _machineId) { + super(AndroidDBusConnectionBuilder.class, _address); + machineId = _machineId; + } + + /** + * Create a new default connection connecting to DBus session bus but use an alternative input for the machineID. + * + * @param _machineIdFileLocation file with machine ID + * + * @return {@link AndroidDBusConnectionBuilder} + */ + public static AndroidDBusConnectionBuilder forSessionBus(String _machineIdFileLocation) { + BusAddress address = validateTransportAddress(AddressBuilder.getSessionConnection(_machineIdFileLocation)); + return new AndroidDBusConnectionBuilder(address, String.format("%s@%s", Util.getCurrentUser(), Util.getHostName())); + } + + /** + * Create new default connection to the DBus system bus. + * + * @return {@link AndroidDBusConnectionBuilder} + */ + public static AndroidDBusConnectionBuilder forSystemBus() { + BusAddress address = validateTransportAddress(AddressBuilder.getSystemConnection()); + return new AndroidDBusConnectionBuilder(address, String.format("%s@%s", Util.getCurrentUser(), Util.getHostName())); + } + + /** + * Create a new default connection connecting to the DBus session bus. + * + * @return {@link AndroidDBusConnectionBuilder} + */ + public static AndroidDBusConnectionBuilder forSessionBus() { + return forSessionBus(null); + } + + /** + * Create a default connection to DBus using the given bus type. + * + * @param _type bus type + * + * @return this + */ + public static AndroidDBusConnectionBuilder forType(DBusConnection.DBusBusType _type) { + return forType(_type, null); + } + + /** + * Create a default connection to DBus using the given bus type and machineIdFile. + * + * @param _type bus type + * @param _machineIdFile machineId file + * + * @return this + */ + public static AndroidDBusConnectionBuilder forType(DBusConnection.DBusBusType _type, String _machineIdFile) { + if (_type == DBusConnection.DBusBusType.SESSION) { + return forSessionBus(_machineIdFile); + } else if (_type == DBusConnection.DBusBusType.SYSTEM) { + return forSystemBus(); + } + + throw new IllegalArgumentException("Unknown bus type: " + _type); + } + + /** + * Use the given address to create the connection (e.g. used for remote TCP connected DBus daemons). + * + * @param _address address to use + * @return this + */ + public static AndroidDBusConnectionBuilder forAddress(String _address) { + return new AndroidDBusConnectionBuilder(BusAddress.of(_address), String.format("%s@%s", Util.getCurrentUser(), Util.getHostName())); + } + + /** + * Use the given address to create the connection (e.g. used for remote TCP connected DBus daemons). + * + * @param _address address to use + * @return this + * + * @since 4.2.0 - 2022-07-18 + */ + public static AndroidDBusConnectionBuilder forAddress(BusAddress _address) { + return new AndroidDBusConnectionBuilder(_address, String.format("%s@%s", Util.getCurrentUser(), Util.getHostName())); + } + + /** + * Checks if the given address can be used with the available transports. + * Will fallback to TCP if no address given and TCP transport is available. + * + * @param _address address to check + * @return address, maybe fallback address + */ + private static BusAddress validateTransportAddress(BusAddress _address) { + if (TransportBuilder.getRegisteredBusTypes().isEmpty()) { + throw new IllegalArgumentException("No transports found to connect to DBus. Please add at least one transport provider to your classpath"); + } + + BusAddress address = _address; + + // no unix transport but address wants to use a unix socket + if (!TransportBuilder.getRegisteredBusTypes().contains("UNIX") + && address != null + && address.isBusType("UNIX")) { + throw new AddressResolvingException("No transports found to handle UNIX socket connections. Please add a unix-socket transport provider to your classpath"); + } + + // no tcp transport but TCP address given + if (!TransportBuilder.getRegisteredBusTypes().contains("TCP") + && address != null + && address.isBusType("TCP")) { + throw new AddressResolvingException("No transports found to handle TCP connections. Please add a TCP transport provider to your classpath"); + } + + return address; + + } + + /** + * Use this connection as shared connection. Shared connection means that the same connection is used multiple times + * if the connection parameter did not change. Default is true. + * + * @param _shared boolean + * @return this + */ + public AndroidDBusConnectionBuilder withShared(boolean _shared) { + shared = _shared; + return this; + } + + /** + * Create the new {@link DBusConnection}. + * + * @return {@link DBusConnection} + * @throws DBusException when DBusConnection could not be opened + */ + @Override + public DBusConnection build() throws DBusException { + ReceivingServiceConfig cfg = buildThreadConfig(); + TransportConfig transportCfg = buildTransportConfig(); + + DBusConnection c; + if (shared) { + synchronized (DBusConnection.CONNECTIONS) { + String busAddressStr = transportCfg.getBusAddress().toString(); + c = getSharedConnection(busAddressStr); + if (c != null) { + c.concurrentConnections.incrementAndGet(); + return c; // this connection already exists, do not change anything + } else { + c = new DBusConnection(shared, machineId, transportCfg, cfg); + DBusConnection.CONNECTIONS.put(busAddressStr, c); + } + } + } else { + c = new DBusConnection(shared, machineId, transportCfg, cfg); + } + + c.setDisconnectCallback(getDisconnectCallback()); + c.setWeakReferences(isWeakReference()); + c.connectImpl(); + return c; + } + + /** + * Retrieve a existing shared connection. + * Will remove existing shared connections when underlying transport is disconnected. + * @param _busAddr bus address + * @return connection if a valid shared connection found or + * null if no connection found or found connection was invalid + */ + private DBusConnection getSharedConnection(String _busAddr) { + synchronized (DBusConnection.CONNECTIONS) { + DBusConnection c = DBusConnection.CONNECTIONS.get(_busAddr); + if (c != null) { + if (!c.isConnected()) { + DBusConnection.CONNECTIONS.remove(_busAddr); + return null; + } else { + return c; + } + } + } + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java deleted file mode 100644 index 15f46d0c..00000000 --- a/app/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.exceptions; - -/** - * An exception within DBus. - */ -@SuppressWarnings("serial") -public class DBusException extends Exception -{ - /** - * Create an exception with the specified message - */ - public DBusException(String message) - { - super(message); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java deleted file mode 100644 index 7dd54fd0..00000000 --- a/app/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.exceptions; - -/** - * An exception while running a remote method within DBus. - */ -@SuppressWarnings("serial") -public class DBusExecutionException extends RuntimeException -{ - private String type; - /** - * Create an exception with the specified message - */ - public DBusExecutionException(String message) - { - super(message); - } - public void setType(String type) - { - this.type = type; - } - /** - * Get the DBus type of this exception. Use if this - * was an exception we don't have a class file for. - */ - public String getType() - { - if (null == type) return getClass().getName(); - else return type; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java deleted file mode 100644 index 6f8d4010..00000000 --- a/app/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.exceptions; - -@SuppressWarnings("serial") -public class FatalDBusException extends DBusException implements FatalException -{ - public FatalDBusException(String message) - { - super(message); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java deleted file mode 100644 index d69efa5a..00000000 --- a/app/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.exceptions; - -public interface FatalException -{ -} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java deleted file mode 100644 index 4113bca8..00000000 --- a/app/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.exceptions; - -@SuppressWarnings("serial") -public class InternalMessageException extends DBusExecutionException implements NonFatalException -{ - public InternalMessageException(String message) - { - super (message); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java deleted file mode 100644 index cf60d3bd..00000000 --- a/app/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.exceptions; - -@SuppressWarnings("serial") -public class MarshallingException extends DBusException implements NonFatalException -{ - public MarshallingException(String message) - { - super(message); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java deleted file mode 100644 index b7efcf7b..00000000 --- a/app/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.exceptions; - -/** - * Thrown if a message is formatted incorrectly. - */ -@SuppressWarnings("serial") -public class MessageFormatException extends DBusException implements NonFatalException -{ - public MessageFormatException(String message) - { - super (message); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java deleted file mode 100644 index 9f6e44e3..00000000 --- a/app/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.exceptions; - -import java.io.IOException; - -@SuppressWarnings("serial") -public class MessageProtocolVersionException extends IOException implements FatalException -{ - public MessageProtocolVersionException(String message) - { - super(message); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java deleted file mode 100644 index b36f30ee..00000000 --- a/app/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.exceptions; - -import java.io.IOException; - -@SuppressWarnings("serial") -public class MessageTypeException extends IOException implements NonFatalException -{ - public MessageTypeException(String message) - { - super(message); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java deleted file mode 100644 index dc565f2e..00000000 --- a/app/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.exceptions; - -public interface NonFatalException -{ -} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java b/app/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java deleted file mode 100644 index 71b8d44a..00000000 --- a/app/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.exceptions; - -/** - * Thrown if a DBus action is called when not connected to the Bus. - */ -@SuppressWarnings("serial") -public class NotConnected extends DBusExecutionException implements FatalException -{ - public NotConnected(String message) - { - super (message); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java deleted file mode 100644 index 76fe2800..00000000 --- a/app/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.exceptions; -import static org.freedesktop.dbus.Gettext.$_; - -@SuppressWarnings("serial") -public class UnknownTypeCodeException extends DBusException implements NonFatalException -{ - public UnknownTypeCodeException(byte code) - { - super($_("Not a valid D-Bus type code: ") + code); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/ProfileStruct.java b/app/src/main/java/org/freedesktop/dbus/test/ProfileStruct.java deleted file mode 100644 index 9fa963b8..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/ProfileStruct.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.dbus.Position; -import org.freedesktop.dbus.Struct; -import org.freedesktop.dbus.UInt32; - -public final class ProfileStruct extends Struct -{ - @Position(0) - public final String a; - @Position(1) - public final UInt32 b; - @Position(2) - public final long c; - - public ProfileStruct(String a, UInt32 b, long c) - { - this.a = a; - this.b = b; - this.c = c; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/Profiler.java b/app/src/main/java/org/freedesktop/dbus/test/Profiler.java deleted file mode 100644 index efe9ab21..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/Profiler.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.DBus.Method.NoReply; -import org.freedesktop.dbus.DBusInterface; -import org.freedesktop.dbus.DBusSignal; -import org.freedesktop.dbus.exceptions.DBusException; -import java.util.List; -import java.util.Map; - -public interface Profiler extends DBusInterface -{ - public class ProfileSignal extends DBusSignal - { - public ProfileSignal(String path) throws DBusException - { - super(path); - } - } - public void array(int[] v); - public void stringarray(String[] v); - public void map(Map m); - public void list(List l); - public void bytes(byte[] b); - public void struct(ProfileStruct ps); - public void string(String s); - public void NoReply(); - public void Pong(); -} - - diff --git a/app/src/main/java/org/freedesktop/dbus/test/ProfilerInstance.java b/app/src/main/java/org/freedesktop/dbus/test/ProfilerInstance.java deleted file mode 100644 index b99d1b75..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/ProfilerInstance.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import java.util.List; -import java.util.Map; - -public class ProfilerInstance implements Profiler -{ - public boolean isRemote() { return false; } - public void array(int[] v) { return; } - public void stringarray(String[] v) { return; } - public void map(Map m) { return; } - public void list(List l) { return; } - public void bytes(byte[] b) { return; } - public void struct(ProfileStruct ps) { return; } - public void string(String s) { return; } - public void NoReply() { return; } - public void Pong() { return; } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestException.java b/app/src/main/java/org/freedesktop/dbus/test/TestException.java deleted file mode 100644 index 2314ec33..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TestException.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.DBus.Description; -import org.freedesktop.dbus.exceptions.DBusExecutionException; - -@Description("A test exception to throw over DBus") -@SuppressWarnings("serial") -public class TestException extends DBusExecutionException -{ - public TestException(String message) - { - super (message); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestNewInterface.java b/app/src/main/java/org/freedesktop/dbus/test/TestNewInterface.java deleted file mode 100644 index 4199403a..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TestNewInterface.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.dbus.DBusInterface; -import org.freedesktop.DBus.Description; - -/** - * A sample remote interface which exports one method. - */ -public interface TestNewInterface extends DBusInterface -{ - /** - * A simple method with no parameters which returns a String - */ - @Description("Simple test method") - public String getName(); -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface.java b/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface.java deleted file mode 100644 index a24ef43b..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.dbus.DBusInterface; -import org.freedesktop.dbus.Path; -import org.freedesktop.dbus.UInt16; -import org.freedesktop.DBus.Description; -import org.freedesktop.DBus.Method; - -import java.lang.reflect.Type; - -import java.util.Map; -import java.util.List; -/** - * A sample remote interface which exports one method. - */ -public interface TestRemoteInterface extends DBusInterface -{ - /** - * A simple method with no parameters which returns a String - */ - @Description("Simple test method") - public String getName(); - public String getNameAndThrow(); - @Description("Test of nested maps") - public int frobnicate(List n, Map> m, T v); - @Description("Throws a TestException when called") - public void throwme() throws TestException; - @Description("Waits then doesn't return") - @Method.NoReply() - public void waitawhile(); - @Description("Interface-overloaded method") - public int overload(); - @Description("Testing Type Signatures") - public void sig(Type[] s); - @Description("Testing object paths as Path objects") - public void newpathtest(Path p); - @Description("Testing the float type") - public float testfloat(float[] f); - @Description("Testing structs of structs") - public int[][] teststructstruct(TestStruct3 in); - @Description("Regression test for #13291") - public void reg13291(byte[] as, byte[] bs); - /* test lots of things involving Path */ - public Path pathrv(Path a); - public List pathlistrv(List a); - public Map pathmaprv(Map a); -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface2.java b/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface2.java deleted file mode 100644 index a44d6279..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface2.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.dbus.DBusInterface; -import org.freedesktop.dbus.DBusInterfaceName; -import org.freedesktop.dbus.DBusMemberName; -import org.freedesktop.dbus.Variant; -import org.freedesktop.DBus.Description; - -import java.util.List; - -@Description("An example remote interface") -@DBusInterfaceName("org.freedesktop.dbus.test.AlternateTestInterface") -public interface TestRemoteInterface2 extends DBusInterface -{ - @Description("Test multiple return values and implicit variant parameters.") - public TestTuple,Boolean> show(A in); - @Description("Test passing structs and explicit variants, returning implicit variants") - public T dostuff(TestStruct foo); - @Description("Test arrays, boxed arrays and lists.") - public List sampleArray(List l, Integer[] is, long[] ls); - @Description("Test passing objects as object paths.") - public DBusInterface getThis(DBusInterface t); - @Description("Test bools work") - @DBusMemberName("checkbool") - public boolean check(); - @Description("Test Serializable Object") - public TestSerializable testSerializable(byte b, TestSerializable s, int i); - @Description("Call another method on itself from within a call") - public String recursionTest(); - @Description("Parameter-overloaded method (string)") - public int overload(String s); - @Description("Parameter-overloaded method (byte)") - public int overload(byte b); - @Description("Parameter-overloaded method (void)") - public int overload(); - @Description("Nested List Check") - public List> checklist(List> lli); - @Description("Get new objects as object paths.") - public TestNewInterface getNew(); - @Description("Test Complex Variants") - public void complexv(Variant v); - @Description("Test Introspect on a different interface") - public String Introspect(); -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestSerializable.java b/app/src/main/java/org/freedesktop/dbus/test/TestSerializable.java deleted file mode 100644 index 80142990..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TestSerializable.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import java.util.List; -import java.util.Vector; - -import org.freedesktop.dbus.DBusSerializable; -import org.freedesktop.dbus.exceptions.DBusException; - -public class TestSerializable implements DBusSerializable -{ - private int a; - private String b; - private Vector c; - public TestSerializable(int a, A b, Vector c) - { - this.a = a; - this.b = b.toString(); - this.c = c; - } - public TestSerializable() {} - public void deserialize(int a, String b, List c) - { - this.a = a; - this.b = b; - this.c = new Vector(c); - } - public Object[] serialize() throws DBusException - { - return new Object[] { a, b, c }; - } - public int getInt() { return a; } - public String getString() { return b; } - public Vector getVector() { return c; } - public String toString() - { - return "TestSerializable{"+a+","+b+","+c+"}"; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface.java b/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface.java deleted file mode 100644 index 406ce36c..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.DBus.Description; -import org.freedesktop.dbus.DBusInterface; -import org.freedesktop.dbus.DBusMemberName; -import org.freedesktop.dbus.DBusSignal; -import org.freedesktop.dbus.Path; -import org.freedesktop.dbus.UInt32; -import org.freedesktop.dbus.exceptions.DBusException; - -import java.util.List; -import java.util.Map; - -/** - * A sample signal with two parameters - */ -@Description("Test interface containing signals") -public interface TestSignalInterface extends DBusInterface -{ - @Description("Test basic signal") - public static class TestSignal extends DBusSignal - { - public final String value; - public final UInt32 number; - /** - * Create a signal. - */ - public TestSignal(String path, String value, UInt32 number) throws DBusException - { - super(path, value, number); - this.value = value; - this.number = number; - } - } - public static class StringSignal extends DBusSignal - { - public final String aoeu; - public StringSignal(String path, String aoeu) throws DBusException - { - super(path, aoeu); - this.aoeu = aoeu; - } - } - public static class EmptySignal extends DBusSignal - { - public EmptySignal(String path) throws DBusException - { - super(path); - } - } - @Description("Test signal with arrays") - public static class TestArraySignal extends DBusSignal - { - public final List v; - public final Map m; - public TestArraySignal(String path, List v, Map m) throws DBusException - { - super(path, v, m); - this.v = v; - this.m = m; - } - } - @Description("Test signal sending an object path") - @DBusMemberName("TestSignalObject") - public static class TestObjectSignal extends DBusSignal - { - public final DBusInterface otherpath; - public TestObjectSignal(String path, DBusInterface otherpath) throws DBusException - { - super(path, otherpath); - this.otherpath = otherpath; - } - } - public static class TestPathSignal extends DBusSignal - { - public final Path otherpath; - public final List pathlist; - public final Map pathmap; - public TestPathSignal(String path, Path otherpath, List pathlist, Map pathmap) throws DBusException - { - super(path, otherpath, pathlist, pathmap); - this.otherpath = otherpath; - this.pathlist = pathlist; - this.pathmap = pathmap; - } - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface2.java b/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface2.java deleted file mode 100644 index d5c9ac52..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface2.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.DBus.Description; -import org.freedesktop.dbus.DBusInterface; -import org.freedesktop.dbus.DBusInterfaceName; -import org.freedesktop.dbus.DBusMemberName; -import org.freedesktop.dbus.DBusSignal; -import org.freedesktop.dbus.UInt32; -import org.freedesktop.dbus.exceptions.DBusException; - -import java.util.List; - -/** - * A sample signal with two parameters - */ -@Description("Test interface containing signals") -@DBusInterfaceName("some.other.interface.Name") -public interface TestSignalInterface2 extends DBusInterface -{ - @Description("Test basic signal") - public static class TestRenamedSignal extends DBusSignal - { - public final String value; - public final UInt32 number; - /** - * Create a signal. - */ - public TestRenamedSignal(String path, String value, UInt32 number) throws DBusException - { - super(path, value, number); - this.value = value; - this.number = number; - } - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestStruct.java b/app/src/main/java/org/freedesktop/dbus/test/TestStruct.java deleted file mode 100644 index ca5deacd..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TestStruct.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.dbus.Position; -import org.freedesktop.dbus.Struct; -import org.freedesktop.dbus.UInt32; -import org.freedesktop.dbus.Variant; - -public final class TestStruct extends Struct -{ - @Position(0) - public final String a; - @Position(1) - public final UInt32 b; - @Position(2) - public final Variant c; - public TestStruct(String a, UInt32 b, Variant c) - { - this.a = a; - this.b = b; - this.c = c; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestStruct2.java b/app/src/main/java/org/freedesktop/dbus/test/TestStruct2.java deleted file mode 100644 index 5c8797b4..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TestStruct2.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.dbus.Position; -import org.freedesktop.dbus.Struct; -import org.freedesktop.dbus.Variant; -import org.freedesktop.dbus.exceptions.DBusException; - -import java.util.List; - -public final class TestStruct2 extends Struct -{ - @Position(0) - public final List a; - @Position(1) - public final Variant b; - public TestStruct2(List a, Variant b) throws DBusException - { - this.a = a; - this.b = b; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestStruct3.java b/app/src/main/java/org/freedesktop/dbus/test/TestStruct3.java deleted file mode 100644 index 52edbe0a..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TestStruct3.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.dbus.Position; -import org.freedesktop.dbus.Struct; -import org.freedesktop.dbus.exceptions.DBusException; - -import java.util.List; - -public final class TestStruct3 extends Struct -{ - @Position(0) - public final TestStruct2 a; - @Position(1) - public final List> b; - public TestStruct3(TestStruct2 a, List> b) throws DBusException - { - this.a = a; - this.b = b; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestTuple.java b/app/src/main/java/org/freedesktop/dbus/test/TestTuple.java deleted file mode 100644 index 1cf507a6..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TestTuple.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.dbus.Position; -import org.freedesktop.dbus.Tuple; - -public final class TestTuple extends Tuple -{ - @Position(0) - public final A a; - @Position(1) - public final B b; - @Position(2) - public final C c; - public TestTuple(A a, B b, C c) - { - this.a = a; - this.b = b; - this.c = c; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TwoPartInterface.java b/app/src/main/java/org/freedesktop/dbus/test/TwoPartInterface.java deleted file mode 100644 index af214cff..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TwoPartInterface.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.dbus.DBusInterface; -import org.freedesktop.dbus.DBusSignal; -import org.freedesktop.dbus.exceptions.DBusException; - -public interface TwoPartInterface extends DBusInterface -{ - public TwoPartObject getNew(); - public class TwoPartSignal extends DBusSignal - { - public final TwoPartObject o; - public TwoPartSignal(String path, TwoPartObject o) throws DBusException - { - super (path, o); - this.o = o; - } - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TwoPartObject.java b/app/src/main/java/org/freedesktop/dbus/test/TwoPartObject.java deleted file mode 100644 index 3c7237b5..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/TwoPartObject.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.dbus.DBusInterface; - -public interface TwoPartObject extends DBusInterface -{ - public String getName(); -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/cross_test_client.java b/app/src/main/java/org/freedesktop/dbus/test/cross_test_client.java deleted file mode 100644 index ae8369b2..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/cross_test_client.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import java.lang.reflect.Array; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import java.util.TreeSet; -import java.util.Vector; - -import org.freedesktop.DBus; -import org.freedesktop.dbus.DBusConnection; -import org.freedesktop.dbus.DBusInterface; -import org.freedesktop.dbus.DBusSigHandler; -import org.freedesktop.dbus.Struct; -import org.freedesktop.dbus.UInt16; -import org.freedesktop.dbus.UInt32; -import org.freedesktop.dbus.UInt64; -import org.freedesktop.dbus.Variant; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; -import org.freedesktop.dbus.types.DBusMapType; - -import cx.ath.matthew.debug.Debug; - -public class cross_test_client implements DBus.Binding.TestClient, DBusSigHandler -{ - private DBusConnection conn; - private static Set passed = new TreeSet(); - private static Map> failed = new HashMap>(); - private static cross_test_client ctc; - static { - List l = new Vector(); - l.add("Signal never arrived"); - failed.put("org.freedesktop.DBus.Binding.TestSignals.Triggered", l); - l = new Vector(); - l.add("Method never called"); - failed.put("org.freedesktop.DBus.Binding.TestClient.Response", l); - } - public cross_test_client(DBusConnection conn) - { - this.conn = conn; - } - public boolean isRemote() { return false; } - public void handle(DBus.Binding.TestSignals.Triggered t) - { - failed.remove("org.freedesktop.DBus.Binding.TestSignals.Triggered"); - if (new UInt64(21389479283L).equals(t.a) && "/Test".equals(t.getPath())) - pass("org.freedesktop.DBus.Binding.TestSignals.Triggered"); - else if (!new UInt64(21389479283L).equals(t.a)) - fail("org.freedesktop.DBus.Binding.TestSignals.Triggered", "Incorrect signal content; expected 21389479283 got "+t.a); - else if (!"/Test".equals(t.getPath())) - fail("org.freedesktop.DBus.Binding.TestSignals.Triggered", "Incorrect signal source object; expected /Test got "+t.getPath()); - } - public void Response(UInt16 a, double b) - { - failed.remove("org.freedesktop.DBus.Binding.TestClient.Response"); - if (a.equals(new UInt16(15)) && (b == 12.5)) - pass("org.freedesktop.DBus.Binding.TestClient.Response"); - else - fail("org.freedesktop.DBus.Binding.TestClient.Response", "Incorrect parameters; expected 15, 12.5 got "+a+", "+b); - } - public static void pass(String test) - { - passed.add(test.replaceAll("[$]", ".")); - } - public static void fail(String test, String reason) - { - test = test.replaceAll("[$]", "."); - List reasons = failed.get(test); - if (null == reasons) { - reasons = new Vector(); - failed.put(test, reasons); - } - reasons.add(reason); - } - @SuppressWarnings("unchecked") - public static void test(Class iface, Object proxy, String method, Object rv, Object... parameters) - { - try { - Method[] ms = iface.getMethods(); - Method m = null; - for (Method t: ms) { - if (t.getName().equals(method)) - m = t; - } - Object o = m.invoke(proxy, parameters); - - String msg = "Incorrect return value; sent ( "; - if (null != parameters) - for (Object po: parameters) - if (null != po) - msg += collapseArray(po) + ","; - msg = msg.replaceAll(".$",");"); - msg += " expected "+collapseArray(rv)+" got "+collapseArray(o); - - if (null != rv && rv.getClass().isArray()) { - compareArray(iface.getName()+"."+method, rv,o); - } else if (rv instanceof Map) { - if (o instanceof Map) { - Map a = (Map) o; - Map b = (Map) rv; - if (a.keySet().size() != b.keySet().size()) { - fail(iface.getName()+"."+method, msg); - } else for (Object k: a.keySet()) - if (a.get(k) instanceof List) { - if (b.get(k) instanceof List) - if (setCompareLists((List) a.get(k), (List) b.get(k))) - ; - else - fail(iface.getName()+"."+method, msg); - else - fail(iface.getName()+"."+method, msg); - } else if (!a.get(k).equals(b.get(k))) { - fail(iface.getName()+"."+method, msg); - return; - } - pass(iface.getName()+"."+method); - } else - fail(iface.getName()+"."+method, msg); - } else { - if (o == rv || (o != null && o.equals(rv))) - pass(iface.getName()+"."+method); - else - fail(iface.getName()+"."+method, msg); - } - } catch (DBusExecutionException DBEe) { - DBEe.printStackTrace(); - fail(iface.getName()+"."+method, "Error occurred during execution: "+DBEe.getClass().getName()+" "+DBEe.getMessage()); - } catch (InvocationTargetException ITe) { - ITe.printStackTrace(); - fail(iface.getName()+"."+method, "Error occurred during execution: "+ITe.getCause().getClass().getName()+" "+ITe.getCause().getMessage()); - } catch (Exception e) { - e.printStackTrace(); - fail(iface.getName()+"."+method, "Error occurred during execution: "+e.getClass().getName()+" "+e.getMessage()); - } - } - @SuppressWarnings("unchecked") - public static String collapseArray(Object array) - { - if (null == array) return "null"; - if (array.getClass().isArray()) { - String s = "{ "; - for (int i = 0; i < Array.getLength(array); i++) - s += collapseArray(Array.get(array, i))+","; - s = s.replaceAll(".$"," }"); - return s; - } else if (array instanceof List) { - String s = "{ "; - for (Object o: (List) array) - s += collapseArray(o)+","; - s = s.replaceAll(".$"," }"); - return s; - } else if (array instanceof Map) { - String s = "{ "; - for (Object o: ((Map) array).keySet()) - s += collapseArray(o)+" => "+collapseArray(((Map) array).get(o))+","; - s = s.replaceAll(".$"," }"); - return s; - } else return array.toString(); - } - public static boolean setCompareLists(List a, List b) - { - if (a.size() != b.size()) return false; - for (Object v: a) - if (!b.contains(v)) return false; - return true; - } - @SuppressWarnings("unchecked") - public static List> PrimitizeRecurse(Object a, Type t) - { - List> vs = new Vector>(); - if (t instanceof ParameterizedType) { - Class c = (Class) ((ParameterizedType) t).getRawType(); - if (List.class.isAssignableFrom(c)) { - Object os; - if (a instanceof List) - os = ((List) a).toArray(); - else - os = a; - Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); - for (int i = 0; i < Array.getLength(os); i++) - vs.addAll(PrimitizeRecurse(Array.get(os, i), ts[0])); - } else if (Map.class.isAssignableFrom(c)) { - Object[] os = ((Map) a).keySet().toArray(); - Object[] ks = ((Map) a).values().toArray(); - Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); - for (int i = 0; i < ks.length; i++) - vs.addAll(PrimitizeRecurse(ks[i], ts[0])); - for (int i = 0; i < os.length; i++) - vs.addAll(PrimitizeRecurse(os[i], ts[1])); - } else if (Struct.class.isAssignableFrom(c)) { - Object[] os = ((Struct) a).getParameters(); - Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); - for (int i = 0; i < os.length; i++) - vs.addAll(PrimitizeRecurse(os[i], ts[i])); - - } else if (Variant.class.isAssignableFrom(c)) { - vs.addAll(PrimitizeRecurse(((Variant) a).getValue(), ((Variant) a).getType())); - } - } else if (Variant.class.isAssignableFrom((Class) t)) - vs.addAll(PrimitizeRecurse(((Variant) a).getValue(), ((Variant) a).getType())); - else if (t instanceof Class && ((Class) t).isArray()) { - Type t2 = ((Class) t).getComponentType(); - for (int i = 0; i < Array.getLength(a); i++) - vs.addAll(PrimitizeRecurse(Array.get(a, i), t2)); - } - else vs.add(new Variant(a)); - - return vs; - } - - @SuppressWarnings("unchecked") - public static List> Primitize(Variant a) - { - return PrimitizeRecurse(a.getValue(), a.getType()); - } - - @SuppressWarnings("unchecked") - public static void primitizeTest(DBus.Binding.Tests tests, Object input) - { - Variant in = new Variant(input); - List> vs = Primitize(in); - List> res; - - try { - - res = tests.Primitize(in); - if (setCompareLists(res, vs)) - pass("org.freedesktop.DBus.Binding.Tests.Primitize"); - else - fail("org.freedesktop.DBus.Binding.Tests.Primitize", "Wrong Return Value; expected "+collapseArray(vs)+" got "+collapseArray(res)); - - } catch (Exception e) { - if (Debug.debug) Debug.print(e); - fail("org.freedesktop.DBus.Binding.Tests.Primitize", "Exception occurred during test: ("+e.getClass().getName()+") "+e.getMessage()); - } - } - public static void doTests(DBus.Peer peer, DBus.Introspectable intro, DBus.Introspectable rootintro, DBus.Binding.Tests tests, DBus.Binding.SingleTests singletests) - { - Random r = new Random(); - int i; - test(DBus.Peer.class, peer, "Ping", null); - - try { if (intro.Introspect().startsWith("(new Integer(1)), new Variant(new Integer(1))); - test(DBus.Binding.Tests.class, tests, "Identity", new Variant("Hello"), new Variant("Hello")); - - test(DBus.Binding.Tests.class, tests, "IdentityBool", false, false); - test(DBus.Binding.Tests.class, tests, "IdentityBool", true, true); - - test(DBus.Binding.Tests.class, tests, "Invert", false, true); - test(DBus.Binding.Tests.class, tests, "Invert", true, false); - - test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) 0, (byte) 0); - test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) 1, (byte) 1); - test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) -1, (byte) -1); - test(DBus.Binding.Tests.class, tests, "IdentityByte", Byte.MAX_VALUE, Byte.MAX_VALUE); - test(DBus.Binding.Tests.class, tests, "IdentityByte", Byte.MIN_VALUE, Byte.MIN_VALUE); - i = r.nextInt(); - test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) i, (byte) i); - - test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) 0, (short) 0); - test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) 1, (short) 1); - test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) -1, (short) -1); - test(DBus.Binding.Tests.class, tests, "IdentityInt16", Short.MAX_VALUE, Short.MAX_VALUE); - test(DBus.Binding.Tests.class, tests, "IdentityInt16", Short.MIN_VALUE, Short.MIN_VALUE); - i = r.nextInt(); - test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) i, (short) i); - - test(DBus.Binding.Tests.class, tests, "IdentityInt32", 0, 0); - test(DBus.Binding.Tests.class, tests, "IdentityInt32", 1, 1); - test(DBus.Binding.Tests.class, tests, "IdentityInt32", -1, -1); - test(DBus.Binding.Tests.class, tests, "IdentityInt32", Integer.MAX_VALUE, Integer.MAX_VALUE); - test(DBus.Binding.Tests.class, tests, "IdentityInt32", Integer.MIN_VALUE, Integer.MIN_VALUE); - i = r.nextInt(); - test(DBus.Binding.Tests.class, tests, "IdentityInt32", i, i); - - - test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) 0, (long) 0); - test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) 1, (long) 1); - test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) -1, (long) -1); - test(DBus.Binding.Tests.class, tests, "IdentityInt64", Long.MAX_VALUE, Long.MAX_VALUE); - test(DBus.Binding.Tests.class, tests, "IdentityInt64", Long.MIN_VALUE, Long.MIN_VALUE); - i = r.nextInt(); - test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) i, (long) i); - - test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(0), new UInt16(0)); - test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(1), new UInt16(1)); - test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(UInt16.MAX_VALUE), new UInt16(UInt16.MAX_VALUE)); - test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(UInt16.MIN_VALUE), new UInt16(UInt16.MIN_VALUE)); - i = r.nextInt(); - i = i > 0 ? i : -i; - test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(i%UInt16.MAX_VALUE), new UInt16(i%UInt16.MAX_VALUE)); - - test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(0), new UInt32(0)); - test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(1), new UInt32(1)); - test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(UInt32.MAX_VALUE), new UInt32(UInt32.MAX_VALUE)); - test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(UInt32.MIN_VALUE), new UInt32(UInt32.MIN_VALUE)); - i = r.nextInt(); - i = i > 0 ? i : -i; - test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(i%UInt32.MAX_VALUE), new UInt32(i%UInt32.MAX_VALUE)); - - test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(0), new UInt64(0)); - test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(1), new UInt64(1)); - test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MAX_LONG_VALUE), new UInt64(UInt64.MAX_LONG_VALUE)); - test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MAX_BIG_VALUE), new UInt64(UInt64.MAX_BIG_VALUE)); - test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MIN_VALUE), new UInt64(UInt64.MIN_VALUE)); - i = r.nextInt(); - i = i > 0 ? i : -i; - test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(i%UInt64.MAX_LONG_VALUE), new UInt64(i%UInt64.MAX_LONG_VALUE)); - - test(DBus.Binding.Tests.class, tests, "IdentityDouble", 0.0, 0.0); - test(DBus.Binding.Tests.class, tests, "IdentityDouble", 1.0, 1.0); - test(DBus.Binding.Tests.class, tests, "IdentityDouble", -1.0, -1.0); - test(DBus.Binding.Tests.class, tests, "IdentityDouble", Double.MAX_VALUE, Double.MAX_VALUE); - test(DBus.Binding.Tests.class, tests, "IdentityDouble", Double.MIN_VALUE, Double.MIN_VALUE); - i = r.nextInt(); - test(DBus.Binding.Tests.class, tests, "IdentityDouble", (double) i, (double) i); - - test(DBus.Binding.Tests.class, tests, "IdentityString", "", ""); - test(DBus.Binding.Tests.class, tests, "IdentityString", "The Quick Brown Fox Jumped Over The Lazy Dog", "The Quick Brown Fox Jumped Over The Lazy Dog"); - test(DBus.Binding.Tests.class, tests, "IdentityString", "ひらがなゲーム - かなぶん", "ひらがなゲーム - かなぶん"); - - testArray(DBus.Binding.Tests.class, tests, "IdentityBoolArray", Boolean.TYPE, null); - testArray(DBus.Binding.Tests.class, tests, "IdentityByteArray", Byte.TYPE, null); - testArray(DBus.Binding.Tests.class, tests, "IdentityInt16Array", Short.TYPE, null); - testArray(DBus.Binding.Tests.class, tests, "IdentityInt32Array", Integer.TYPE, null); - testArray(DBus.Binding.Tests.class, tests, "IdentityInt64Array", Long.TYPE, null); - testArray(DBus.Binding.Tests.class, tests, "IdentityDoubleArray", Double.TYPE, null); - - testArray(DBus.Binding.Tests.class, tests, "IdentityArray", Variant.class, new Variant("aoeu")); - testArray(DBus.Binding.Tests.class, tests, "IdentityUInt16Array", UInt16.class, new UInt16(12)); - testArray(DBus.Binding.Tests.class, tests, "IdentityUInt32Array", UInt32.class, new UInt32(190)); - testArray(DBus.Binding.Tests.class, tests, "IdentityUInt64Array", UInt64.class, new UInt64(103948)); - testArray(DBus.Binding.Tests.class, tests, "IdentityStringArray", String.class, "asdf"); - - int[] is = new int[0]; - test(DBus.Binding.Tests.class, tests, "Sum", 0L, is); - r = new Random(); - int len = (r.nextInt() % 100) + 15; - len = (len<0 ? -len: len)+15; - is = new int[len]; - long result = 0; - for (i = 0; i < len; i++) { - is[i] = r.nextInt(); - result += is[i]; - } - test(DBus.Binding.Tests.class, tests, "Sum", result, is); - - byte[] bs = new byte[0]; - test(DBus.Binding.SingleTests.class, singletests, "Sum", new UInt32(0), bs); - len = (r.nextInt() % 100); - len = (len<0 ? -len: len)+15; - bs = new byte[len]; - int res = 0; - for (i = 0; i < len; i++) { - bs[i] = (byte) r.nextInt(); - res += (bs[i] < 0 ? bs[i]+256 : bs[i]); - } - test(DBus.Binding.SingleTests.class, singletests, "Sum", new UInt32(res % (UInt32.MAX_VALUE+1)), bs); - - test(DBus.Binding.Tests.class, tests, "DeStruct", new DBus.Binding.Triplet("hi", new UInt32(12), new Short((short) 99)), new DBus.Binding.TestStruct("hi", new UInt32(12), new Short((short) 99))); - - Map in = new HashMap(); - Map> out = new HashMap>(); - test(DBus.Binding.Tests.class, tests, "InvertMapping", out, in); - - in.put("hi", "there"); - in.put("to", "there"); - in.put("from", "here"); - in.put("in", "out"); - List l = new Vector(); - l.add("hi"); - l.add("to"); - out.put("there", l); - l = new Vector(); - l.add("from"); - out.put("here", l); - l = new Vector(); - l.add("in"); - out.put("out", l); - test(DBus.Binding.Tests.class, tests, "InvertMapping", out, in); - - primitizeTest(tests, new Integer(1)); - primitizeTest(tests, - new Variant>>>( - new Variant>>( - new Variant>( - new Variant("Hi"))))); - primitizeTest(tests, new Variant>(in, new DBusMapType(String.class,String.class))); - - test(DBus.Binding.Tests.class, tests, "Trigger", null, "/Test", new UInt64(21389479283L)); - - try { - ctc.conn.sendSignal(new DBus.Binding.TestClient.Trigger("/Test", new UInt16(15), 12.5)); - } catch (DBusException DBe) { - if (Debug.debug) Debug.print(DBe); - throw new DBusExecutionException(DBe.getMessage()); - } - - try { Thread.sleep(10000); } catch (InterruptedException Ie) {} - - test(DBus.Binding.Tests.class, tests, "Exit", null); - } - public static void testArray(Class iface, Object proxy, String method, Class arrayType, Object content) - { - Object array = Array.newInstance(arrayType, 0); - test(iface, proxy, method, array, array); - Random r = new Random(); - int l = (r.nextInt() % 100); - array = Array.newInstance(arrayType, (l < 0 ? -l : l) + 15); - if (null != content) - Arrays.fill((Object[]) array, content); - test(iface, proxy, method, array, array); - } - public static void compareArray(String test, Object a, Object b) - { - if (!a.getClass().equals(b.getClass())) { - fail(test, "Incorrect return type; expected "+a.getClass()+" got "+b.getClass()); - return; - } - boolean pass = false; - - if (a instanceof Object[]) - pass = Arrays.equals((Object[]) a, (Object[]) b); - else if (a instanceof byte[]) - pass = Arrays.equals((byte[]) a, (byte[]) b); - else if (a instanceof boolean[]) - pass = Arrays.equals((boolean[]) a, (boolean[]) b); - else if (a instanceof int[]) - pass = Arrays.equals((int[]) a, (int[]) b); - else if (a instanceof short[]) - pass = Arrays.equals((short[]) a, (short[]) b); - else if (a instanceof long[]) - pass = Arrays.equals((long[]) a, (long[]) b); - else if (a instanceof double[]) - pass = Arrays.equals((double[]) a, (double[]) b); - - if (pass) - pass(test); - else { - String s = "Incorrect return value; expected "; - s += collapseArray(a); - s += " got "; - s += collapseArray(b); - fail(test, s); - } - } - - public static void main(String[] args) - { try { - /* init */ - DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); - ctc = new cross_test_client(conn); - conn.exportObject("/Test", ctc); - conn.addSigHandler(DBus.Binding.TestSignals.Triggered.class, ctc); - DBus.Binding.Tests tests = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Binding.Tests.class); - DBus.Binding.SingleTests singletests = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Binding.SingleTests.class); - DBus.Peer peer = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Peer.class); - DBus.Introspectable intro = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Introspectable.class); - - DBus.Introspectable rootintro = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/", DBus.Introspectable.class); - - doTests(peer, intro, rootintro, tests, singletests); - - /* report results */ - for (String s: passed) - System.out.println(s+" pass"); - int i = 1; - for (String s: failed.keySet()) - for (String r: failed.get(s)) { - System.out.println(s+" fail "+i); - System.out.println("report "+i+": "+r); - i++; - } - - conn.disconnect(); - } catch (DBusException DBe) { - DBe.printStackTrace(); - System.exit(1); - }} -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/cross_test_server.java b/app/src/main/java/org/freedesktop/dbus/test/cross_test_server.java deleted file mode 100644 index acfb2fa9..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/cross_test_server.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TreeSet; -import java.util.Set; -import java.util.Vector; - -import org.freedesktop.DBus; -import org.freedesktop.dbus.DBusConnection; -import org.freedesktop.dbus.DBusSigHandler; -import org.freedesktop.dbus.UInt16; -import org.freedesktop.dbus.UInt32; -import org.freedesktop.dbus.UInt64; -import org.freedesktop.dbus.Variant; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; - -public class cross_test_server implements DBus.Binding.Tests, DBus.Binding.SingleTests, DBusSigHandler -{ - private DBusConnection conn; - boolean run = true; - private Set done = new TreeSet(); - private Set notdone = new TreeSet(); - { - notdone.add("org.freedesktop.DBus.Binding.Tests.Identity"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityByte"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityBool"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityDouble"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityString"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityArray"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityByteArray"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray"); - notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityStringArray"); - notdone.add("org.freedesktop.DBus.Binding.Tests.Sum"); - notdone.add("org.freedesktop.DBus.Binding.SingleTests.Sum"); - notdone.add("org.freedesktop.DBus.Binding.Tests.InvertMapping"); - notdone.add("org.freedesktop.DBus.Binding.Tests.DeStruct"); - notdone.add("org.freedesktop.DBus.Binding.Tests.Primitize"); - notdone.add("org.freedesktop.DBus.Binding.Tests.Invert"); - notdone.add("org.freedesktop.DBus.Binding.Tests.Trigger"); - notdone.add("org.freedesktop.DBus.Binding.Tests.Exit"); - notdone.add("org.freedesktop.DBus.Binding.TestClient.Trigger"); - } - - public cross_test_server(DBusConnection conn) - { - this.conn = conn; - } - public boolean isRemote() { return false; } - @SuppressWarnings("unchecked") - @DBus.Description("Returns whatever it is passed") - public Variant Identity(Variant input) - { - done.add("org.freedesktop.DBus.Binding.Tests.Identity"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.Identity"); - return new Variant(input.getValue()); - } - @DBus.Description("Returns whatever it is passed") - public byte IdentityByte(byte input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityByte"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityByte"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public boolean IdentityBool(boolean input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityBool"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityBool"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public short IdentityInt16(short input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt16"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public UInt16 IdentityUInt16(UInt16 input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt16"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public int IdentityInt32(int input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt32"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public UInt32 IdentityUInt32(UInt32 input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt32"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public long IdentityInt64(long input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt64"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public UInt64 IdentityUInt64(UInt64 input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt64"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public double IdentityDouble(double input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityDouble"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityDouble"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public String IdentityString(String input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityString"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityString"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public Variant[] IdentityArray(Variant[] input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityArray"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityArray"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public byte[] IdentityByteArray(byte[] input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityByteArray"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityByteArray"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public boolean[] IdentityBoolArray(boolean[] input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public short[] IdentityInt16Array(short[] input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public UInt16[] IdentityUInt16Array(UInt16[] input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public int[] IdentityInt32Array(int[] input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public UInt32[] IdentityUInt32Array(UInt32[] input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public long[] IdentityInt64Array(long[] input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public UInt64[] IdentityUInt64Array(UInt64[] input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public double[] IdentityDoubleArray(double[] input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray"); - return input; - } - @DBus.Description("Returns whatever it is passed") - public String[] IdentityStringArray(String[] input) - { - done.add("org.freedesktop.DBus.Binding.Tests.IdentityStringArray"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityStringArray"); - return input; - } - @DBus.Description("Returns the sum of the values in the input list") - public long Sum(int[] a) - { - done.add("org.freedesktop.DBus.Binding.Tests.Sum"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.Sum"); - long sum = 0; - for (int b: a) sum += b; - return sum; - } - @DBus.Description("Returns the sum of the values in the input list") - public UInt32 Sum(byte[] a) - { - done.add("org.freedesktop.DBus.Binding.SingleTests.Sum"); - notdone.remove("org.freedesktop.DBus.Binding.SingleTests.Sum"); - int sum = 0; - for (byte b: a) sum += (b < 0 ? b+256 : b); - return new UInt32(sum % (UInt32.MAX_VALUE+1)); - } - @DBus.Description("Given a map of A => B, should return a map of B => a list of all the As which mapped to B") - public Map> InvertMapping(Map a) - { - done.add("org.freedesktop.DBus.Binding.Tests.InvertMapping"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.InvertMapping"); - HashMap> m = new HashMap>(); - for (String s: a.keySet()) { - String b = a.get(s); - List l = m.get(b); - if (null == l) { - l = new Vector(); - m.put(b, l); - } - l.add(s); - } - return m; - } - @DBus.Description("This method returns the contents of a struct as separate values") - public DBus.Binding.Triplet DeStruct(DBus.Binding.TestStruct a) - { - done.add("org.freedesktop.DBus.Binding.Tests.DeStruct"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.DeStruct"); - return new DBus.Binding.Triplet(a.a, a.b, a.c); - } - @DBus.Description("Given any compound type as a variant, return all the primitive types recursively contained within as an array of variants") - @SuppressWarnings("unchecked") - public List> Primitize(Variant a) - { - done.add("org.freedesktop.DBus.Binding.Tests.Primitize"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.Primitize"); - return cross_test_client.PrimitizeRecurse(a.getValue(), a.getType()); - } - @DBus.Description("inverts it's input") - public boolean Invert(boolean a) - { - done.add("org.freedesktop.DBus.Binding.Tests.Invert"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.Invert"); - return !a; - } - @DBus.Description("triggers sending of a signal from the supplied object with the given parameter") - public void Trigger(String a, UInt64 b) - { - done.add("org.freedesktop.DBus.Binding.Tests.Trigger"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.Trigger"); - try { - conn.sendSignal(new DBus.Binding.TestSignals.Triggered(a, b)); - } catch (DBusException DBe) { - throw new DBusExecutionException(DBe.getMessage()); - } - } - public void Exit() - { - done.add("org.freedesktop.DBus.Binding.Tests.Exit"); - notdone.remove("org.freedesktop.DBus.Binding.Tests.Exit"); - run = false; - synchronized (this) { - notifyAll(); - } - } - public void handle(DBus.Binding.TestClient.Trigger t) - { - done.add("org.freedesktop.DBus.Binding.TestClient.Trigger"); - notdone.remove("org.freedesktop.DBus.Binding.TestClient.Trigger"); - try { - DBus.Binding.TestClient cb = conn.getRemoteObject(t.getSource(), "/Test", DBus.Binding.TestClient.class); - cb.Response(t.a, t.b); - } catch (DBusException DBe) { - throw new DBusExecutionException(DBe.getMessage()); - } - } - - public static void main(String[] args) - { try { - DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); - conn.requestBusName("org.freedesktop.DBus.Binding.TestServer"); - cross_test_server cts = new cross_test_server(conn); - conn.addSigHandler(DBus.Binding.TestClient.Trigger.class, cts); - conn.exportObject("/Test", cts); - synchronized (cts) { - while (cts.run) { - try { - cts.wait(); - } catch (InterruptedException Ie) {} - } - } - for (String s: cts.done) - System.out.println(s+" ok"); - for (String s: cts.notdone) - System.out.println(s+" untested"); - conn.disconnect(); - System.exit(0); - } catch (DBusException DBe) { - DBe.printStackTrace(); - System.exit(1); - }} -} - diff --git a/app/src/main/java/org/freedesktop/dbus/test/profile.java b/app/src/main/java/org/freedesktop/dbus/test/profile.java deleted file mode 100644 index f998fa2c..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/profile.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import java.util.Random; -import java.util.HashMap; -import java.util.Vector; - -import org.freedesktop.DBus.Peer; -import org.freedesktop.DBus.Introspectable; -import org.freedesktop.dbus.DBusConnection; -import org.freedesktop.dbus.DBusSigHandler; -import org.freedesktop.dbus.UInt32; - -class ProfileHandler implements DBusSigHandler -{ - public int c = 0; - public void handle(Profiler.ProfileSignal s) - { - if (0 == (c++%profile.SIGNAL_INNER)) System.out.print("-"); - } -} - -/** - * Profiling tests. - */ -public class profile -{ - public static final int SIGNAL_INNER = 100; - public static final int SIGNAL_OUTER = 100; - public static final int PING_INNER = 100; - public static final int PING_OUTER = 100; - public static final int BYTES = 2000000; - public static final int INTROSPECTION_OUTER = 100; - public static final int INTROSPECTION_INNER = 10; - public static final int STRUCT_OUTER = 100; - public static final int STRUCT_INNER = 10; - public static final int LIST_OUTER = 100; - public static final int LIST_INNER = 10; - public static final int LIST_LENGTH = 100; - public static final int MAP_OUTER = 100; - public static final int MAP_INNER = 10; - public static final int MAP_LENGTH = 100; - public static final int ARRAY_OUTER = 100; - public static final int ARRAY_INNER = 10; - public static final int ARRAY_LENGTH = 1000; - public static final int STRING_ARRAY_OUTER = 10; - public static final int STRING_ARRAY_INNER = 1; - public static final int STRING_ARRAY_LENGTH = 20000; - - public static class Log - { - private long last; - private int[] deltas; - private int current = 0; - public Log(int size) - { - deltas = new int[size]; - } - public void start() - { - last = System.currentTimeMillis(); - } - public void stop() - { - deltas[current] = (int) (System.currentTimeMillis()-last); - current++; - } - public double mean() - { - if (0 == current) return 0; - long sum = 0; - for (int i = 0; i < current; i++) - sum+=deltas[i]; - return sum /= current; - } - public long min() - { - int m = Integer.MAX_VALUE; - for (int i = 0; i < current; i++) - if (deltas[i] < m) m = deltas[i]; - return m; - } - public long max() - { - int m = 0; - for (int i = 0; i < current; i++) - if (deltas[i] > m) m = deltas[i]; - return m; - } - public double stddev() - { - double mean = mean(); - double sum = 0; - for (int i=0; i < current; i++) - sum += (deltas[i]-mean)*(deltas[i]-mean); - return Math.sqrt(sum / (current-1)); - } - } - public static void main(String[] args) - { - try { - if (0==args.length) { - System.out.println("You must specify a profile type."); - System.out.println("Syntax: profile "); - System.exit(1); - } - DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); - conn.requestBusName("org.freedesktop.DBus.java.profiler"); - if ("pings".equals(args[0])) { - int count = PING_INNER*PING_OUTER; - System.out.print("Sending "+count+" pings..."); - Peer p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Peer.class); - Log l = new Log(count); - long t = System.currentTimeMillis(); - for (int i = 0; i < PING_OUTER; i++) { - for (int j = 0; j < PING_INNER; j++) { - l.start(); - p.Ping(); - l.stop(); - } - System.out.print("."); - } - t = System.currentTimeMillis()-t; - System.out.println(" done."); - System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); - System.out.println("deviation: "+l.stddev()); - System.out.println("Total time: "+t+"ms"); - } else if ("strings".equals(args[0])) { - int count = STRING_ARRAY_INNER*STRING_ARRAY_OUTER; - System.out.print("Sending array of "+STRING_ARRAY_LENGTH+" strings "+count+" times."); - ProfilerInstance pi = new ProfilerInstance(); - conn.exportObject("/Profiler", pi); - Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); - String[] v = new String[STRING_ARRAY_LENGTH]; - Random r = new Random(); - for (int i = 0; i < STRING_ARRAY_LENGTH; i++) v[i] = ""+r.nextInt(); - Log l = new Log(count); - long t = System.currentTimeMillis(); - for (int i = 0; i < STRING_ARRAY_OUTER; i++) { - for (int j = 0; j < STRING_ARRAY_INNER; j++) { - l.start(); - p.stringarray(v); - l.stop(); - } - System.out.print("."); - } - t = System.currentTimeMillis()-t; - System.out.println(" done."); - System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); - System.out.println("deviation: "+l.stddev()); - System.out.println("Total time: "+t+"ms"); - } else if ("arrays".equals(args[0])) { - int count = ARRAY_INNER*ARRAY_OUTER; - System.out.print("Sending array of "+ARRAY_LENGTH+" ints "+count+" times."); - ProfilerInstance pi = new ProfilerInstance(); - conn.exportObject("/Profiler", pi); - Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); - int[] v = new int[ARRAY_LENGTH]; - Random r = new Random(); - for (int i = 0; i < ARRAY_LENGTH; i++) v[i] = r.nextInt(); - Log l = new Log(count); - long t = System.currentTimeMillis(); - for (int i = 0; i < ARRAY_OUTER; i++) { - for (int j = 0; j < ARRAY_INNER; j++) { - l.start(); - p.array(v); - l.stop(); - } - System.out.print("."); - } - t = System.currentTimeMillis()-t; - System.out.println(" done."); - System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); - System.out.println("deviation: "+l.stddev()); - System.out.println("Total time: "+t+"ms"); - } else if ("maps".equals(args[0])) { - int count = MAP_INNER*MAP_OUTER; - System.out.print("Sending map of "+MAP_LENGTH+" string=>strings "+count+" times."); - ProfilerInstance pi = new ProfilerInstance(); - conn.exportObject("/Profiler", pi); - Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); - HashMap m = new HashMap(); - for (int i = 0; i < MAP_LENGTH; i++) - m.put(""+i, "hello"); - Log l = new Log(count); - long t = System.currentTimeMillis(); - for (int i = 0; i < MAP_OUTER; i++) { - for (int j=0; j < MAP_INNER; j++) { - l.start(); - p.map(m); - l.stop(); - } - System.out.print("."); - } - t = System.currentTimeMillis()-t; - System.out.println(" done."); - System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); - System.out.println("deviation: "+l.stddev()); - System.out.println("Total time: "+t+"ms"); - } else if ("lists".equals(args[0])) { - int count = LIST_OUTER*LIST_INNER; - System.out.print("Sending list of "+LIST_LENGTH+" strings "+count+" times."); - ProfilerInstance pi = new ProfilerInstance(); - conn.exportObject("/Profiler", pi); - Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); - Vector v = new Vector(); - for (int i = 0; i < LIST_LENGTH; i++) - v.add("hello "+i); - Log l = new Log(count); - long t = System.currentTimeMillis(); - for (int i = 0; i < LIST_OUTER; i++) { - for (int j=0; j < LIST_INNER; j++) { - l.start(); - p.list(v); - l.stop(); - } - System.out.print("."); - } - t = System.currentTimeMillis()-t; - System.out.println(" done."); - System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); - System.out.println("deviation: "+l.stddev()); - System.out.println("Total time: "+t+"ms"); - } else if ("structs".equals(args[0])) { - int count = STRUCT_OUTER*STRUCT_INNER; - System.out.print("Sending a struct "+count+" times."); - ProfilerInstance pi = new ProfilerInstance(); - conn.exportObject("/Profiler", pi); - Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); - ProfileStruct ps = new ProfileStruct("hello", new UInt32(18), 500L); - Log l = new Log(count); - long t = System.currentTimeMillis(); - for (int i = 0; i < STRUCT_OUTER; i++) { - for (int j=0; j < STRUCT_INNER; j++) { - l.start(); - p.struct(ps); - l.stop(); - } - System.out.print("."); - } - t = System.currentTimeMillis()-t; - System.out.println(" done."); - System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); - System.out.println("deviation: "+l.stddev()); - System.out.println("Total time: "+t+"ms"); - } else if ("introspect".equals(args[0])) { - int count = INTROSPECTION_OUTER*INTROSPECTION_INNER; - System.out.print("Recieving introspection data "+count+" times."); - ProfilerInstance pi = new ProfilerInstance(); - conn.exportObject("/Profiler", pi); - Introspectable is = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Introspectable.class); - Log l = new Log(count); - long t = System.currentTimeMillis(); - String s = null; - for (int i = 0; i < INTROSPECTION_OUTER; i++) { - for (int j = 0; j < INTROSPECTION_INNER; j++) { - l.start(); - s = is.Introspect(); - l.stop(); - } - System.out.print("."); - } - t = System.currentTimeMillis()-t; - System.out.println(" done."); - System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); - System.out.println("deviation: "+l.stddev()); - System.out.println("Total time: "+t+"ms"); - System.out.println("Introspect data: "+s); - } else if ("bytes".equals(args[0])) { - System.out.print("Sending "+BYTES+" bytes"); - ProfilerInstance pi = new ProfilerInstance(); - conn.exportObject("/Profiler", pi); - Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); - byte[] bs = new byte[BYTES]; - for (int i = 0; i < BYTES; i++) - bs[i] = (byte) i; - long t = System.currentTimeMillis(); - p.bytes(bs); - System.out.println(" done in "+(System.currentTimeMillis()-t)+"ms."); - } else if ("rate".equals(args[0])) { - ProfilerInstance pi = new ProfilerInstance(); - conn.exportObject("/Profiler", pi); - Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); - Peer peer = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Peer.class); - conn.changeThreadCount((byte)1); - - long start = System.currentTimeMillis(); - int count = 0; - do { - p.Pong(); - count++; - } while(count < 10000); - long end = System.currentTimeMillis(); - System.out.println("No payload: "+((count*1000)/(end-start))+" RT/second"); - start = System.currentTimeMillis(); - count = 0; - do { - p.Pong(); - count++; - } while(count < 10000); - peer.Ping(); - end = System.currentTimeMillis(); - System.out.println("No payload, One way: "+((count*1000)/(end-start))+" /second"); - int len = 256; - while (len <= 32768) { - byte[] bs = new byte[len]; - count = 0; - start = System.currentTimeMillis(); - do { - p.bytes(bs); - count++; - } while(count < 1000); - end = System.currentTimeMillis(); - long ms = end-start; - double cps = (count*1000)/ms; - double rate = (len*cps)/(1024.0*1024.0); - System.out.println(len+" byte array) "+(count*len)+" bytes in "+ms+"ms (in "+count+" calls / "+(int)cps+" CPS): "+rate+"MB/s"); - len <<= 1; - } - len = 256; - while (len <= 32768) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < len; i++) sb.append('a'); - String s = sb.toString(); - end = System.currentTimeMillis()+500; - count = 0; - do { - p.string(s); - count++; - } while(count < 1000); - long ms = end-start; - double cps = (count*1000)/ms; - double rate = (len*cps)/(1024.0*1024.0); - System.out.println(len+" string) "+(count*len)+" bytes in "+ms+"ms (in "+count+" calls / "+(int)cps+" CPS): "+rate+"MB/s"); - len <<= 1; - } - } else if ("signals".equals(args[0])) { - int count = SIGNAL_OUTER*SIGNAL_INNER; - System.out.print("Sending "+count+" signals"); - ProfileHandler ph = new ProfileHandler(); - conn.addSigHandler(Profiler.ProfileSignal.class, ph); - Log l = new Log(count); - Profiler.ProfileSignal ps = new Profiler.ProfileSignal("/"); - long t = System.currentTimeMillis(); - for (int i = 0; i < SIGNAL_OUTER; i++) { - for (int j = 0; j < SIGNAL_INNER; j++) { - l.start(); - conn.sendSignal(ps); - l.stop(); - } - System.out.print("."); - } - t = System.currentTimeMillis()-t; - System.out.println(" done."); - System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); - System.out.println("deviation: "+l.stddev()); - System.out.println("Total time: "+t+"ms"); - while (ph.c < count) try { Thread.sleep(100); } - catch (InterruptedException Ie) {}; - } else { - conn.disconnect(); - System.out.println("Invalid profile ``"+args[0]+"''."); - System.out.println("Syntax: profile "); - System.exit(1); - } - conn.disconnect(); - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/test.java b/app/src/main/java/org/freedesktop/dbus/test/test.java deleted file mode 100644 index 4ccd696e..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/test.java +++ /dev/null @@ -1,965 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Vector; - -import java.text.Collator; - -import org.freedesktop.dbus.CallbackHandler; -import org.freedesktop.dbus.DBusAsyncReply; -import org.freedesktop.dbus.DBusCallInfo; -import org.freedesktop.dbus.DBusConnection; -import org.freedesktop.dbus.DBusInterface; -import org.freedesktop.dbus.DBusSigHandler; -import org.freedesktop.dbus.DBusSignal; -import org.freedesktop.dbus.Marshalling; -import org.freedesktop.dbus.Path; -import org.freedesktop.dbus.UInt16; -import org.freedesktop.dbus.UInt32; -import org.freedesktop.dbus.UInt64; -import org.freedesktop.dbus.Variant; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.exceptions.DBusExecutionException; -import org.freedesktop.dbus.exceptions.NotConnected; - -import org.freedesktop.DBus; -import org.freedesktop.DBus.Error.MatchRuleInvalid; -import org.freedesktop.DBus.Error.ServiceUnknown; -import org.freedesktop.DBus.Error.UnknownObject; -import org.freedesktop.DBus.Peer; -import org.freedesktop.DBus.Introspectable; -import org.freedesktop.DBus.Properties; - -class testnewclass implements TestNewInterface -{ - public boolean isRemote() { return false; } - public String getName() - { - return toString(); - } -} - -class testclass implements TestRemoteInterface, TestRemoteInterface2, TestSignalInterface, TestSignalInterface2, Properties -{ - private DBusConnection conn; - public testclass(DBusConnection conn) - { - this.conn = conn; - } - public String Introspect() - { - return "Not XML"; - } - public int[][] teststructstruct(TestStruct3 in) - { - List> lli = in.b; - int[][] out = new int[lli.size()][]; - for (int j = 0; j < out.length; j++) { - out[j] = new int[lli.get(j).size()]; - for (int k = 0; k < out[j].length; k++) - out[j][k] = lli.get(j).get(k); - } - return out; - } - public float testfloat(float[] f) - { - if (f.length < 4 || - f[0] != 17.093f || - f[1] != -23f || - f[2] != 0.0f || - f[3] != 31.42f) - test.fail("testfloat got incorrect array"); - return f[0]; - } - public void newpathtest(Path p) - { - if (!p.toString().equals("/new/path/test")) - test.fail("new path test got wrong path"); - } - public void waitawhile() - { - System.out.println("Sleeping."); - try { - Thread.sleep(1000); - } catch (InterruptedException Ie) {} - System.out.println("Done sleeping."); - } - public TestTuple, Boolean> show(A in) - { - System.out.println("Showing Stuff: "+in.getClass()+"("+in+")"); - if (!(in instanceof Integer) || ((Integer) in).intValue() != 234) - test.fail("show received the wrong arguments"); - DBusCallInfo info = DBusConnection.getCallInfo(); - List l = new Vector(); - l.add(1953); - return new TestTuple, Boolean>(info.getSource(), l, true); - } - @SuppressWarnings("unchecked") - public T dostuff(TestStruct foo) - { - System.out.println("Doing Stuff "+foo); - System.out.println(" -- ("+foo.a.getClass()+", "+foo.b.getClass()+", "+foo.c.getClass()+")"); - if (!(foo instanceof TestStruct) || - !(foo.a instanceof String) || - !(foo.b instanceof UInt32) || - !(foo.c instanceof Variant) || - !"bar".equals(foo.a) || - foo.b.intValue() != 52 || - !(foo.c.getValue() instanceof Boolean) || - ((Boolean) foo.c.getValue()).booleanValue() != true) - test.fail("dostuff received the wrong arguments"); - return (T) foo.c.getValue(); - } - /** Local classes MUST implement this to return false */ - public boolean isRemote() { return false; } - /** The method we are exporting to the Bus. */ - public List sampleArray(List ss, Integer[] is, long[] ls) - { - System.out.println("Got an array:"); - for (String s: ss) - System.out.println("--"+s); - if (ss.size()!= 5 || - !"hi".equals(ss.get(0)) || - !"hello".equals(ss.get(1)) || - !"hej".equals(ss.get(2)) || - !"hey".equals(ss.get(3)) || - !"aloha".equals(ss.get(4))) - test.fail("sampleArray, String array contents incorrect"); - System.out.println("Got an array:"); - for (Integer i: is) - System.out.println("--"+i); - if (is.length != 4 || - is[0].intValue() != 1 || - is[1].intValue() != 5 || - is[2].intValue() != 7 || - is[3].intValue() != 9) - test.fail("sampleArray, Integer array contents incorrect"); - System.out.println("Got an array:"); - for (long l: ls) - System.out.println("--"+l); - if (ls.length != 4 || - ls[0] != 2 || - ls[1] != 6 || - ls[2] != 8 || - ls[3] != 12) - test.fail("sampleArray, Integer array contents incorrect"); - Vector v = new Vector(); - v.add(-1); - v.add(-5); - v.add(-7); - v.add(-12); - v.add(-18); - return v; - } - public String getName() - { - return "This Is A UTF-8 Name: س !!"; - } - public String getNameAndThrow() throws TestException - { - throw new TestException("test"); - } - public boolean check() - { - System.out.println("Being checked"); - return false; - } - public int frobnicate(List n, Map> m, T v) - { - if (null == n) - test.fail("List was null"); - if (n.size() != 3) - test.fail("List was wrong size (expected 3, actual "+n.size()+")"); - if (n.get(0) != 2L || - n.get(1) != 5L || - n.get(2) != 71L) - test.fail("List has wrong contents"); - if (!(v instanceof Integer)) - test.fail("v not an Integer"); - if (((Integer) v) != 13) - test.fail("v is incorrect"); - if (null == m) - test.fail("Map was null"); - if (m.size() != 1) - test.fail("Map was wrong size"); - if (!m.keySet().contains("stuff")) - test.fail("Incorrect key"); - Map mus = m.get("stuff"); - if (null == mus) - test.fail("Sub-Map was null"); - if (mus.size() != 3) - test.fail("Sub-Map was wrong size"); - if (!(new Short((short)5).equals(mus.get(new UInt16(4))))) - test.fail("Sub-Map has wrong contents"); - if (!(new Short((short)6).equals(mus.get(new UInt16(5))))) - test.fail("Sub-Map has wrong contents"); - if (!(new Short((short)7).equals(mus.get(new UInt16(6))))) - test.fail("Sub-Map has wrong contents"); - return -5; - } - public DBusInterface getThis(DBusInterface t) - { - if (!t.equals(this)) - test.fail("Didn't get this properly"); - return this; - } - public void throwme() throws TestException - { - throw new TestException("test"); - } - public TestSerializable testSerializable(byte b, TestSerializable s, int i) - { - System.out.println("Recieving TestSerializable: "+s); - if ( b != 12 - || i != 13 - || !(s.getInt() == 1) - || !(s.getString().equals("woo")) - || !(s.getVector().size() == 3) - || !(s.getVector().get(0) == 1) - || !(s.getVector().get(1) == 2) - || !(s.getVector().get(2) == 3) ) - test.fail("Error in recieving custom synchronisation"); - return s; - } - public String recursionTest() - { - try { - TestRemoteInterface tri = conn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface.class); - return tri.getName(); - } catch (DBusException DBe) { - test.fail("Failed with error: "+DBe); - return ""; - } - } - public int overload(String s) - { - return 1; - } - public int overload(byte b) - { - return 2; - } - public int overload() - { - DBusCallInfo info = DBusConnection.getCallInfo(); - if ("org.freedesktop.dbus.test.AlternateTestInterface".equals(info.getInterface())) - return 3; - else if ("org.freedesktop.dbus.test.TestRemoteInterface".equals(info.getInterface())) - return 4; - else - return -1; - } - public List> checklist(List> lli) - { - return lli; - } - public TestNewInterface getNew() - { - testnewclass n = new testnewclass(); - try { - conn.exportObject("/new", n); - } catch (DBusException DBe) - { throw new DBusExecutionException(DBe.getMessage()); } - return n; - } - public void sig(Type[] s) - { - if (s.length != 2 - || !s[0].equals(Byte.class) - || ! (s[1] instanceof ParameterizedType) - || ! Map.class.equals(((ParameterizedType) s[1]).getRawType()) - || ((ParameterizedType) s[1]).getActualTypeArguments().length != 2 - || ! String.class.equals(((ParameterizedType) s[1]).getActualTypeArguments()[0]) - || ! Integer.class.equals(((ParameterizedType) s[1]).getActualTypeArguments()[1])) - test.fail("Didn't send types correctly"); - } - @SuppressWarnings("unchecked") - public void complexv(Variant v) - { - if (!"a{ss}".equals(v.getSig()) - || ! (v.getValue() instanceof Map) - || ((Map) v.getValue()).size() != 1 - || !"moo".equals(((Map) v.getValue()).get("cow"))) - test.fail("Didn't send variant correctly"); - } - public void reg13291(byte[] as, byte[] bs) - { - if (as.length != bs.length) test.fail("didn't receive identical byte arrays"); - for (int i = 0; i < as.length; i++) - if (as[i] != bs[i]) test.fail("didn't receive identical byte arrays"); - } - @SuppressWarnings("unchecked") - public A Get (String interface_name, String property_name) - { - return (A) new Path("/nonexistant/path"); - } - public void Set (String interface_name, String property_name, A value) {} - public Map GetAll (String interface_name) { return new HashMap(); } - public Path pathrv(Path a) { return a; } - public List pathlistrv(List a) { return a; } - public Map pathmaprv(Map a) { return a; } -} - -/** - * Typed signal handler for renamed signal - */ -class renamedsignalhandler implements DBusSigHandler -{ - /** Handling a signal */ - public void handle(TestSignalInterface2.TestRenamedSignal t) - { - if (false == test.done5) { - test.done5 = true; - } else { - test.fail("SignalHandler R has been run too many times"); - } - System.out.println("SignalHandler R Running"); - System.out.println("string("+t.value+") int("+t.number+")"); - if (!"Bar".equals(t.value) || !(new UInt32(42)).equals(t.number)) - test.fail("Incorrect TestRenamedSignal parameters"); - } -} - -/** - * Empty signal handler - */ -class emptysignalhandler implements DBusSigHandler -{ - /** Handling a signal */ - public void handle(TestSignalInterface.EmptySignal t) - { - if (false == test.done7) { - test.done7 = true; - } else { - test.fail("SignalHandler E has been run too many times"); - } - System.out.println("SignalHandler E Running"); - } -} -/** - * Disconnect handler - */ -class disconnecthandler implements DBusSigHandler -{ - private DBusConnection conn; - private renamedsignalhandler sh; - public disconnecthandler(DBusConnection conn, renamedsignalhandler sh) - { - this.conn = conn; - this.sh = sh; - } - /** Handling a signal */ - public void handle(DBus.Local.Disconnected t) - { - if (false == test.done6) { - test.done6 = true; - System.out.println("Handling disconnect, unregistering handler"); - try { - conn.removeSigHandler(TestSignalInterface2.TestRenamedSignal.class, sh); - } catch (DBusException DBe) { - DBe.printStackTrace(); - test.fail("Disconnect handler threw an exception: "+DBe); - } - } - } -} - - -/** - * Typed signal handler - */ -class pathsignalhandler implements DBusSigHandler -{ - /** Handling a signal */ - public void handle(TestSignalInterface.TestPathSignal t) - { - System.out.println("Path sighandler: "+t); - } -} - -/** - * Typed signal handler - */ -class signalhandler implements DBusSigHandler -{ - /** Handling a signal */ - public void handle(TestSignalInterface.TestSignal t) - { - if (false == test.done1) { - test.done1 = true; - } else { - test.fail("SignalHandler 1 has been run too many times"); - } - System.out.println("SignalHandler 1 Running"); - System.out.println("string("+t.value+") int("+t.number+")"); - if (!"Bar".equals(t.value) || !(new UInt32(42)).equals(t.number)) - test.fail("Incorrect TestSignal parameters"); - } -} - -/** - * Untyped signal handler - */ -class arraysignalhandler implements DBusSigHandler -{ - /** Handling a signal */ - public void handle(TestSignalInterface.TestArraySignal t) - { - try { - if (false == test.done2) { - test.done2 = true; - } else { - test.fail("SignalHandler 2 has been run too many times"); - } - System.out.println("SignalHandler 2 Running"); - if (t.v.size() != 1) test.fail("Incorrect TestArraySignal array length: should be 1, actually "+t.v.size()); - System.out.println("Got a test array signal with Parameters: "); - for (String str: t.v.get(0).a) - System.out.println("--"+str); - System.out.println(t.v.get(0).b.getType()); - System.out.println(t.v.get(0).b.getValue()); - if (!(t.v.get(0).b.getValue() instanceof UInt64) || - 567L != ((UInt64) t.v.get(0).b.getValue()).longValue() || - t.v.get(0).a.size() != 5 || - !"hi".equals(t.v.get(0).a.get(0)) || - !"hello".equals(t.v.get(0).a.get(1)) || - !"hej".equals(t.v.get(0).a.get(2)) || - !"hey".equals(t.v.get(0).a.get(3)) || - !"aloha".equals(t.v.get(0).a.get(4))) - test.fail("Incorrect TestArraySignal parameters"); - - if (t.m.keySet().size() != 2) test.fail("Incorrect TestArraySignal map size: should be 2, actually "+t.m.keySet().size()); - if (!(t.m.get(new UInt32(1)).b.getValue() instanceof UInt64) || - 678L != ((UInt64) t.m.get(new UInt32(1)).b.getValue()).longValue() || - !(t.m.get(new UInt32(42)).b.getValue() instanceof UInt64) || - 789L != ((UInt64) t.m.get(new UInt32(42)).b.getValue()).longValue()) - test.fail("Incorrect TestArraySignal parameters"); - - } catch (Exception e) { - e.printStackTrace(); - test.fail("SignalHandler 2 threw an exception: "+e); - } - } -} - -/** - * Object path signal handler - */ -class objectsignalhandler implements DBusSigHandler -{ - public void handle(TestSignalInterface.TestObjectSignal s) - { - if (false == test.done3) { - test.done3 = true; - } else { - test.fail("SignalHandler 3 has been run too many times"); - } - System.out.println(s.otherpath); - } -} - -/** - * handler which should never be called - */ -class badarraysignalhandler implements DBusSigHandler -{ - /** Handling a signal */ - public void handle(T s) - { - test.fail("This signal handler shouldn't be called"); - } -} - -/** - * Callback handler - */ -class callbackhandler implements CallbackHandler -{ - public void handle(String r) - { - System.out.println("Handling callback: "+r); - Collator col = Collator.getInstance(); - col.setDecomposition(Collator.FULL_DECOMPOSITION); - col.setStrength(Collator.PRIMARY); - if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!", r)) - test.fail("call with callback, wrong return value"); - if (test.done4) test.fail("Already ran callback handler"); - test.done4 = true; - } - public void handleError(DBusExecutionException e) - { - System.out.println("Handling error callback: "+e+" message = '"+e.getMessage()+"'"); - if (!(e instanceof TestException)) test.fail("Exception is of the wrong sort"); - Collator col = Collator.getInstance(); - col.setDecomposition(Collator.FULL_DECOMPOSITION); - col.setStrength(Collator.PRIMARY); - if (0 != col.compare("test", e.getMessage())) - test.fail("Exception has the wrong message"); - if (test.done8) test.fail("Already ran callback error handler"); - test.done8=true; - } -} - -/** - * This is a test program which sends and recieves a signal, implements, exports and calls a remote method. - */ -public class test -{ - public static boolean done1 = false; - public static boolean done2 = false; - public static boolean done3 = false; - public static boolean done4 = false; - public static boolean done5 = false; - public static boolean done6 = false; - public static boolean done7 = false; - public static boolean done8 = false; - public static void fail(String message) - { - System.out.println("Test Failed: "+message); - System.err.println("Test Failed: "+message); - if (null != serverconn) serverconn.disconnect(); - if (null != clientconn) clientconn.disconnect(); - System.exit(1); - } - static DBusConnection serverconn = null; - static DBusConnection clientconn = null; - @SuppressWarnings("unchecked") - public static void main(String[] args) - { try { - System.out.println("Creating Connection"); - serverconn = DBusConnection.getConnection(DBusConnection.SESSION); - clientconn = DBusConnection.getConnection(DBusConnection.SESSION); - serverconn.setWeakReferences(true); - clientconn.setWeakReferences(true); - - System.out.println("Registering Name"); - serverconn.requestBusName("foo.bar.Test"); - - /** This gets a remote object matching our bus name and exported object path. */ - Peer peer = clientconn.getRemoteObject("foo.bar.Test", "/Test", Peer.class); - DBus dbus = clientconn.getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); - - System.out.print("Listening for signals..."); - signalhandler sigh = new signalhandler(); - renamedsignalhandler rsh = new renamedsignalhandler(); - try { - /** This registers an instance of the test class as the signal handler for the TestSignal class. */ - clientconn.addSigHandler(TestSignalInterface.EmptySignal.class, new emptysignalhandler()); - clientconn.addSigHandler(TestSignalInterface.TestSignal.class, sigh); - clientconn.addSigHandler(TestSignalInterface2.TestRenamedSignal.class, rsh); - clientconn.addSigHandler(DBus.Local.Disconnected.class, new disconnecthandler(clientconn, rsh)); - String source = dbus.GetNameOwner("foo.bar.Test"); - clientconn.addSigHandler(TestSignalInterface.TestArraySignal.class, source, peer, new arraysignalhandler()); - clientconn.addSigHandler(TestSignalInterface.TestObjectSignal.class, new objectsignalhandler()); - clientconn.addSigHandler(TestSignalInterface.TestPathSignal.class, new pathsignalhandler()); - badarraysignalhandler bash = new badarraysignalhandler(); - clientconn.addSigHandler(TestSignalInterface.TestSignal.class, bash); - clientconn.removeSigHandler(TestSignalInterface.TestSignal.class, bash); - System.out.println("done"); - } catch (MatchRuleInvalid MRI) { - test.fail("Failed to add handlers: "+MRI.getMessage()); - } catch (DBusException DBe) { - test.fail("Failed to add handlers: "+DBe.getMessage()); - } - - System.out.println("Listening for Method Calls"); - testclass tclass = new testclass(serverconn); - testclass tclass2 = new testclass(serverconn); - /** This exports an instance of the test class as the object /Test. */ - serverconn.exportObject("/Test", tclass); - serverconn.exportObject("/BadTest", tclass); - serverconn.exportObject("/BadTest2", tclass2); - serverconn.addFallback("/FallbackTest", tclass); - - // explicitly unexport object - serverconn.unExportObject("/BadTest"); - // implicitly unexport object - tclass2 = null; - System.gc(); - System.runFinalization(); - System.gc(); - System.runFinalization(); - System.gc(); - System.runFinalization(); - - System.out.println("Sending Signal"); - /** This creates an instance of the Test Signal, with the given object path, signal name and parameters, and broadcasts in on the Bus. */ - serverconn.sendSignal(new TestSignalInterface.TestSignal("/foo/bar/Wibble", "Bar", new UInt32(42))); - serverconn.sendSignal(new TestSignalInterface.EmptySignal("/foo/bar/Wibble")); - serverconn.sendSignal(new TestSignalInterface2.TestRenamedSignal("/foo/bar/Wibble", "Bar", new UInt32(42))); - - System.out.println("These things are on the bus:"); - String[] names = dbus.ListNames(); - for (String name: names) - System.out.println("\t"+name); - - System.out.println("Getting our introspection data"); - /** This gets a remote object matching our bus name and exported object path. */ - Introspectable intro = clientconn.getRemoteObject("foo.bar.Test", "/", Introspectable.class); - /** Get introspection data */ - String data;/* = intro.Introspect(); - if (null == data || !data.startsWith(" peers = serverconn.new PeerSet(); - peers.add("org.freedesktop.DBus"); - clientconn.requestBusName("test.testclient"); - peers.add("test.testclient"); - clientconn.releaseBusName("test.testclient"); - - System.out.println("Pinging ourselves"); - /** Call ping. */ - for (int i = 0; i < 10; i++) { - long then = System.currentTimeMillis(); - peer.Ping(); - long now = System.currentTimeMillis(); - System.out.println("Ping returned in "+(now-then)+"ms."); - } - - System.out.println("Calling Method0/1"); - /** This gets a remote object matching our bus name and exported object path. */ - TestRemoteInterface tri = (TestRemoteInterface) clientconn.getPeerRemoteObject("foo.bar.Test", "/Test"); - System.out.println("Got Remote Object: "+tri); - /** Call the remote object and get a response. */ - String rname = tri.getName(); - System.out.println("Got Remote Name: "+rname); - - Path path = new Path("/nonexistantwooooooo"); - Path p = tri.pathrv(path); - System.out.println(path.toString()+" => "+p.toString()); - if (!path.equals(p)) fail("pathrv incorrect"); - List paths = new Vector(); - paths.add(path); - List ps = tri.pathlistrv(paths); - System.out.println(paths.toString()+" => "+ps.toString()); - if (!paths.equals(ps)) fail("pathlistrv incorrect"); - Map pathm = new HashMap(); - pathm.put(path, path); - Map pm = tri.pathmaprv(pathm); - System.out.println(pathm.toString()+" => "+pm.toString()); - System.out.println(pm.containsKey(path)+" "+pm.get(path)+" "+path.equals(pm.get(path))); - System.out.println(pm.containsKey(p)+" "+pm.get(p)+" "+p.equals(pm.get(p))); - for (Path q: pm.keySet()) { - System.out.println(q); - System.out.println(pm.get(q)); - } - if (!pm.containsKey(path) || !path.equals(pm.get(path))) fail("pathmaprv incorrect"); - - serverconn.sendSignal(new TestSignalInterface.TestPathSignal("/Test", path, paths, pathm)); - - Collator col = Collator.getInstance(); - col.setDecomposition(Collator.FULL_DECOMPOSITION); - col.setStrength(Collator.PRIMARY); - if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!", rname)) - fail("getName return value incorrect"); - System.out.println("sending it to sleep"); - tri.waitawhile(); - System.out.println("testing floats"); - if (17.093f != tri.testfloat(new float[] { 17.093f, -23f, 0.0f, 31.42f })) - fail("testfloat returned the wrong thing"); - System.out.println("Structs of Structs"); - List> lli = new Vector>(); - List li = new Vector(); - li.add(1); - li.add(2); - li.add(3); - lli.add(li); - lli.add(li); - lli.add(li); - TestStruct3 ts3 = new TestStruct3(new TestStruct2(new Vector(), new Variant(0)), lli); - int[][] out = tri.teststructstruct(ts3); - if (out.length != 3) fail("teststructstruct returned the wrong thing: "+Arrays.deepToString(out)); - for (int[] o: out) - if (o.length != 3 - ||o[0] != 1 - ||o[1] != 2 - ||o[2] != 3) fail("teststructstruct returned the wrong thing: "+Arrays.deepToString(out)); - - System.out.println("frobnicating"); - List ls = new Vector(); - ls.add(2L); - ls.add(5L); - ls.add(71L); - Map mus = new HashMap(); - mus.put(new UInt16(4), (short) 5); - mus.put(new UInt16(5), (short) 6); - mus.put(new UInt16(6), (short) 7); - Map> msmus = new HashMap>(); - msmus.put("stuff", mus); - int rint = tri.frobnicate(ls, msmus, 13); - if (-5 != rint) - fail("frobnicate return value incorrect"); - - System.out.println("Doing stuff asynchronously with callback"); - clientconn.callWithCallback(tri, "getName", new callbackhandler()); - System.out.println("Doing stuff asynchronously with callback, which throws an error"); - clientconn.callWithCallback(tri, "getNameAndThrow", new callbackhandler()); - - /** call something that throws */ - try { - System.out.println("Throwing stuff"); - tri.throwme(); - test.fail("Method Execution should have failed"); - } catch (TestException Te) { - System.out.println("Remote Method Failed with: "+Te.getClass().getName()+" "+Te.getMessage()); - if (!Te.getMessage().equals("test")) - test.fail("Error message was not correct"); - } - - /* Test type signatures */ - Vector ts = new Vector(); - Marshalling.getJavaType("ya{si}", ts, -1); - tri.sig(ts.toArray(new Type[0])); - - tri.newpathtest(new Path("/new/path/test")); - - /** Try and call an invalid remote object */ - try { - System.out.println("Calling Method2"); - tri = clientconn.getRemoteObject("foo.bar.NotATest", "/Moofle", TestRemoteInterface.class); - System.out.println("Got Remote Name: "+tri.getName()); - test.fail("Method Execution should have failed"); - } catch (ServiceUnknown SU) { - System.out.println("Remote Method Failed with: "+SU.getClass().getName()+" "+SU.getMessage()); - } - - /** Try and call an invalid remote object */ - try { - System.out.println("Calling Method3"); - tri = clientconn.getRemoteObject("foo.bar.Test", "/Moofle", TestRemoteInterface.class); - System.out.println("Got Remote Name: "+tri.getName()); - test.fail("Method Execution should have failed"); - } catch (UnknownObject UO) { - System.out.println("Remote Method Failed with: "+UO.getClass().getName()+" "+UO.getMessage()); - } - - /** Try and call an explicitly unexported object */ - try { - System.out.println("Calling Method4"); - tri = clientconn.getRemoteObject("foo.bar.Test", "/BadTest", TestRemoteInterface.class); - System.out.println("Got Remote Name: "+tri.getName()); - test.fail("Method Execution should have failed"); - } catch (UnknownObject UO) { - System.out.println("Remote Method Failed with: "+UO.getClass().getName()+" "+UO.getMessage()); - } - - /** Try and call an implicitly unexported object */ - try { - System.out.println("Calling Method5"); - tri = clientconn.getRemoteObject("foo.bar.Test", "/BadTest2", TestRemoteInterface.class); - System.out.println("Got Remote Name: "+tri.getName()); - test.fail("Method Execution should have failed"); - } catch (UnknownObject UO) { - System.out.println("Remote Method Failed with: "+UO.getClass().getName()+" "+UO.getMessage()); - } - - System.out.println("Calling Method6"); - tri = clientconn.getRemoteObject("foo.bar.Test", "/FallbackTest/0/1", TestRemoteInterface.class); - intro = clientconn.getRemoteObject("foo.bar.Test", "/FallbackTest/0/4", Introspectable.class); - System.out.println("Got Fallback Name: "+tri.getName()); - System.out.println("Fallback Introspection Data: \n"+intro.Introspect()); - - System.out.println("Testing Properties returning Paths"); - Properties prop = clientconn.getRemoteObject("foo.bar.Test", "/Test", Properties.class); - Path prv = (Path) prop.Get("foo.bar", "foo"); - System.out.println("Got path "+prv); - System.out.println("Calling Method7--9"); - /** This gets a remote object matching our bus name and exported object path. */ - TestRemoteInterface2 tri2 = clientconn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface2.class); - System.out.print("Calling the other introspect method: "); - String intro2 = tri2.Introspect(); - System.out.println(intro2); - if (0 != col.compare("Not XML", intro2)) - fail("Introspect return value incorrect"); - - /** Call the remote object and get a response. */ - TestTuple,Boolean> rv = tri2.show(234); - System.out.println("Show returned: "+rv); - if (!serverconn.getUniqueName().equals(rv.a) || - 1 != rv.b.size() || - 1953 != rv.b.get(0) || - true != rv.c.booleanValue()) - fail("show return value incorrect ("+rv.a+","+rv.b+","+rv.c+")"); - - System.out.println("Doing stuff asynchronously"); - DBusAsyncReply stuffreply = (DBusAsyncReply) clientconn.callMethodAsync(tri2, "dostuff", new TestStruct("bar", new UInt32(52), new Variant(new Boolean(true)))); - - System.out.println("Checking bools"); - if (tri2.check()) fail("bools are broken"); - - List l = new Vector(); - l.add("hi"); - l.add("hello"); - l.add("hej"); - l.add("hey"); - l.add("aloha"); - System.out.println("Sampling Arrays:"); - List is = tri2.sampleArray(l, new Integer[] { 1, 5, 7, 9 }, new long[] { 2, 6, 8, 12 }); - System.out.println("sampleArray returned an array:"); - for (Integer i: is) - System.out.println("--"+i); - if (is.size() != 5 || - is.get(0).intValue() != -1 || - is.get(1).intValue() != -5 || - is.get(2).intValue() != -7 || - is.get(3).intValue() != -12 || - is.get(4).intValue() != -18) - fail("sampleArray return value incorrect"); - - System.out.println("Get This"); - if (!tclass.equals(tri2.getThis(tri2))) - fail("Didn't get the correct this"); - - Boolean b = stuffreply.getReply(); - System.out.println("Do stuff replied "+b); - if (true != b.booleanValue()) - fail("dostuff return value incorrect"); - - System.out.print("Sending Array Signal..."); - /** This creates an instance of the Test Signal, with the given object path, signal name and parameters, and broadcasts in on the Bus. */ - List tsl = new Vector(); - tsl.add(new TestStruct2(l, new Variant(new UInt64(567)))); - Map tsm = new HashMap(); - tsm.put(new UInt32(1), new TestStruct2(l, new Variant(new UInt64(678)))); - tsm.put(new UInt32(42), new TestStruct2(l, new Variant(new UInt64(789)))); - serverconn.sendSignal(new TestSignalInterface.TestArraySignal("/Test", tsl, tsm)); - - System.out.println("done"); - - System.out.print("testing custom serialization..."); - Vector v = new Vector(); - v.add(1); - v.add(2); - v.add(3); - TestSerializable s = new TestSerializable(1, "woo", v); - s = tri2.testSerializable((byte) 12, s, 13); - System.out.print("returned: "+s); - if (s.getInt() != 1 || - ! s.getString().equals("woo") || - s.getVector().size() != 3 || - s.getVector().get(0) != 1 || - s.getVector().get(1) != 2 || - s.getVector().get(2) != 3) - fail("Didn't get back the same TestSerializable"); - - System.out.println("done"); - - System.out.print("testing complex variants..."); - Map m = new HashMap(); - m.put("cow", "moo"); - tri2.complexv(new Variant(m, "a{ss}")); - System.out.println("done"); - - System.out.print("testing recursion..."); - - if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!",tri2.recursionTest())) fail("recursion test failed"); - - System.out.println("done"); - - System.out.print("testing method overloading..."); - tri = clientconn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface.class); - if (1 != tri2.overload("foo")) test.fail("wrong overloaded method called"); - if (2 != tri2.overload((byte) 0)) test.fail("wrong overloaded method called"); - if (3 != tri2.overload()) test.fail("wrong overloaded method called"); - if (4 != tri.overload()) test.fail("wrong overloaded method called"); - System.out.println("done"); - - System.out.print("reg13291..."); - byte[] as = new byte[10]; - for (int i = 0; i < 10; i++) - as[i] = (byte) (100-i); - tri.reg13291(as, as); - System.out.println("done"); - - System.out.print("Testing nested lists..."); - lli = new Vector>(); - li = new Vector(); - li.add(1); - lli.add(li); - List> reti = tri2.checklist(lli); - if (reti.size() != 1 || - reti.get(0).size() != 1 || - reti.get(0).get(0) != 1) - test.fail("Failed to check nested lists"); - System.out.println("done"); - - System.out.print("Testing dynamic object creation..."); - TestNewInterface tni = tri2.getNew(); - System.out.print(tni.getName()+" "); - System.out.println("done"); - - /* send an object in a signal */ - serverconn.sendSignal(new TestSignalInterface.TestObjectSignal("/foo/bar/Wibble", tclass)); - - /** Pause while we wait for the DBus messages to go back and forth. */ - Thread.sleep(1000); - - // check that bus name set has been trimmed - if (peers.size() != 1) fail("peers hasn't been trimmed"); - if (!peers.contains("org.freedesktop.DBus")) fail ("peers contains the wrong name"); - - System.out.println("Checking for outstanding errors"); - DBusExecutionException DBEe = serverconn.getError(); - if (null != DBEe) throw DBEe; - DBEe = clientconn.getError(); - if (null != DBEe) throw DBEe; - - System.out.println("Disconnecting"); - /** Disconnect from the bus. */ - clientconn.disconnect(); - serverconn.disconnect(); - - System.out.println("Trying to do things after disconnection"); - - /** Remove sig handler */ - clientconn.removeSigHandler(TestSignalInterface.TestSignal.class, sigh); - - /** Call a method when disconnected */ - try { - System.out.println("getName() suceeded and returned: "+tri.getName()); - fail("Should not succeed when disconnected"); - } catch (NotConnected NC) { - System.out.println("getName() failed with exception "+NC); - } - clientconn = null; - serverconn = null; - - if (!done1) fail("Signal handler 1 failed to be run"); - if (!done2) fail("Signal handler 2 failed to be run"); - if (!done3) fail("Signal handler 3 failed to be run"); - if (!done4) fail("Callback handler failed to be run"); - if (!done5) fail("Signal handler R failed to be run"); - if (!done6) fail("Disconnect handler failed to be run"); - if (!done7) fail("Signal handler E failed to be run"); - if (!done8) fail("Error callback handler failed to be run"); - - } catch (Exception e) { - e.printStackTrace(); - fail("Unexpected Exception Occurred: "+e); - }} -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/test_low_level.java b/app/src/main/java/org/freedesktop/dbus/test/test_low_level.java deleted file mode 100644 index e65956d8..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/test_low_level.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; -import cx.ath.matthew.debug.Debug; -import org.freedesktop.dbus.BusAddress; -import org.freedesktop.dbus.DBusSignal; -import org.freedesktop.dbus.Message; -import org.freedesktop.dbus.MethodCall; -import org.freedesktop.dbus.Transport; - -public class test_low_level -{ - public static void main(String[] args) throws Exception - { - Debug.setHexDump(true); - String addr = System.getenv("DBUS_SESSION_BUS_ADDRESS"); - Debug.print(addr); - BusAddress address = new BusAddress(addr); - Debug.print(address); - - Transport conn = new Transport(address); - - Message m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello", (byte) 0, null); - conn.mout.writeMessage(m); - m = conn.min.readMessage(); - Debug.print(m.getClass()); - Debug.print(m); - m = conn.min.readMessage(); - Debug.print(m.getClass()); - Debug.print(m); - m = conn.min.readMessage(); - Debug.print(""+m); - m = new MethodCall("org.freedesktop.DBus", "/", null, "Hello", (byte) 0, null); - conn.mout.writeMessage(m); - m = conn.min.readMessage(); - Debug.print(m); - - m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "RequestName", (byte) 0, "su", "org.testname", 0); - conn.mout.writeMessage(m); - m = conn.min.readMessage(); - Debug.print(m); - m = new DBusSignal(null, "/foo", "org.foo", "Foo", null); - conn.mout.writeMessage(m); - m = conn.min.readMessage(); - Debug.print(m); - conn.disconnect(); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/test_p2p_client.java b/app/src/main/java/org/freedesktop/dbus/test/test_p2p_client.java deleted file mode 100644 index aafbca4f..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/test_p2p_client.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.InputStreamReader; - -import org.freedesktop.DBus; -import org.freedesktop.dbus.DirectConnection; - -public class test_p2p_client -{ - public static void main(String[] args) throws Exception - { - BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream("address"))); - String address = r.readLine(); - DirectConnection dc = new DirectConnection(address); - System.out.println("Connected"); - TestRemoteInterface tri = (TestRemoteInterface) dc.getRemoteObject("/Test"); - System.out.println(tri.getName()); - System.out.println(tri.testfloat(new float[] { 17.093f, -23f, 0.0f, 31.42f })); - - try { - tri.throwme(); - } catch (TestException Te) { - System.out.println("Caught TestException"); - } - ((DBus.Peer) tri).Ping(); - System.out.println(((DBus.Introspectable) tri).Introspect()); - dc.disconnect(); - System.out.println("Disconnected"); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/test_p2p_server.java b/app/src/main/java/org/freedesktop/dbus/test/test_p2p_server.java deleted file mode 100644 index 68406941..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/test_p2p_server.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import java.lang.reflect.Type; -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import org.freedesktop.dbus.DirectConnection; -import org.freedesktop.dbus.Path; -import org.freedesktop.dbus.UInt16; - -public class test_p2p_server implements TestRemoteInterface -{ - public int[][] teststructstruct(TestStruct3 in) - { - List> lli = in.b; - int[][] out = new int[lli.size()][]; - for (int j = 0; j < out.length; j++) { - out[j] = new int[lli.get(j).size()]; - for (int k = 0; k < out[j].length; k++) - out[j][k] = lli.get(j).get(k); - } - return out; - } - public String getNameAndThrow() - { - return getName(); - } - public String getName() - { - System.out.println("getName called"); - return "Peer2Peer Server"; - } - public int frobnicate(List n, Map> m, T v) - { - return 3; - } - public void throwme() throws TestException - { - System.out.println("throwme called"); - throw new TestException("BOO"); - } - public void waitawhile() - { - return; - } - public int overload() - { - return 1; - } - public void sig(Type[] s) - { - } - public void newpathtest(Path p) - { - } - public void reg13291(byte[] as, byte[] bs) - { - } - public Path pathrv(Path a) { return a; } - public List pathlistrv(List a) { return a; } - public Map pathmaprv(Map a) { return a; } - public boolean isRemote() { return false; } - public float testfloat(float[] f) - { - System.out.println("got float: "+Arrays.toString(f)); - return f[0]; - } - - public static void main(String[] args) throws Exception - { - String address = DirectConnection.createDynamicSession(); - //String address = "tcp:host=localhost,port=12344,guid="+Transport.genGUID(); - PrintWriter w = new PrintWriter(new FileOutputStream("address")); - w.println(address); - w.flush(); - w.close(); - DirectConnection dc = new DirectConnection(address+",listen=true"); - System.out.println("Connected"); - dc.exportObject("/Test", new test_p2p_server()); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/two_part_test_client.java b/app/src/main/java/org/freedesktop/dbus/test/two_part_test_client.java deleted file mode 100644 index f37e955b..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/two_part_test_client.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.dbus.DBusConnection; - -public class two_part_test_client -{ - public static class two_part_test_object implements TwoPartObject - { - public boolean isRemote() { return false; } - public String getName() - { - System.out.println("client name"); - return toString(); - } - } - public static void main(String[] args) throws Exception - { - System.out.println("get conn"); - DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); - System.out.println("get remote"); - TwoPartInterface remote = conn.getRemoteObject("org.freedesktop.dbus.test.two_part_server", "/", TwoPartInterface.class); - System.out.println("get object"); - TwoPartObject o = remote.getNew(); - System.out.println("get name"); - System.out.println(o.getName()); - two_part_test_object tpto = new two_part_test_object(); - conn.exportObject("/TestObject", tpto); - conn.sendSignal(new TwoPartInterface.TwoPartSignal("/FromObject", tpto)); - try { Thread.sleep(1000); } catch (InterruptedException Ie) {} - conn.disconnect(); - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/test/two_part_test_server.java b/app/src/main/java/org/freedesktop/dbus/test/two_part_test_server.java deleted file mode 100644 index 7452303b..00000000 --- a/app/src/main/java/org/freedesktop/dbus/test/two_part_test_server.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.test; - -import org.freedesktop.dbus.DBusConnection; -import org.freedesktop.dbus.DBusSigHandler; - -public class two_part_test_server implements TwoPartInterface, DBusSigHandler -{ - public class two_part_test_object implements TwoPartObject - { - public boolean isRemote() { return false; } - public String getName() - { - System.out.println("give name"); - return toString(); - } - } - private DBusConnection conn; - public two_part_test_server(DBusConnection conn) - { - this.conn = conn; - } - public boolean isRemote() { return false; } - public TwoPartObject getNew() - { - TwoPartObject o = new two_part_test_object(); - System.out.println("export new"); - try { conn.exportObject("/12345", o); } catch (Exception e) {} - System.out.println("give new"); - return o; - } - public void handle(TwoPartInterface.TwoPartSignal s) - { - System.out.println("Got: "+s.o); - } - public static void main(String[] args) throws Exception - { - DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); - conn.requestBusName("org.freedesktop.dbus.test.two_part_server"); - two_part_test_server server = new two_part_test_server(conn); - conn.exportObject("/", server); - conn.addSigHandler(TwoPartInterface.TwoPartSignal.class, server); - while (true) try { Thread.sleep(10000); } catch (InterruptedException Ie) {} - } -} - diff --git a/app/src/main/java/org/freedesktop/dbus/types/DBusListType.java b/app/src/main/java/org/freedesktop/dbus/types/DBusListType.java deleted file mode 100644 index f7711dc8..00000000 --- a/app/src/main/java/org/freedesktop/dbus/types/DBusListType.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.types; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.List; - -/** - * The type of a list. - * Should be used whenever you need a Type variable for a list. - */ -public class DBusListType implements ParameterizedType -{ - private Type v; - /** - * Create a List type. - * @param v Type of the list contents. - */ - public DBusListType(Type v) - { - this.v = v; - } - public Type[] getActualTypeArguments() - { - return new Type[] { v }; - } - public Type getRawType() - { - return List.class; - } - public Type getOwnerType() - { - return null; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/types/DBusMapType.java b/app/src/main/java/org/freedesktop/dbus/types/DBusMapType.java deleted file mode 100644 index 825df7db..00000000 --- a/app/src/main/java/org/freedesktop/dbus/types/DBusMapType.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.types; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Map; - -/** - * The type of a map. - * Should be used whenever you need a Type variable for a map. - */ -public class DBusMapType implements ParameterizedType -{ - private Type k; - private Type v; - /** - * Create a map type. - * @param k The type of the keys. - * @param v The type of the values. - */ - public DBusMapType(Type k, Type v) - { - this.k = k; - this.v = v; - } - public Type[] getActualTypeArguments() - { - return new Type[] { k, v }; - } - public Type getRawType() - { - return Map.class; - } - public Type getOwnerType() - { - return null; - } -} diff --git a/app/src/main/java/org/freedesktop/dbus/types/DBusStructType.java b/app/src/main/java/org/freedesktop/dbus/types/DBusStructType.java deleted file mode 100644 index 86d7533f..00000000 --- a/app/src/main/java/org/freedesktop/dbus/types/DBusStructType.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - D-Bus Java Implementation - Copyright (c) 2005-2006 Matthew Johnson - - This program is free software; you can redistribute it and/or modify it - under the terms of either the GNU Lesser General Public License Version 2 or the - Academic Free Licence Version 2.1. - - Full licence texts are included in the COPYING file with this program. -*/ -package org.freedesktop.dbus.types; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import org.freedesktop.dbus.Struct; - -/** - * The type of a struct. - * Should be used whenever you need a Type variable for a struct. - */ -public class DBusStructType implements ParameterizedType -{ - private Type[] contents; - /** - * Create a struct type. - * @param contents The types contained in this struct. - */ - public DBusStructType(Type... contents) - { - this.contents = contents; - } - public Type[] getActualTypeArguments() - { - return contents; - } - public Type getRawType() - { - return Struct.class; - } - public Type getOwnerType() - { - return null; - } -} diff --git a/app/src/main/java/org/mpris/MediaPlayer2.java b/app/src/main/java/org/mpris/MediaPlayer2.java new file mode 100644 index 00000000..3892d9e0 --- /dev/null +++ b/app/src/main/java/org/mpris/MediaPlayer2.java @@ -0,0 +1,60 @@ +package org.mpris; + +import java.util.List; +import org.freedesktop.dbus.TypeRef; +import org.freedesktop.dbus.annotations.DBusBoundProperty; +import org.freedesktop.dbus.annotations.DBusProperty; +import org.freedesktop.dbus.annotations.DBusProperty.Access; +import org.freedesktop.dbus.interfaces.DBusInterface; + +/** + * Auto-generated class. + */ +public interface MediaPlayer2 extends DBusInterface { + + @DBusBoundProperty(name = "CanQuit", access = Access.READ) + public boolean canQuit(); + + @DBusBoundProperty(name = "Fullscreen", access = Access.READ) + public boolean isFullscreen(); + @DBusBoundProperty(name = "Fullscreen", access = Access.WRITE) + public void setFullscreen(boolean _property); + + @DBusBoundProperty(name = "CanSetFullscreen", access = Access.READ) + public boolean canSetFullscreen(); + + @DBusBoundProperty(name = "CanRaise", access = Access.READ) + public boolean canRaise(); + + @DBusBoundProperty(name = "HasTrackList", access = Access.READ) + public boolean hasTrackList(); + + @DBusBoundProperty(name = "Identity", access = Access.READ) + public String getIdentity(); + + @DBusBoundProperty(name = "SupportedUriSchemes", access = Access.READ) + public List getSupportedUriSchemes(); + + @DBusBoundProperty(name = "SupportedMimeTypes", access = Access.READ) + public List getSupportedMimeTypes(); + + + + public void Raise(); + public void Quit(); + + + public static interface PropertySupportedUriSchemesType extends TypeRef> { + + + + + } + + public static interface PropertySupportedMimeTypesType extends TypeRef> { + + + + + } +} diff --git a/app/src/main/java/org/mpris/mediaplayer2/Player.java b/app/src/main/java/org/mpris/mediaplayer2/Player.java new file mode 100644 index 00000000..d9f0fbda --- /dev/null +++ b/app/src/main/java/org/mpris/mediaplayer2/Player.java @@ -0,0 +1,112 @@ +package org.mpris.mediaplayer2; + +import java.util.Map; +import org.freedesktop.dbus.DBusPath; +import org.freedesktop.dbus.TypeRef; +import org.freedesktop.dbus.annotations.DBusBoundProperty; +import org.freedesktop.dbus.annotations.DBusInterfaceName; +import org.freedesktop.dbus.annotations.DBusProperty; +import org.freedesktop.dbus.annotations.DBusProperty.Access; +import org.freedesktop.dbus.annotations.PropertiesEmitsChangedSignal; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.interfaces.DBusInterface; +import org.freedesktop.dbus.interfaces.Properties; +import org.freedesktop.dbus.messages.DBusSignal; +import org.freedesktop.dbus.types.Variant; + +/** + * Auto-generated class. + */ +@DBusInterfaceName("org.mpris.MediaPlayer2.Player") +public interface Player extends DBusInterface { + + @DBusBoundProperty(name = "PlaybackStatus", access = Access.READ) + public String getPlaybackStatus(); + + @DBusBoundProperty(name = "LoopStatus", access = Access.READ) + public String getLoopStatus(); + @DBusBoundProperty(name = "LoopStatus", access = Access.WRITE) + public void setLoopStatus(String _property); + + @DBusBoundProperty(name = "Rate", access = Access.READ) + public double getRate(); + @DBusBoundProperty(name = "Rate", access = Access.WRITE) + public void setRate(double _property); + + @DBusBoundProperty(name = "Shuffle", access = Access.READ) + public boolean isShuffle(); + @DBusBoundProperty(name = "Shuffle", access = Access.WRITE) + public void setShuffle(boolean _property); + + @DBusBoundProperty(name = "Metadata", access = Access.READ) + public Map getMetadata(); + + @DBusBoundProperty(name = "Volume", access = Access.READ) + public double getVolume(); + @DBusBoundProperty(name = "Volume", access = Access.WRITE) + public void setVolume(double _property); + + @DBusBoundProperty(name = "Position", access = Access.READ) + public long getPosition(); + + @DBusBoundProperty(name = "MinimumRate", access = Access.READ) + public double getMinimumRate(); + + @DBusBoundProperty(name = "MaximumRate", access = Access.READ) + public double getMaximumRate(); + + @DBusBoundProperty(name = "CanGoNext", access = Access.READ) + public boolean canGoNext(); + + @DBusBoundProperty(name = "CanGoPrevious", access = Access.READ) + public boolean canGoPrevious(); + + @DBusBoundProperty(name = "CanPlay", access = Access.READ) + public boolean canPlay(); + + @DBusBoundProperty(name = "CanPause", access = Access.READ) + public boolean canPause(); + + @DBusBoundProperty(name = "CanSeek", access = Access.READ) + public boolean canSeek(); + + @DBusBoundProperty(name = "CanControl", access = Access.READ) + public boolean canControl(); + + + + public void Next(); + public void Previous(); + public void Pause(); + public void PlayPause(); + public void Stop(); + public void Play(); + public void Seek(long Offset); + public void SetPosition(DBusPath TrackId, long Position); + public void OpenUri(String Uri); + + + public static class Seeked extends DBusSignal { + + private final long Position; + + public Seeked(String _path, long _Position) throws DBusException { + super(_path, _Position); + this.Position = _Position; + } + + + public long getPosition() { + return Position; + } + + + } + + public static interface PropertyMetadataType extends TypeRef> { + + + + + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 738d9f60..cddcb052 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,8 +5,8 @@ buildscript { google() } dependencies { - classpath("com.android.tools.build:gradle:7.4.2") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21") + classpath("com.android.tools.build:gradle:8.1.4") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23") } } diff --git a/gradle.properties b/gradle.properties index 9a2223a5..a41171d2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,6 @@ +android.defaults.buildfeatures.buildconfig=true android.enableJetifier=true +android.nonFinalResIds=false +android.nonTransitiveRClass=false android.useAndroidX=true org.gradle.jvmargs=-Xmx4096m diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d6c35de1..8b886a63 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip From b77974241df8f79a1ee23a7e4ebbefca734035ab Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Fri, 3 May 2024 21:54:24 +0200 Subject: [PATCH 04/10] Fix some dbus-java-related issues --- app/build.gradle.kts | 6 +- ...bus-java-core-5.0.1-20240403.084925-19.jar | Bin 0 -> 323006 bytes ...transport-tcp-5.0.1-20240403.084925-19.jar | Bin 0 -> 7990 bytes .../org/asteroidos/sync/dbus/MediaService.kt | 271 +++++++++--------- .../sync/dbus/NotificationService.kt | 5 - .../asteroidos/sync/media/IMediaService.kt | 3 + .../java/org/mpris/mediaplayer2/Player.java | 2 +- 7 files changed, 145 insertions(+), 142 deletions(-) create mode 100644 app/libs/dbus-java-core-5.0.1-20240403.084925-19.jar create mode 100644 app/libs/dbus-java-transport-tcp-5.0.1-20240403.084925-19.jar diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a63f3c36..d3a5abdd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -70,6 +70,7 @@ android { repositories { mavenCentral() + maven("https://oss.sonatype.org/content/repositories/snapshots/") maven("https://maven.google.com") maven("https://jitpack.io") } @@ -87,11 +88,12 @@ dependencies { implementation("no.nordicsemi.android.support.v18:scanner:1.6.0") implementation("no.nordicsemi.android:ble:2.7.2") implementation("com.google.guava:guava:33.1.0-android") - implementation("com.github.hypfvieh:dbus-java-core:5.0.0") - implementation("com.github.hypfvieh:dbus-java-transport-tcp:5.0.0") implementation("androidx.media3:media3-session:1.3.1") implementation("androidx.media3:media3-common:1.3.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1-Beta") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.8.1-Beta") + api(fileTree("src/main/libs") { include("*.jar") }) + compileOnly("org.slf4j:slf4j-api:2.0.7") + implementation("uk.uuid.slf4j:slf4j-android:2.0.7-0") } diff --git a/app/libs/dbus-java-core-5.0.1-20240403.084925-19.jar b/app/libs/dbus-java-core-5.0.1-20240403.084925-19.jar new file mode 100644 index 0000000000000000000000000000000000000000..32192355bda569ae232a17ef2b64d0a919102704 GIT binary patch literal 323006 zcmbrlW00oJvM$_qPj^q-wr$(CZQIkfr)}G|ZQHgrtuybpR;>8qoISI5ob$x}lmImd)Z(LU6L-o2w2KIJR1ZM74)oQmbnXEQk$$4qq*w z1kJRU;R2IYu9$%N14KDVaEM0^QJ0C|a|HtYmp1-u#ew{$uyrt{{YPK_H3j^?DNG!U zjg5>QEuC!b{z8rXUr-wvI6M9Yg8wIozKxBolfIL=thbksNfEB23m^dIbRCvtSsxB08i|F|IR9Bl239h}@9j7|Px z{68*WM?3Ss!2ZbfAK1U;{|lV(PX_x3{vTKS%K(^v8odOi#XUC&=^|jJ33aXLOLleqJM5p1SU)2fRGTu_c8eU8Ii!- z3nLofQ$=U;NeIy83=JO`Gti}?ngSZ2vNl9CIor-QX%zS`t^G(O%9GbzwUZA@s;^89+U-R|D-%=S5-g#@6ym1V`U8?z%P>qUZ`cP(jO zC@R5#X0_hv5`-oumNE|;AQ)AFoca}f#qcvvDp-@B9SQRO(XFMFx60gG!@yJl*WzqF zOM?|E&jDymbHrSj}VtzBF|cxEO+LL^W6@=UHCb6yVL zu+1O+MyzB^Vg14Er-{&mn!i=)O3oJQX-vs_KE^AN+c`85Gtxv*FC(%Nq@}6GuOM>t zb`XGiqzRFzz`2?vtB5klva*V1B6Kyhh=w{8N8<@aI$UToX+Uj%EOE3hOf~C7`|!Y^ zRcSzv?<)D_d%FucD3Vp1h;zY#hJ|WG{Hr;nofZ@2P(G|3C5Q;>QdDP-7bj9fgtc~# zg3WY=I{X1O9&I%mxL9+BKi8PK3KIKcM9?LXsRR=OOWi|fSgw+5*i@^~&yRxl3KghG z?Sk#Ma@~yR;v+BSI>asP_hB-~_o+Zl_pu=B>niLkbq6X&`|=`x`D;*RV3FOBR5IfU zWtQ)7F-elen70Gh3MoVdH2_ggl7U!N{H%?~UssWWkDo?m>yYgGj=Aa5CsC{6KkVf0 zs)da)w}&3GOr{p$JV1v}SJ9kcwvX5E3<$(PqjX~fGOuhRG|&@T$|x)iLI^olQQmA% z$XY(g@=G}!7K#PR+~Fr>+^0VJH9iDr-k=RD#|-&gsic27&&-T=w2f7qeVjjzRU#d= zE?cMK+b@!^MU`I9T7OA9a%NIz%Av<5mGuh9r*WhbJx4oY0#!4SN{|7E?9c>~hc#j0 z1#`9DA9QZvF76UhBp?u+zdzy(4tI{@Je3H3P$*gl%H#PGsY(inaGdmPaI7XQrxSw#DZg_OYhGw4g&vLJDzF4^9v7dHN z;-P5+t>s8sQI??*WPp&zYppH{eMT43#l^K5r@l5?F>qrqvHo!TgjPg*QsP2y(;Kr$ z3;p`kT{5u5k8W*oXp(tSRy||~M&#RMxoy%>gKCrb+)a`Zq@8X0({&DBFu2L~3I6H! z(3W+v>H^}~P29*yJN4$h?3CKT_TJ5GaghSgdo1!6&5|kFyrV@5UIKjzvwA9n9hT29 zvlx}r>n3TUe33T$moeu7TWx`?rT5EG-Ytql+E6?}vd7r-Mck3j-8i^P2?PRzOI?B- z8toe`n9n0G75*PBth%Fi1f{SHKts1kb0NXda;Y7!pewdUUeT3Y>3)$yWXLi1Eqkw7 zpB;SCFgIQf`v4inY=LjaTsb@UZgPE*6$^`PIExEt_qFB4hScK1=6EvQAxK^bgjUpo z9l6=QMaAcG8rR0Y06*{f?q8P&(-O!^U z&kWsZKn=vHsmu`lpM#myRlF zDOrD3mK4G_py*o4BSbW+;EYtUbjl*CHi=2C$6{<$4Yn%@`jM7|ESNQ8uvQr^1c7067Ivm%eF=Dq!ZX0AZ?0pJ+qCWTpe@?r512>Mi?cKDw3gFTL=w2yq<_V)Mw~3yIVGYN~@myX3F4+j{qnH3BcO%2fJ3<^DFsd`irB@S}+|!1U8y z(&C2bV6C{Kl0mf^=P;SQ74gA^=15-PPMByk&#UTOVTSp_8X>V*QjnYz7>KN z8?x-ZBt7e{<_pU(gBSSr;WvCeKYqXi!q{Ot=fLcEe1mX)HG|U+oYXGcVI}kagK7P} zL1dX;qQ;ptaxJ*m=(@fSQTk&UDQ+;^7}H%c8E=1R$u)DKeKxOeTw!alb8~MMu*G4p zlHP`F-Y}F|LgVf<$E(E3#LQJ;A1UhlWfZr{iIKUDj<#V=H@wvgErI+r{WMEj@==WLvpmp`Ug$J~?C+eYmM?AVp(a*`Zs;l)KN{y29!q)|-I6+(FnD|5U?FG+ zV{Y&^D-PRFhuTmAB&kN|Eic;Fuq(W=69B}VuA0hJrc@jd#Kt}&bLgdG$bs9XK67j%U53e3R<1D24D3W7s7 ziGx>0+K9e#(T|}TotBFU@q_4&J^D;H8e3sB)sh|7VE@b{e^WLN_NKS!d}G`(?}=^t zhL}(>el2>9xH!esXCUt4*~2-JH|qRx^Y{@x(dgID$XB27cT|{2Y_Kc> zLCXlNU%=SE2tSQD4pRD86tb0$PfhfnuLJs|&bEdlvBj(1DvZ`b6po~fh!``ES%WPc z^~20yz<}KuJ&)2n%PP1<5ts*01~uQ!+Y$%E5ayAp#j8hWD8*2lsL}gvEM*&8Nki_o zD*UkwBE&l!4)Bf|JSB$hGLOMn>S8$X&kF33ntAk*Ca{TP`e*l%2sk{oKBwrt6n?@_ z%jo1@UqbbnyNRAAe6$!9TYwt)%4J-e2!alR( zCf@8BvL__o94Kf>jkh_z_$JYo0BMIxyg3QKB`E$}H;L6_>GtD0+@3R*!Pqu!qP=Px zysoK#M=);$QQTRD0Fv(NJ8N5HtwWzgI#dgHJqoo}ONq&q1^TiIs_~R=3cKn)#(4^k z5le)WF?cE+W$os%l@p7@UFwh(w+tgoR81p}2yu3zIrOO5xhlq}rt#{bNqhw2RNOo` z=;h-C?4fOB59aAxSi@>8aECn%xQB@Sc7H6RZldS_=w8n83|rUE0E(f@iX)Nf6Y(@5 zd%gU$+SG;obZy4!epXnMG$vN@kS2p^Hl-Mtwb` z#l%#!qM^$Z5_4L8f2!v!7!c(T7sf!w)GX*!g<|{4#CklyjWHuMz2VhCM=M&+3+jux z=Nd+?0iS!cBYL8H$Ty%TOjr-`aLURXQ$hm((EKi={(R{5$K+ek z*2YQS+{W19U&(i-YJ{hvDf*YU_*z0y@eCML;P^=kwK)d^xPzpAz+6?Lo&9Avk~+Jz z%>2*!jEjRn`OI&m;jYE$s4TROP%y$l+7i%J44E>=9dE-W(>S-!nOJiL(s9GZvW`>F zKG%=WUE6Oy$D3bT*EyE3Swfd6FiXNYV)<*`VxdJ>(`X<)Y35(Qzd@}V7^qWhS9X)yZ zVXJ5RbDu!S(6eMavga-#xrfA-Yzjl?@S_+51fUVhOom}Sed=#O(m%J$Nnt1F6t&Se z#p0S6u*jP&PW!3AXh2Y;$W80 z$p(!UWKEj}!)W)Q;)Fgd8ctrN-(kUN-{vL$)YI0DP4)7 zL5>-5RO?5BVh80$Hdb-6lEpVHaH9NS<93)TRxi7;;NC z-HuMri5CT5=g;e#kD-X9vTTC_HiwoM(@pg-Uu5K0Ywy6I005xh2E5C03eaICtDT zY*}!)#=jvP!N^9CU6mEuM`|3>3VK_9{k`keUZ7`w>3a`a9h4L@I~RV9!8hL!^AQpK zF_)6aw2O+>hMuHw2He>SosTg8Ea|gFsU#(~*+bTr8`1wlW^qj0t@QL5&K7WMqr39Kvp&?K zWZi+ZlrQ1o)qCn9^0ni4Bghx2cVx}>s@Mt^wu(0yd?jmiXPDEc6Md{~y(Sl9D`uQO zj(3eEPbUlN)3j^Q&a))8*N^Afs!O%)qx}hNpb<0X!J!NV+ZK!283|J864GX?)zMpB z!gxH1`i+{1uDGsCn@!|+7xN)2$QgCK@nY5C*UX$V7muocqLBpOPdR4w>bJFeeTGfT zF;Pn6=u$9BY5T)Ad%qO#?}P&LnDkFvXlJNnL-~2vEk9P5d_garo+&zhxqOMhn5e53kq+1qq)YzN z7~Xt~xilP^D)m(0E)YZ9P@DTx1k(EDNjb--!s}6|b-7AzmajQU8r{Q3yN^j0$>Ek= zJYt&kMCi%a8bx|borS@*S&%VuE3$GJvo@~eo#w~(zBZ_Dtl>jv_`NPw?)z6)P%A!* zgnJ|DI1?KxF?aP2Z3q7*vJuqh(D-|%x_Yay$#&;O8IFu0oNc64CHzZL{V$fUSM{D} zj?a5ff6n8VC7HGz#u*RTJmn6+!?OM_AYas;@onLo+%ScRTLM*!ILz%{SL`lP8_Vye zrObS5^_7TbDSV0q93LcHZ=LOVoL>0b4@nr;Noz-IM`_8kxOdFUJS~Ed5T z7KAavjwB=$Jsm6OG)1%7U;9b@p)|#fZ)tTy+Xo=WEjN*@UapB z<~QqGs&k^X2W~mIi2$>?H#tpnOD9sd{{`!W+Zqf`f;eV*M)fuEVwfiI27dmL zZoUB=6LOD*WRDE-CU3ueUyB@c*bbey@H{yqf5#p79N7sR9l=)7MyPt^+J>jONrX0M z$d&!qH;azseA`~%Z|8ZeyW6ycR*rdG&flP0J;KY2N$h{sCHoTNva*F(<2uIc;bZLp za)~+N7It@1mGkUvPSaKqMrTw1gsg?|72z!$*3^zUT%Wcdd~5;aDIB_8{j!Y*sjAxx z*WtbL=xnifZnrhvKT9MO*5dW{IvyJ@WYm^=z+;b@r;a+$jdvew6X_h-SKjLx9hv@G z#u9pW!?tZ{dTL`=)g4@2Izj3>8Qe|nLa=)ZMz?9B0;M3an}ie_B+AMs;jQ=HTx?mS zUH~h#htf?b{rZ&{Kc->o*ib=&spagmLB?F)7>%Fv36^C_?sLzak*E{KfE@MkjvBqj zN`Fc5Mr__a$oWF}dEiESWBQKlD|*uXPl^+NU~L@tyXhB!@V69){7>hLLITc?{EqH6 zh6=`ZR_^~kWK=nKL|jJx(k5~bQu8Mi)H3kj1ZX50Co{*-T_GEahLi~eT=xT@b|Tgt zuPfAcbv@)uXYnTS?#A6!L8WGEnQ^k?aRg=E0rW$p>P2`qYG}b+GLq-U zfD94Hp7E)yGW9g#iDV)4Gx6heEr`?w%S~(;_X+Ze>xRQrISsbvC~}5_dYfrQ{B=t+ zh|71<7h*f2#oz=Qt3<5zFrr^D7tFPAF%Vb^Fp^b2Bjz+8GaEMyYOfBnGAlfbJ2hR* zWvY}*i;6MM#9YAM!^1w@l<>}~G?fP%nUT`Ye}oO&$>EV`FXOL-35XuvnTS`tA`gGN zNa-S%bbe&_zU~IYWVlu4qN&MO`99(#QYJoKKQ=d>nebUx&t|M#uJKe8M);Bzp9mDg zf_^?$NKpy<%4|C(gn{0s;$Wq@<7O&tk>I~}GA}&snwnEO#CyaTArCAneQ*=VSEgyH zPBxx|O5(@tqzvf2IlyX1Rg!B*nJK|b2b6}!c(dRlZhIqB{y=1hMJnZmND3-tVIicu zbKtUulfe$WKcT{UH7(wKwFY@`8|ep$SWG|q{d!E?ZMk&i>De!GJDwx#vpE@7LJqA6BkLstKx zH!;eq%0|zX^@mTD8T%os>e$pH>q|51AK~ldRb-q8(tcXW)Ym%2i15sQyEN+znoAwF zi7M0TmbsdhLAW?>D2{@_`{p?%l*lJ_Kn$bGGl(?7xh4H1dTt7lU_; z?=-(;`fZmp{sQ;wf6k@6I4x)3GVHCjo=~=Ba!rh-2Y~(6?b@=#R{q#nOM78Q?O$fZ zS>xKmHg^e6p>vO{8$EbS>K8(ziJaXhC>ibPAa>)QrO%?peJ?)UqpQBBooc&tZRMyt zF?LF;chr%100I4AcX%Ua5rXlCS5t8ch5OB|Ai;uVp7NNg(;;{dgVYv=(yRLJ9QPo` zLE7|6z=$E8Zfm#-mMa4R^F+1UZ(;-tjBynfYb?@6??szIRPON2UH}jn>3To31J2WY z$_FdwZrCpL8VO9EP?SUt0P|Z;Df1YVw0CmR<;+jXE(oqUlNQtdWeqn!kfCrzAq6#; zBW@TQg9DZ%ptI4w5nsD^mR}+~L${8p7bIClhpsnRzA>9a10y6darf}qyU+B@k{*Py2C;^x&kss=Q=(a z2J8|z5hP$@+k`2?EMIWCs`m{;ZaZ-4IU`pny?()eAejTdo`ac8VYuxg`a>|k2D^Vj zCX(k?j+bOphoF!ZhB|VbA^xU5dQ4BEs*I)^PrdEm>%HG&xC{-ZAwkD zW}uE%t3HX4s$q8w{si1qcgsn%+s`Q_={L2X=g= zJjdH7sqZw+XEqv0F%4G((J{})!tHTvkuW5bJ|t8kzcW0c^uwKFAhJ|o3(B<+Tar4W4kJG z5n3Q)h1AIipu!OZNNG&uL80b-i0szI#xC!}`Q22Rr(RL|UAg7K^Mia~6bt}>fhK)k zJ8$qnJSf9HPuQ?XIN?N*IOX8dX&t2uur0HRZG{ByrzJT^uNxGLhk;wo>vpx8jy4RyNHWD|P&~)C!Vw>(VAgi} zXw5S#q7oTMB??VyRLz!@#P_}vhZ!F|r+D=*vh^!MUQ`Oq44?Uu=@Olzo%(?nOfew`%*o#TU?h+N1<_Js0eK4U<&p?|7n+B_{ z0C&t=UU)JVfuAN9c+$@#y!+?M`cn688Ba(Ou7mJK?IeH4%Q`_K1s(+V+gp{nHOXN^ zA=9})x&{fK_uDfncB7S(s;1`ac&!!8fJDOesL0>`$K@~36FyWXW^>$1pxlSvCh0@CKTuu-yQpQ%B5erZ5D?t6s} zPF@tZbCa{{qz=U|Xg(kvKD+_oJwU(MyfS6m?_ydyukxX`E~J*jEGXV7nYP`j?8jco zsoh_%kFEfuw_RYMkjUb|h+^~;>^AKVn};2Po#|ckl?^Pw)ecWmJK9X`t;XxF*K`H% z)Z^osdTdI#&7Eb0u+l*`dT=no6R`J9>Zd6#?GEPmV79*k8}wF_x<_J1P%#9?bbu8{ z=%gI@AN}^#yAD@iDvIzUJ}8P{v3g+oFW5}Llrx%JhUvU7HN0cll%6X5j&HaGJ;ooJ zk}^=>loymH7&_)SZ*wm}H>>u4bvT!fYb&0pf3}Ra_s!VDr^&SGGTEWg(wj*it1LUc zwh7|B)}DsdsUt_yGG4%;18<)x?J*;ZiuuZpDTG?vxQLx?xZPM(&ghAW=u0nBr&ohD zv$VG3WKJz*7Ok46R1tp`GLnyt#lG>Dq5p)9&^KY7%DnovR9lM&eNMUfDB@h(0i3jl z3u+QF0(TG+q1-~Tf|cuG7R6ya$(qgzsl-scwxPSbP&ohESMsrzw%8=iYiE8f)Ws)J z0(v6Ve*rSL2v0-nf{k5hj^t#q@8U#&z<*#KKJydyPGuC`J=*d3 zLf`&gO4*w}T;NdGx3Sph@z@~&t>7&Ub{$S}OL?LQqgg@P)hY=VZYv0k%3cVZhuc&7 z?C9r_v*UR~o?4aYR~cg&#BKA`R^!oPWZ`G-pV3NCDNk0&D zdL8vO5@&d1pRj0lyd>UG|5^W*_~UJ5zyJWWzYD)VhdhG6g}mSE|6kFrQSHqY$rRnE zt8=n0H4q*TA0EPbt|s{>Y^lLyuo?s&6Ld-Lu1QqFCcb*zs=AA*U&9jSLi?kQW`}aw zAT-6I0tDHTF83#5*8Z*S$t{*}>-M2{_OpBTbLQ&%M`rso;6<;l zz)eqiPjO$&5570{OMjGUi`Lbb+Eho6F(imc69;rhsUFN|sY-r$OQb-h20r8ydZ_x_ zdQffI^FjCdk>Pp9LIba_E`{6BBviSF4E2!{VLw>YDWY>}QltjRixMf`M9QQ)_k6vx zQ$zR8oK$o|*Bu3k>2ZFD%A4StZ`e>vgD1b;$-S~Fl!B--+E(}Yh7CIqrx)E`|$ z14HI2Ga%hNG*X3$Wn!*8cSnC~iCY5Y=$D>DVXx3rh1CID@W*c;gc)!o`o{d zW{a-~qeRH_$4SWLrAb&J#g8fx)B9OUnFgw@3BR(sV4aBXY3McAM$AEWNRN!$iwNXH zojpwh^{~Xoqd8Vs5V{AR$)5-MMpvcR9S+Q|# zpNm(z2N4ZrWxm2=V^kkGM@9!Z#vVYNoQ}Mro%wOgUP&kJeBSzVYa-vW59-R0f+t#b zMr|w$1r4g&iDa&VCuZ3RBv?oZ8$LzB9*9(Y-Jhj82DA02N!CaTl5~}s1-p7M@Z!j2 z05eTS>9?lr`MH%;sNr%Xt9*n#2-b7jIC8*j>Vw7#iw`q%eGyu$Ws|y$Q!xy3YR*Dr zhP&0&hg3&&|LrD+ONHHe8`JZO#`+Z6bquxHk(1l7f!opnw5V4_b!|$BhJwh9Qe%ff z$V{LoP2m9w+wW+8GSlPI*po{dq|(&PvbZSl!cV75b*JRingmTf51^iS4DXy1-FXKk zO$zEEjg0xDY4OeJS(u?P#*;=SEXApX{xpXu?lb#a>Z^=kX^kBwelE=GVuncGMQ(cd zwlo^FmGhCwR_Y&Sg4jeJ$E zmYXsQdQY~3x7K@l#fASBYwGZ!&rO9A#hCq9xHxaP?Hj@OfcKGOXY64gulVKSSEu-MoGroGb@o&DH=(>sm(Sm}`QOHG-fXmP z!_Kci$2|kyypxt?7yuq-u2p=hZ1+uF;PQ+l^HyZWwcp(tBG@gLb1={G7}!)1&vOE- zwoJ~k8^}hWYEP}Rkks7Z8@d)}zQHek%Y#L{1ehIC5l;8SYj|HVO(I%5_ex5Q*%TzL zZzMn6oG0|#?hdw@a?TEa+89bAwz}ULK#l>yY*z<&2^C)#9)(Hp*AAU=L$?a$MS(e> zMjyng;lJXD@ds|2*OqLz6t0@o^mF@+i1pXG#VwjVhy*9D((Vxke=q;GchSJzqxv+s*NkTJGmY3@caqghr#vmEXCU`uo^4QD~SyaY{nET6S z4EGM|RK(~8m9BY5*&1oMvb;z+g1duY@amFrr8#Rrt1WR(EMEq&3P z@d>Nh8ztMZZ*=U-rD8^zL|ZPgia^w*=s-**bQnl^HPOY@du#I%^r{ZQE6y?5evn`8 zDr+2{O?{EE9Dyr_b}?4|^4Z-h)v~F%+U`MFbxlnu_INkx8l`Nml^=nV@Q zj0$pxf`BC5O^e-N-YML)^W$ixjnC}M{&$qzf za!c;2&+GU;FEmdhOjnON`}wYRZji%VZqNy?t2K7>u-ySkJ!d!cIgb@FJkgX&53F*t zwuWYKqq2g-_cm+smKF<~SdXyVgXHT0#o;^S$|}ko0~P(3#kBin&R+;VR>DKLYA=wY z>1NNx?{CC9{nfXaQ`-$yz5|$An-hsPMMo&ZwhF4vK$dpkuD7O6UvU5AG{gvA(Sg77 z8TH?tS!{5Lg?lcAY{vz76`GaC&^H%-;V&uZ#d@;k?iky=F7^t8fSVp1EybduV7 zw&;YG#`+Z*4Nhv2q%1?`rUNktfiXEWy%Yu~vY$;%si08i0_Hyoh$Xe#aGH#k?$ z%2!a80wLJWy7pH6J*)hM2HkT4_{lw1#=Zme|NFI;_=$tm-;8*HUU?UU2lqjKB&~6IzPu zcgug;hjNhtNrD8K!t%VXYB3-R=&-Tx(;#TIF`-C!pis?ZhXZk3zPyHtcBx;50e#Ju z7?a~7+JDq?-GH1D-+yG*If0l3UQUhS!>=+-hzp-f}?+Y$N~^1iSc)g}`dG7tE&Z0v}t&f)2+V64riy5~{F2?F!nlr$-}*4}YDra>0z? zh@UXd{+pN@p`a+p=OYj=?0xj;jV`w&bB(yZu#QT;IrHidJb(~c}{A& zY2*ZA6g}6Jl`!oF&#Kz*rC7U2Wp@LB^v{zgOtZvQj1kGrLw$m`M8^-(U{6v(d5=>y z%<C#IgKAsh(TL4D zHidAvdQ+>KtU1%x+dW+xU6>H7957Mk#RNso3TB6M)@=zQL;EszX8_BIaiBvKnuQeu zE{GL#)xw8rigIewL=_9sdVGKbNb*`DHV(9=05}-Z((Kx>H8DwJ7Nl_)_$W2BQ-&&T zN@CF$UvuCQd)EikL_p>O5Y@;xq|cwibHowJBNZqt1b5rG6b|EhBl_Kn{Rl~}hT0wK zBE4Ax4476qxZ*kT?xm~2uT|tIjn_S;I#9bycM8+Kmlm8@L5*K09$vHF0=}aph_K;E z2IbM8IqO8X1mp@OJKHUU2UUu+o-`w$IZiOH4}j;`&71sw*hs#kA3$Rpw=WY7ngxthtw&Bmm=(Q#W4Qsu*?zX*9W zNSeyu#^sYl?>>hLh&sBCKTUC8R(e;qKQlGEK3;AtuB~otHaUu-GRGg%U(OnZvwVQR z2m)s#S^G-pn_M-RB2LZXw>$ugW;j!pf2(;@mH3)Z014QZ-AqDcsoJ6NEDln z%@XxsEI1bKMxEXn2g_o<#mX|3!%m(?N#}5e4UTUZKU?#5f6tZv_7Wb3daGPyN12T- zg$?fk>y`OEYFoVjTMWtf$O>5M)GHUAX}$fyWxDt~RIV zR-21k{W)ph7CZ$7^A)0{wU*~Lrm*N488_BeTk8`_@fh_mzikR0!=o^A5&ujlODl4g z!X{7mzYJ2bio$%!?)!xFXO%9FEqX6L?f|(1fqpC6Ew@wTVJ~X4y+rO3JbRkc*Vsdl zWMC$|h?a-6+EfS_Z6}!ObFw8vKwd+4Dg?`SN-Ye^>F=2{<&~HCmFJRc^suTN{IEAw zUsTKTrJp=mn)Z>$gVTN_A*;tS4v$1h2TF;!O*A!0rI3u;!Y`4JgotXr&VSb?;Lz{4o+JX&E+cuHlonI2IX3FKXoSJI(> z>l_iS$QiVM02a8_rn3}q3Q$s`w-!0l#yHl^0!aw@(H?=nEJAEThNFZL4Ht8>Tdzub zJLa8msI+pQ6zC@Cj;x|Xqhh3A1WNjxW{^VeRovIrOy??xS?bnyqbf8@xR{U$ulCU_ zU2@Hs2(SG8IDsBNsfRA>4fALH7!9_y;UiN}NJjQVhYNoPbD5&lwZmpYq6x{5@DS~)nyC|`g4iKz*p1vHnR)4_aBENM!clW!F{D}{nVL&08MW$UNq{>>pdF4;p^64y;#|a+(kOT|ZvpkC_J$ua38Y-#tTpB`FrD zp)j|ET1I_MYBnl44vwdXS69h1LrSlj)FvcOi1dhI2RXde`Pb-_ST9|E6;`}m47Mc6 z3c&S{PBFhIcCjGo5Gn^c7j$O}SCf1MP0i4Dm+oW(4Z}g?z@ffbP8^yE^uUqb6v- zv`4!Ny#jr!`eMoV>{EoEw6$HMTHxtBu2b8UH_*B#m|=h4PG*NAg6D=}lJiML0Yo{K z#~`=Q5pni|BO>iK0`-m?hUu=21g9m-!J3blB@Zl}ZMh<~b(V*reExzY-3|}>oP)mg z&WPWNi-G*jJBu$JR^gXC+&qoh}O^F5elNiACEvK%*qzHY!8xb*X*#X zt&xgpwKG3vG%Zi!`DN%y>B-yNnb?_aZkw3pR^zF)=yCkT;qn6gLX(R^ytc(Mf57mC zl`gc!KFq^O(>>rlnHg;@TThmkTw*?*x~NIUJD;t{GCJ@j>$$=|+z%F34`N;&eMAux zu-U)!m{nnSHOZF3y05p@AhS~M%y4C)1O64})lv1Tc5yR1%D#GyAj=!`B1E$_4Dt0@ z!L~nYw`<>m);-HO7}Z@sWV@p(!_=LstFBFQ^~m&H*bOjU)|9{_EbBd|YmTQ$3KKn} z-)GW=G$7YoO)3w)yRkQ!F1yP01g*@!Ynv^^^CYtEzRNIr+gEPGLaQT{!@@h|n-gx* zKA}3nl8I17q2*6b5~&oFiIFKniLZHgqF(wo*er-H7}wjhdLBaMGn;UrsKoY{{@S7m)yUu(>FYb+mY z+-sFJybPyUE=!ho`0gyb7e}!?|Kbh!FzalST4K*=;w=L9?f%8EZv$NG?4U4NtC->U z#pC*KH{uG}dGOh}TQQ8jydrWkJ0uxKO6*e?59~uf6(cdThc_!swxZ?(FJW*76E^Ij zV)nI2JT^ku%fyi9hk|Q#T7POca>W5YLMr!V&2bC5L%1WQFTxy7*6F-ODrDi$sP-w= z#Ra`r*9^!Nm8m^(kb=C|ncwop1$>QSz4N9Ad?BhrekrO(e+esW=fH21d{%$|x`zL{ z{{F>F@c;csC|nNxdJg-73;lu%n~e$miV6F&9QZ1>1^P-Y>%e=bEE@Jj`RE_&-YU(P zDRCL}GUW+oc#YhR_<>XE1rwok12T50^^3?%)tEosZA@J;uCd4fMt7_N-+oD5F`==@ zBvK?WMYpI0vCLUF6+0w%I-9V+NqOw4Ig`y&?;ZeSJ zweCuqUg;(nFAXpqZ6n_y0w)$56EojT3sPM-NTs^>0I8pDd|izbVE4;qY}m8n}zQnu|Djvmb>9+#N#QZQtHMS*U5n#xj|XVR=#azv%cuUn1sSE4MDB8KJ5SlJsYyZPYy`qex`J zMCl?2bf0#nZuhFlL-D6}Xwlzb7Z;i$9Xm$t7saDx=qCp5zns%{7^)ITwX>Ylb*Yz1 zJVp`dW0%j@5|(`A8JjaucRS=2EZ56Q?;G36)s-_FglNS^cUJ8+j_M-`U4t9~sJ7@U z%1p8@3eSNAYxGJBJEa?Ui|(1Eyt74;^2HP_n=DX@o65?ZWfCAxXipN`EE=VtCNhH# zJYQp5`DPf9QOVG#>kF(;I_h?2%RyyA%q>Czg57^Xzfa7;-py_R2W3GwtmSz74e{ME zsxd4Ezx$~G%{^R%nC%n4oTB!?YDBS_Z46NNWKn;oRPUW`8&7Ro{z3YR(EE(dQQe8& zV2%X7Ge@^^g|IQSbGll+xxuGr^G`%V<0umFsz+g zR;$(^a!dTtzQ2lQe%Bp)$pg9C100hYsKq( zYEuldj8+i8`zmO$B%@d7%2vJqi?44At}N=hO**!1o!Calwr$&H$4)x7lM~x^x|4Kl z+qRRN@BVeG?#usG?W(;WR@Hu5YtA+1fO;6R(Mqk$nz<%FW633Hr2U{fgddH&A*D#M zZJrTKj)K)o5Ch^}7uAv2SUsCb_K)iRSQ_^|K%rT#LWKXEq{|V<#ORD9V3tH}Nt{*z z@S|xo>O`X+>y7Hg)ZhOxtWBR>z}wFZgAZOlH0T3<++2c{ISC%LXfhm2xsl2ih5}tRvq1-FO`HjU!c*?95xXsis4Zjde%Tdw4a)@ z*C8Kqo~4Cg6j9K)MR%HkIOxg)ogMe`$O|w7H!61Qy#L@Di~^bO3^#3l40jAN+Nk2G zopQzAb!}Ils1^636ZX8%!U>{IoQHR6OD95GWabCXNz2U2y7)!tWM;obyUEKJ-Ph`= z+MzxdH8{2K911yicAZdEsy_cfa0O7Lv==fc$kmmNyJLyso+cHAD&uWFPtl_V9Dz9l znZTqSe)SV9;BSa!rWWJp=H~0~(?X|d3N5#E5X@0aR0?I&IBzg~Pf;j3!cTD6t;dSh z&fBH-LS(4^Tx6$R{5J8vFDPqB%|Mc#Dz9+6ahPr4>Y0x>ZyUr>S66URlvz%wI)-GJ zv~kTP8o#dDM(!;8xrJQ4yGl{T5pE>M;qb+PIg z`SAeHsZoCNg{sYEhFRU@V^IjT^`6vy3$FM2m<}}I#bktsrL-_Vmjug&-=pTM-p0Y0 z+T}iNZp5F7k#Z3pO0uVO4YKgcOrEJOq89LY{IjlnhvWr`=tx!s*GX%Yi8d^3V5U>! zuGkVA(0Y=g`)uoRU@ySTS;Addi^IsIi#E?klScaBDaFrbpnmfW-KKtNpcdDTH*>1; zg)~lZ$-p^;-f(OxUDtFmb^V!EGujLu>=c3jx9xrT^=AQ#NQ-OTIY#*T7ekaSdDhD4 zW&SgNB4|L>F8{hhqr$;CE5_3{YQ5?bsDsmPYctnDxN1Ai`eP&)VFC&iu;Ed=osA8I{k5HC5cIPs3eO|eD} z1zdcSU%b*u>fWtFkU@{dleGtM7$5d0F9;Vu>#y?PBPiIhbva`X910`-)jy&h?*t6x z&Pf-4%pLbcob6wEx64zW>AvOc^EzTo@SqfMMsm0su@}0Yor>c#>nfO>s6c5&Pa9zF z)p+PgZ`uhV;=Z^ij?Bs^SO*omYL`Rk#CF-l%}_-4_mL2#w&9^FGIr$=paYnirlAQ)`-b=4{f~Y2Ov<1bLfLzoCs|gt(7e zW)F*bm>9Z>d8<4kI*?hlV_Ct-?Ln6Bkh+5&jnALw6ajWCt%k_u-^gDw6w{2(DP23f zjRlMqB1vfdg^%0*BnDdHxBc4pYTvy%h?q*#@$LC1nOqMLYcw(aRNuWsPOwei1d2oV zq76OophnTCr)V}3dF;hRAAPbcdMZEtYcRi{v&kH6$n>-1*@!5k@)th!9IwW5T=T-} z(o&WG&G+!bz`xVL7vZO9{EjoY#38z_$Y?tx-!G_s$te-F^EmM3SoWpA;^*U6_ZJ); z!`!jFV{#r-%p01!T16I_F+Weo1j7EDc>Jv2htc*SWJJ9?I9BTlbMZ_f-+J=kr@QbK z=FRK$3*LeA4W+Jn#rsb+eJ)Z_fBLSZQU6ss@xK~D$r(9+dpFtITG?CtPvhF{3uCCd zu<*tG)IFN@uZvC=+i)45BQWHbC_E~1;BQH`U#OU%?C_%@qKUHqWRVrm!epwe+f)(e z5wI+qF_lDqpgOg-={)(Bx8&(u?EWZx@qg;!Ov*4ty?zVW>a{!U?#y$!Y(ILRY>&Tu zv(n&2A)rKTll*P|+deT^Ih|z+z6wUR3}L=DlPTaJqdy8-WvnUlHS@14!0zAjFeG=z ztA{&HG~43$f(2C-9UNQN1fk66byM7Ab7dYgrWcXeN`NXmWN=FV^{Hf;k?_G7@H(VD zaKzCqzXj7IW4$scVWly10B(sLJj3}oN&x^1a#MDe2ZA}jzw@e0N32%F2*%Fzy57!8UOWE= zxW!~~)VF|=a`fpo>8C&vw)uQA+1oiLU{i>C1S%aX0ggf`#U(h;1x>IxlNp8Crd&~R zcDyO>R?%`QMN(F=LyVLOHQo>#TVT3L60%GBeyqvql3s_Jnljls+h#G0D?0cpc9o?y z{8AN6E^_ad%ZMxG6fR%0M4WVDr7$6Z-86YpT){iI9D@#49QawfGFWVEpc_U(n49d1 z)H1!4vKotfeYA72_R%zY609W+dNqe;FO71wylA8?*JSaDD<3XRaC1L&IxZh1n2+&z zeuiTD53Q@WT#_vaQ%mArxiX-(q7PP>BYHIv!2zidtyV z)Opd08_YQHQ!*o<)6cw8Q+KwSfe&%!Mjm<*3eSede9SNP zuH6chp2dopJ}|=Z@ceN=QBS07Q$NYda6n1ncS2I?ZuX>IMeO{34a__T1nq}(&KY)E{6TY3Xsq&wfff4DNz_AM0g(;zG`~HT{VX^o zb(^N_yQC^RGCYVMFlnE+AS`cuO9NCKQbS@NY zc`7)mOqGM1TEM(d|C$=!dJ&IG#X2G*M^122MJOf-*~s!_Edp|#*Bnd1r>tS;!@eQa zd4KvOwkpmOd>$-t*9uYDABhun^F75e%hES+%j3`8*BX+bA|$7o11JGm@R>9jdppNL~M`B7V6^ zncOS(*XT~N0h3VN*rZQsgKdsD#SS$lVA&Tm+%wTnwrZG8G+Zh6HF>^5(4sI6i)2cy z!)z)6Iot(oP1YD}i&DKt!dtPf%|c7*uJn50-N&o3qe?isD@_&A&CT&5x)TGZBhxF& zs7y@q9fXvq`m>4RZXoAJFL;G(U}5&A(pUGTCG0y9LRqM~itqL~_q^#*#o;nAu0ajj z59xb{x$ur@BATf5xURJ56eq~n%c_Cgt-sQW2{1X>B}KfT_zpRB2z57=#9L$Hp?6n{zlyWzx#WlL){#Hw3@OoBFS4h&trO>S2X3 zQj6SDIpN>`WnVU${kR7?`~}f)_)A3>prb9jDpjorM`ex`oRwneNWZsVsMoZXA1=Ep za^c7b&(F7*$P^ zpJ~)YWPzKNJSO<_UAXUdnKFEU{dPi3VO9n5r`>FxmBEh?`E*Q0rQE$`@arO}b5C8l zBW*Mu3DC`-;!~OT*`lt*TK?=tz!Jp={ zTP26y9Ug%^8c+jwK{jL!R~7(2c>|`Chq7Z_>>&mF0mc1dqJm&9 zbeo`95aLfXX5IG4A79)@Qcujh8a#2K?y*fv1OPPFCem4GxDmDzkMVUE;(Wt;tjlz_ zB!U(gstTKksw4()*mF2uIr$XavT*u86ZT1R^1;M36=1QfBr<-oqszi8iAK(VC(x`N zV2aUN!jlYaq1pVP7U0RO7K29`NZ;se8s9aCjlnPmtWHZe3#j%r3;`u>a7ssT??cftKVr3B*s5&Ic58=eCyeXD(V3%<| zABP~9SMkhaGe2X4{3{0kTcUs{T7i)JkF5#|7pvb(;UA}`^gzXI5m+rY-@|lK{3Pph zTS$>MZiRu_^-VZoroiZS228q}dzsXTjBa|Los%6EhaWaQ5E|N&pCB=?e+r~}VR1<1VwsSN20odK22B2RK9e)B zd)AEJdHT--hpC`dMT_s)lNB-yAfR)wONL)%Q%?P&M&Ym%w58R`>KTxw?Clas;!G`! zl!W(Z$wJ&%_}2dp8hU;69rUA~GtwwH38cy#S&TbLTKdI8*&5ZLTi=VOxE-2wFS74+ zed+Q@LN#jWG~dZvGW$X8_J1`E(o*d5@J05m!`85IQ<0?HfVw^fTX4Cl{&XrxnInUi6rB>)n>YbK|pVIYWQUyeTQpqK;HO~kh9c-f%a=%lmPK4 zcmFCRM%a9qdJ2`9UaGAz_4O3A3){RqISch~67AZ5(J9T`6AF?&2Xs?lHlFD`WPmb}8mvVag@cvbex{g%CM zG?6AiZn<{^_HN`Q>vYNs~gxHiquQPtE9#LzB$N8 zIu!*&=62(kSP6j3mut8{Pc8CQ5R1Yo>2j75jXBw5p6>haL@D;Yh_M z^@Tnf0)c;SPn#!d3YQn{WE`p~7j_k#OnWu_E7-#)aejBS{=t?^@Dmi!qyvh!drb`o zudiimj-sDn?Z^j30E+_>Y;0LOozC3lfTPo^bM)y35x;-TkgXIoMq}$PAocb0>Wn5x z*1vBbs(n~mNIY0Jr7-0^ggj+1DLm6Drw*ff-Tay%$>UDsTDbcWlC1_QSFg~y3iU8v z9(S2C(xmMtNxinR4xTPXSu8MVx9eR006NQ>Rt>dD_)=*)p8bRUu~2!CCA%rX5~)0a^rg2SZ4+Ur>%v-DZsdJ z00C^i8AZjwc_%6tT{2Vl!t6D@{k8_WrT{ZgT)mjaez*Q1pS`5o%CdaCtMTlXw6)Ed zp{s^U?hY9;%<%86g)i(%vef{;w-r`lm#G^nD}@-HJzPNbK*O;{upkZTStMA1<;CRF zMnMa^nSMByqz`M*9a}cZ$0KWl6|25(U*$8aKOoKe!$1m+%w7NF?_~>xR=Uack*x8H zZE&xs;G#*>ue#CEuU{)t_oSc#xcdnorK4ZF0Xq5`6+&H7I5RHxT;32tcAr7L%r82h zh{A~{F}R_BQYo8}?Kd56K|etW61A9tEVaS16#l|SSK9! z_P|F7mB0Y0X#;|?p|A?s%FquD4zYI&PEy^wrAjagwO-Om#5bWZ0p-}F0?61W#wtXP zffF@aUZ^xIUAV}Fn!E7RVCZD+&^2`6KSy#y2hHKv2$f>ipZ5ZVb%4TC9};x;Ir%GE zRHOy!F%aRpDC=u1fqd3_hHme5yJ62|hXkuAevYm@de#??E{`ZWU=fd8q#My`ZB@dO z){Yah3Rnx#4VG^6zNH$KF(~~GyO=4vM6R;pL+H$3>LTO9oA$f!`$f7|PxA|X zZCpCQj2QLzO?N=10pyFk6M2jYBgiUJDcwlAan)aQ7TVzcu+Vx&GQ>!V!u|$qs2C=s z--32h%c}}7D5}z;?Rd=kvP?rtz;vox!}rcjVhpHat|hHLF7I+S8ZGgzHH;JzK);16 zk3n`!6mUdbQtmSOgNG8iCKb{EUoMjbAh8jDQGKQXMQif&_8{~hXCVlKx@?LDcH^#A z>WTL9d(m@=>MNu`Hf2Cuioo3d6#RRHF!LI+7?28+Qm`7)3fQS*DY`vea%aIn$Ef3a zJ9>vjCp!JjQ8Kw4;6AuBpQNYB(^FM%$~QH)8f_r;vRkr~VB&u@`xT21iB7M{^ew?z z7b}628N-^Q(^F;Z^gRrMU`-R=H=8jDnr6SbOSKXpd(Dbdi$C#dTkW-4nJab20OFTo zh0z4x?qz1a=0gNxO1~A=kkkHXROQbC7(yuB`%09<*Ch z-G-o~BJW+kaZ79Y5DI+nlLW4b0}5KsC{d5;aHl;~6MHuJ;eIEUiAXNqVF;wwl<6^2 z5L37VKBd#MiMfrJR<|p%wMufN<+Ede@Y0e9n|J$aPx9^Rb|&#a zrhnvEOk9idy_~zeaVM@xfcC8jCp(T-xYs>A3eymUY_RuB`XI86*{5QA*Q}x6d3Uk$ zqQ)|3^Y+$?7ef>73U;k^_XXE{fZ07Ob7L1XwtYqHDm`>9um*stQcye~$(656&Wh+K zG(Z0Q-9Fu(B{m~~wpW%)@dPWk892Mi7o(MHN+7ho%;>%xf4Ht{?X?e`+Gb6;D=FzL2q8)>r?I4s;`Vp zn1^C-7Pl@f<#?blyE(AKUSO*2t&e}k=Q7ourM&FuS*@y|2hB9#KI@cvB2fjIiOU}< zgZYQvt0wQG6M1vfb?Cn8&v790L(3rAq&tf%@*V{@Zk%3*W=oh8*Fr7`2jGGf9kQ7xP`N`hBG)+x?D%BhE_uV0 zZbPcPWX0uAv%=D?TP?pSoE*;4!W}F=zXWYNtgStg?%5 zIel`wX>d}j`@3Io+*YZxcG;D?Yz4J(SYqIo*naUaTYBm=x+5>xlyS0*RXZsYUa>rh z2wjk@@(isG9i~hlB{W)T1Cjz|N<@h<&a-};GRX-+vJ_%46t?MhTE8JwND8ioiDIMB zU=e+!UvD^@{GccgIjf9x2#alGwnS#)<}m>qac3+f+$!AF7`I0vnT1cwA}kS=j`ONz zlemmC&kKHp9*MS?m8u+JML|y!u3Ix()G2!WN0rrZ`F6~-O+HtS`0_hfB$Bd$;#z1K znO&iIzI+@9tDX-Pr4RDozZ-;Wm*~yiXM5_*!#LI$FX{~PeKa|D9s2@rb%$VpW9jP0e(rA9KQrSPruNcPu5jJEw=aVFjw z8c5bkTW5s1Hb`D!9o4t*xz`?asO032xYguCYxqmUFw*E(Dtd`E28VLzn5SYeX|_ZM zmDFZr1@a3E`*yfDjx5Qz7%JN{_z!=SvX?0$QcI>t>*#IYwy)l8X!o43cVE)8%^uxm zdO=wAG+|aeWG@Ksm^+USbo@AiM9chmRL`W>IL>|xV1jzf_E@RPq0|Da^`TP!U2)La z#b99lhB-F=$lYDjRmQg7F||RMGV)?AN#oto+|_`<@b9^b(XG=suUGkeFQ4YePZ90o z0rOSp?{haXG3fy>9X5MKIkn^GPtyd^$&E6IyVLPmMNKG0JC7fESfnB z_tLRN+TRx1`_xi)Ycw^nuof;y#i`Cw&AC|}(?D$z^)r?!`cdsan$>fBt!`F&*n2jd z8*#xdltV}774)BFEyd<fW>KFXK*Aqxi|21+f z6MC`LZ7;PkhK?+w$`(R>d#LVBaR=sR6v6!B!q{K`GEY2T9u1uHdXsfqW(4MQ*Baqd z;0kXI8g8>NUoUaFi*m2lVbysbJ#%uzZ=r064idQ2gN^L&ljM0ge9IfE>Bd^n?lg>3 zsS3v>(^7MhrWpfDrBrpDJMW|FO+Ca)pVNsI_v1$N?d*kMI{Mol(0M#Ve1j z87Z?->`1LCJv&x^-<0E=o^vJZh7aS21+KMRIrBuGZinwFKN0MXvX1$Cg59sX??J9aHwdkFx|@>niq!qXSD(>VhYe;# z9PNEV#4ku=AEQ9;Be)U?G1~9b(`6MRwC*h&pUJ!CRen(_4ANersolqLh38^q9dkQD z{bX#Ji{2&imf{m!upv7pxWM+-$dbCUGQ00_ z$MwsHI1#z3smbC-Ot~D~bm?JQN-C{>IBq8(7k5vrnL@i$zI;t6D81A zsdpK+j#WKZ5Kv+iE_;hwj2dnrdNfV8X0KPFr22OlR>iuYgIg>h!S>gGIe2eas}RK` zZ%;uK`@TeQk>la_F37wX6#)wYb>621V=E{)%d9%5@ zEKs*zzKSw!P-I~jU-4v~@Ni>S_u59%lx#A%O0ru@cIZ^jn6zNiA9Rx4sV7z_rFaM; z*;>Vyq-2#=SCqjRCq0FuZ%!x)ly`XeR~D)ZL!T;$GpZ{vfv}qL7XAVR zUH+^}kTX)!0^2y44iStT0{1E(bc08!Nd}78$$CkA>^VUf^_Z}D#`C=N9`PEp)fvjL zrLxgdF|?V*5}!6w_F_?dd#~a1ZFD10{?dNp_JKH7km6C1K7)dq6_qy;o6A%Zs?S_I zvDqPF=Ne^8<@G5Uz{|6z8Em*ux|06+%l0RO5RI~Jy0~uC*Ju=xRhy6k?)|bqH_ra? zGmPsv1<$Vn*3S5ITq1Y?s~05(4L)(}lf-E-O5VF*<2-}cE(L!4>Vd$oU*e#8ys%Cz z1;NGw7F#f1vjeLD%cE_b(SnU4IijGbWcpntX6#81M|nIvS&xXf#_*=PGyC>-V=(`H z0RUle@5FO3;hrj5_0Sz6AMFg(n}V8WoN_%>IafXI)HjnvsJC}lOF`Wcd%%S#*J9^| zC)enHe2_J$6mJSJ|AjWi?ztIpq|FyF;6jjl`<1$lf3E`@kX3v3f>^s}j+?~Iu?8n{ z)RCgh$;E?=V;UE!UTo`F2Me_ovr8nwG2-pk-4HtGhd48q`<|S=vF=bl1)4o)`;9Ty zB|){^APdQuE}aN6WE}ksw4P0DYsW9UeZ!AC<|EtZv*VKvDlyL!iSN1GXI}Ib>7tIq zpk*?V`XOeG`IARR{+*7Oo0CHo@!!82+ZLLZhTNoKi4}-f#LzCwoksjA&Y7pDX(noI zPnghm%N-~5xL8d0LPccychFF)nE^Q}*?3HjLMAfpHtbNXnE_mMa|t+hDs&0B(gG%8 z?ex68ms#&Ch95G&w^h@G3lmU*rZ@P12yHu3^;%RlGFQeZQ=4x`QQK{&J>j|}_^KRCHizkQ!>2pO9|VHQb|qQ;-HbyV_>!g$2^%=QVD*T3b${nZD$vz;<9nldmbv~!@g z3tfXb_#(?3rx6XKO9^!BP9H%iOrJufz{fPh4}8-hQ@>s*3|flh(MPgytFZ*bt6FGOx7OjV$H zB#l>))jl^qr#O3Efq~3N(@G^lO26YGrp~W9Fa<5FsU1@$+hm7$(MxmePG}qSXjSe` z=-U*@u9R_etVYJ-xQY^S0S^5Irs)L+9ve9?ezBGP`4TL}KjV;lXMD`F?JWYcv4IrZ zCc)N^z;e%2=Y?=*mp%W+Flhq|wqU@rLQmlu@_zhWYlOSc{t=#H|7wQ}*h4-RHzLguS69 zl-6`UzU9$ZWnAG7=ruv_+BB$3L1k?vFbm4h3)M6VbUdDqEC&-0*B9Uk=50}cnlWj>sQq-8u+KwhClwN22q!> zEfK9v(P6_FEfatvtyFO9H)NJ>NB{Bin3$9A=Ax_*MP4h(d_s_-79vhh2gS!F)d%of zcD&@ehhfvUcicJ#bDxzj&>VV(QW*H!MD=iz`0DBdWa~0E)y98N=MH?m|3QGaJYqqj zD*Keti5*v&vLvl6 zy(#}Gwi1TEDg6h)cCimPZV0vq?yAD-Rr1STcs_hpH?K03Gue*G5X|BJB*&`H4?HdE z#Wi~fy$5gA8!6*mVodNi+wU(?GEZKDQC>?10YQv+V!bhXSNfn*xpnEaw%E%fz3ZS( zBN(DW4-`c|*rhij|8yQt#;iJu5`_NP#f=cf8&ol}|ihLk%{HHQm$21t&rqdi9in_tAvaiXoMmv`U{P#pW;=19SNP5z(mDi*MOjl+% zI!-@@Bh5hAovX4cPrTU|!9BHrBS0a<5^GzYJW={xNGGyBfBJSY)*rA~!Wj$3tX03k zm9Y9!H;ZiNA$9@(22L-`!I6c#H%RS6>1D*G9r0dIpruHS;a<6^zyv?t%}~;4r%iya zzwr!{u38N1`qMP8P{%+=bA?YvSK)bb3MJp6R_%>$a$oyLJ_M)U1z8*3p-@#OnDlwi ztL&Li*)l#RCU=~weZ|sZ7`|leer=tinGrc~ym=MHt(V$$~S4)bT7j7^{UmZC5X93n?FCkgG?N$Td&V8ymTn zWQ~m=g(0{T_3apPlW}Y*im=zeB=hxS%}MV}tJOH-jCQcylN-qOPd)-_dXI9-qu%ho zm>gt#j(1fjw`Wn(tGV&MOnNOAq8(`0Jy=I2WXvYl$}KjK7VVE{0QIY*WVfpEXOgD~}IRjA{)%XwKM*+sdx| z=Pu1RWVq#cX&Us40PU*s$3N(Iby=9hak>W$gtiBi#x}wcoNRL|6>qFF6J?gbPxV(_ zz?9KzbJq_)+KD>lNBxurjq-zUqXPcrnGV7E0BY+cu|+Gc}82{nri8lc~up4&VGx!EZh~% zx0Dd&?X%%(nl5=ZPMgJC3Xx8P_a%~(1;k2(_2S8+zI8WRta2bpdF9DxBGfa)9~q2pxl@WZye@w7K6bLVVyt>0QayDZZL>MBO(*JE_sImE&Su8WNbgf~*9w?5 z22z6W?*wMI{>vj_uX0fN)*8pSg%10n`d zbg%mNW+af;jyroW#>7NqkWPWv|3ZzS^T@+~^UqYP{LJs{53W)5e9HRQH#vbR4 zw%r69`V9GdP&(|n3AO*pbGnJ~=h}iauNNihMZ&2{5RW^c?v#)Yrpu>ci*`5kVcg@XnH;$^EPSI;FX>B*X_Kj0_`RGS z%+R?^6NrFSYK1m@vg%Z*ON&a|^g^1y5RsxCTsB>Uk+KRvnZHC=TXpBt5p7-sOwJRc zR5n|Pc0YzeTMz*SqRz`&_X_2uu)<>0w0FN*qmG-nZJpr}*?Gg5j;P`^3h|dt=+5A|!dUa~y1(aWPBIkvT z&jz}lKHK<jzzbbf~iTMCuFtO~k)c1WA5$J4g^3|3_C4yW5vui9UiZVP9;b)&rG%>MVr~JaVX9!x#aN;$R)`Y|5vcF-XG~UVF+$mwfZxe)t ziGm7J0-FLp5qkd;)azoAteZs51$gu)Q`6-#1Z~}Dw%0vK zPP?$SXB4V*H8Fly+a;0GGvpmvWd7kMvZoX3G+kQ9m+#9^_zg=*nkMZPPNwB;JhX@g zQGYIoU#Rg9-wL_rKu3SnB`ssd<~F#vkJf@C^Ey51(b(svk)arN`qL61oTpOeL^x9y z`BE>&zS`;8&&%PFi-wEwg!i+nj~cO78CATIuB-#{{TdW1rz!Rm6s3DZXVrjGTniH| z1qaR08umU_JoMTK8ZC`|<(11DM|5j7qxh!wZ!Xm0Z0S>U*&O5uRmCp7=T9vQ+71?KQWkaA##QQG$)Qb&#rIo_> z-#2Hq#I7>h=Nrs0Nuk=1H^WDF`2?Jt{Jv#r{Z8b!vc8Uq>Pv5;&jvpt28Y2S>M(UJ z%~Jb5GszrCZ!;>p3MQTj3+cM>x-}5&yctr+9TH;EjDM0(?I^;f%@Jg&`zwuor1#IiV^aWudZoaD21YtS!?Jz`a`po*U;k^2=$DA}X6)pAeZwUq? z(;vF>Hq@AZ z?e!_x)n^ca^`di==opvnz9qDj07;^?VTh3pV1Gt93U z_%x)p>*qwPyRNrOW`?F8C`JXMF_uG`%rC@;z?mzIcfxgJaYOZQ$3zKB++xORERX0W97QjFlR#URNkGJc z2F`D#m1<*G#Lr~3llM>g(5v0kHl&8vk%6b4)Mci#@t^-P=hmv;tp}=>hRwUh?ZQ;v zDPEoO%T#MZpS9vU)%G_Pde`aW^gC4WT?7!8>2Gh`&p+;85xsv#$B*f*&;T_JnhK1= z(^EBfUH~q7#^HOYv<+O0jr{e0?uVl2_&FK@^?z<6F|_#TUF?kGasKl~*2q%-=if6d zb7jUznSUcoCF<)I5#kYoChZ>Le%(!-P(5K2$j5)WsnXi8`11x=2H z@q2AR{@6>Lfw6sy1B#B>GbIDSwqFwgpn3#@gqi=yjx}>dRoWwfH^w}18Z$Rtspi)Q zY|&X(9ACLno>&-ntW2`wDI8Hm%dGGc&lqBJmAmYGf50Ak5kGu1K6+~X1yp;9q7|l} z;BF{Bt6FPso*dkUsRc)oZ}O5=Z(^V2&ndS8{;%;ZJqG`Z?RPpXwBCPH1O4y(WiwYx z2UAry$N%S-6RWDDFlUJE&%db5F;*TE{EKB~bP@Cp1Qw4bD*w1I8U^(}$hF8ew841E z3VvsGs}D)hzHM3*U5?H3%zra+!5;zbiSWb=r>7W zZh@PgPxY`|_3vnbsj4%r@B;r?D(uOhkOt%(LlbzW&nSkLe@`Zx$}ythFA^+Ihvt6D zLJ}8A2{*=IHnF&BxijdqEr~DfQPZZ)U1-stX+G*e3Jg;dF^7<;ITe;mPZblvu2O)T zx#lO43?VGq>KQCV&7)&Hb-FR(`>QqLT(5q0Q0@DP`395BQAnuusI<+j z;*V4(l4|olR2#G8+P7hW!`O8i$4U2WpS)fuyw#F+`aFJt4W{jQ4L}vo-1glOB z5`4U@ovJ7=TH%!k_k_B15EhfwCWd?1Y@A!bdfO0ofZvLryOtR_i4K|TRFML87> zJ8m7ZDdiezD63OBR8C=rejhq(F5ZgO9CN~7ld|xSVuIs&lNUX^OGpq>`Kz*oe6(4) z2GuCExRig}cYL%=>=2vBx^8D#Y<$$EJrsks(ZA&{A8i=0smP4;u$dl7gGssQ6NmgX z0|HC*TK3HAT}qop(#18fNv)yrwW`0UIhT}Wpt~#v!`iA(f|VVu8hsPylJ9u(fp{Og z)G5gQiugA@6VM+*Wr6WG;*94n4tDon3~rJqKZl0Nbh!mJKVmp2giWygb{rHTd`bWC zkXxP?)Csae+|6ATKnOLVCUe9nuaa+sj7c#`eNnYl`s@#`FZgLM%y`2h@hPgHhi|z> z2`ILn?{UwK|4_){AA0!Mt9me_>AP*cV}3_4$-3<}MiF7es$F>CHbcWOfHP z^G7Ol$ooT7c!gZldAx09iRJE*SO?YLdIg*5xEIa#IJ*oLNz;q=aSQic z$DPzmnViX-RIuR*r_Bks;Sq4DR{r{y=RrXe?c+krearPi{;OP%;(xR0R4}&wPR#!Q zgt00*3TQ&;pTGqc;VRtTrMM5Fv~LF7yzbL)%hduemE45+2KYkPYv@#djGC&V;Y@wg));f9DIv*9@V@N{ZIy}4)^{V z1`aKJOOcJ*zzN3>81sjX6UMc7tF*!vKuv>>-+nic1#8Q*yS*k-Y02Fs1Sm7-ScsR9 zcw1}4j`ZwkuBSzl)Y>OZ56=cn0{`X{X6MB@Pow_9D3Km_wtH7DS4I+ACls~fUWA#j zO?y2_ui3hm>v5uk(j*KY0w-hXe`lt-COuS77Y85n!R^m)jiHA=99>6{!f{uNcqZ-0d zf{nXO+{@xJis$MB;y*he1|dkJ_S>lX4E(>9sQ;uXJ2+b~nme1BnVPxSxH>p8{wF$_ zQQ6GS!PQL4-rd3E|3=+Q{!d=`|Hy4NzU|$T4|2Xfk}|%zlW8Dm+>l~W`{}x(N_>EP z05mpq1h|l(T=-}j6H`W{i=*d!rFC;Nl{SV=cgkYX2(75nbx*B(YxCpu#=@fe!iIXM z-IDdjcLE!0k`xO}ze(Q3E#JScKVKhPOK52KnEc4wKY zh3@4GC^FiyGYIvHOgM33@o?hb5OsFhuD67v;T$rQ-GEz^YDOdqHnn4j@l2Va`KO6> z5~U=yl1?umAqGn1+iatmhm((rvo1bz2Ad3fiPm`CIvTmmJt0LG&jthgLfzS^F8F)k zR%>}0Y9l`~IP=JZH3Fl%6EXqB+pDh){E(IH`}AY82v*TOYf@!OM2>mnd|0y48>Zp( zO}8{mj09uWVyeI@Cc%u>6oR$2g%(#{ys-;n{#0wnFCqCmt13jj(o)X=16b?LwhQ-!m5CnR3yi2)=l3Qr$=OQtc%3q5@mAFZ%G|8$>LBEE6)5gB5|11Y(<<3 zDshIsr+RD@B-Ongn-H-;7&HH8O@%<6joU-z@2P5dRCyPfEPf=2SDwb!JfDVQu7*&f zWn+sY8#YXy{LAql1{QuwR9SL(e6q69$~;FltKqdE5?Uo6TM?R>^o)*aL2#Sk$YvMg z>eSt5O*Dk`%A%r~*cYxz!J>xaAbw0W=+zYOk!L)Y<^+ApEV=tMOFA6;wJKrf`bYqJ zd!4AFZSA52skKRYB!#sJPOz~FQ?{zjxE!5GLSPcOS$A2M@r*mCx?xJ>0#}Bo<@V#j zSRPv=-2AA@!YF3pEn}HxO&e0CW>K+|5-6^^P%{lxuJbDvn>w9GV9kZ}h%KWA4Rn^} zA8fEQ2-aI`R_qdZAzr~09z5f6OWKXHxhP7`%Ik9`6tNggc*@hpI+`HdJ(f7 zH^I+R5_@7J(nxd_5&eN2D_mEegO(hbYL2zL#!DXeYC?o<>AH!p_J#>27M)znI#$Jv z(*hI9?tOFfbxUKkzh>>tqp&53sdB_gfA#T0o{So9+QA+E1<+6?9)go4u=h{D%Jv~C zXoFcW*+SlS*t(RK_#V-r8T?ss=g)KytM=q-nwJLdDnSrqUrnX?&F3!ewF4#xUt$oY zTLZ%kI&|xX^THb!f{k7h7t)tDzll}5!n&qMuevv5ZxI$y63h%E6p%e;yK8Mt3i6tS zaC5x)+Wu z`dgJ;4*Ib51B71(P_3;VLZhN)uk+*7q+@xtdt?Dn)6ek@MeiL=<#noV&q&LOt|IFF zfnTa|wt(pgKZEz(8hU683~dttVMmsyE6S{HpV`igSg5$$qCvvT&yrn_(cy|O)wDa> zZ+zQ6oKSn8RaM;s!a59IK(=H(gZfilA1#>B;KY%$c_iO+(ax5qyNVlM#8%z!y;t6n zsK30(ubXY#qX`WB{9&L!A02cvq&<>hFDTxsJrM(?_nHd6cW#RveJ(M%$qThrgjWj# zsN(Bc?>p#-;wy$b7jGwP zGZ`8V-gx(Hp~EB_a7|&-qh1L}P)cD$kNz`S3MmcltAfhkVe>r?@)T`WwIA6grA>ft zIRiMRMQ(V;@ULPXy>z8`22OVqQXE|D9c|q+NM6N>B=-nJ&pN&&FZ<>=K{#p z2z3$dRW!%0O9r6sxL^Fd+pR~$(npsaF9Kg&bM;Jw$Q#iwnC`XR#pse=uP`XgNV-v_ z!cqBT9W_Xp(sBMpH5z||2KHsTn|GtqA3{<9L7`-0LUz<|86jn-&Hwc;30u!I$IHT= zk1+a8*on3bLXrm5cZZF|fCgpphfSd?bISde;_w|-kVlVZ#`_PHyIxHJwTI`FNP|7O z!_-aOj0vbHlU3KPcMdtMG2R0#xs7xZU_8ddryBH4E(j80T>FB%-OOwZXqiC~ONL|z>$wPkXfBe9^1F3=Aw zFw5^I>f$QeM5cNY`t6~2yB6|uZRpQ)R=#lrZ0Y>K8ymMkf=j2D)ZT3C`N`*w+EUfM zzpwS~RRt2u!v!k=u6l%du;g@O=@Y{#K=8IH%~Cu4#(E_=ahuKY?o+5bZkSg0Be`gp4$T20%Rq7h zOH{-D>cnFZwO5UY7okCM_9O?RPMt``?M`$_;stZcJ};Y;K?GZ=&+=GL>AP_ZwaHvU zVoS?{b%X~K**^Hgt|p7m0L9y^U;FMjL%SKiNdo#K=K;t~!VibaYOA%AM^jC8b06cs z8EWQih;%7{$Q;MWr9d&;@EkLaOg(^d=s>R z3hGJ`1ADg)OQ92Rx zEaznWiDleWI(PHQ>Y<{Km#6}nQwT+|W0)2t=l{dnI|o<3Hf^JsOzeqmPi)&ZCbn0c ziEZ1qZQItwwmosaY&><|I_K?8L#v~Wl|Z)n zx_Mr%$)J<{thO-Z@3wj}Sx)F-W!dgwl3swA2xy#Q8>5b>?T!v8WE#mGudAUYAzeaCp3F*(&Jg@7!zR#TU>^iF|UB= zjg-UFMjkn?$C*=FE>o1!RntCpL{Gk2adj+uA~1d7@C(t)Qg;+%#MNN#XS)%BAkW(L=8+{#;t3GtWQ&x@7pa}orI%JD;Yj`kOYT5r=i zkQ1ettXofrjGiM zt=$i6*u{f6^fwCg8~zhW{Y%ey-P`R{l+gSSGDb*!4M=brC{MhEX!31=9;@>U%o-WKcIq!Wy?Agc`2 zdg7uNX9a7OgdW4Bx^c2nfY5=(B90)>v?KfKk4r$`X1zF^N2C5$C4;#=o7qBy(6@%0 z>ejb2rhS=~usYH8T1h9Yo8*w+`?f3`Un5s{mf*dX2cJ@_v}yZZ&U3oLXj_67j6 ze30cpXseTyvGb_2z7^ScnoqU|+*a+6`=M8P0+d*x?th9tw}Ft(XCysoFCsAXFH78u zDnb?7f#=sg^P?vy!{^>arEGU(LT21_Ff z4|g;Rt#~I8(z_O6_V6P?+dh=LY^JUchq(>l-&JTc$e5g+&3@8zH?%cN2EPv0SA!EN z5a8UV_GQR}ZH-6g7x=0r_xBrYE7{Zg`f*a;BJkU@{Qy^&*dN*$He7nvv^N}?Ymu?u zahuwQ7fijow?3KCVAKlP!Wh%Wu&y3p^~S#%$Q!n0*COwk+pt;nDF>jM+bf4^^s(Bt ze;KHKn>PQ=(NanX7Lqufo;~}wn}h0zsg<*cN&EM25XB42>{1jo{bHK5WaOn&zx& zPI`D*T^{W|+eb*uYf{wcvVcb*=|?QoZAGd?eQesiYN5d`kZZjXkZGz2HBv0wTz91J zdz@_ImEDvrC}joEWRmbQF+;Yc?V;IlJ_Yl9FmNHE&oFec9Q#bqS`dnN#lguU;+lwb zN^FMuQF;0KN{=@6K6Ie2_2MjwctuECO5u3Q+OAjh3QM}Wg?06o<0j!cMPHJ9fWP7j zPSPoLW7-`I(HRa;$kK% zGLTMLR+e7$u)iUBC&bBf*}**p;$}#y3q>gbD2AtdP=&z{R~?|>3RyJQo}w#%M|kJO z&g{FR^0XdWk+;Eb-f^^EwIum==Jj%-ioF#~ zz<$L6ZQ|gP`I*jT489+PcJ=#COV1G2E=;Mfa|(qXnwI4fLB&c5ho@4VuP)qWDTJmy zFm=Vzh4Z7zf-W*k@8l0@Z<-7akrEdG8+dYxzE7J0A<9=Jzj(5v7 ze|sC~dpA}ZU1F_pQ5(oiJ*jqyjxL#CRvUItzls%OGNcy)skqxc_gPmzVX+99UR?6c zXy|58-MtORdSQ^3&>a;l*AAWGJ^v-G)izbci zx8_DJT$_!V7H-qVRm4+`VcQ3U#(hVLWB6q}J0tcMp6$0Tao~wz1F2Z`&Bjfutx`1N z*MeVDbGKPT66npjw;Sgmn1s{LdcsC5;PqYam9zTmM@`!mU0p5KhYR&up7bGm5}t9S zQ>{aMIEyH5p0RQ!@3H@cR#4{ettlUc?HF0W5b=IamMx(SjTDmgvFV&-?)nZ?yrpP> z?@3H8pSlj4wK{M2h>rq?Q;TzZf!P#kQbDNX^3XvngyCHS`6|V(O5)ffPVMH!%p~U- zxkhv(OTi}lYgg9jW4)4ilhhIg5`}fbuNpzPGX)FfaE+AIjfBcOd?J!vg^BMJIZby? z!Wa0lX`g>zKd1K4GzRoRSRWfO#33d)j&1MB12Zd2^c*^Xj@H8rIgfo-5A$F)YH?<( z(^zOtHkJ5A!*p3shftTP(1rujS8Z1!Fg-T}@lJBJE`=$GDzV|KUEHD%U7c&wu3P=C zTjLy$zb$5o)+8q5N)d0g9H?(Jda>)MG;cbAQ79IrY{hoX&mAlId=q*nCXCjX;?V=k zwJktzwl1a}D*j~N64C}R-@j&39TF>BqiX8T-;;EHW)UzVQruma(4;)%f}b*Bk_`FM zB}3g*hD}t;1(+gEzs4@+sw;R^TtG~Ud<0q0d<*9mJI0N~P!RUbl3(QJFAYp3R;s|o zRhaU(lZ|;n3TMu*Y!49yCr-sXv3v}3U{R7_yqn0DD76q_1a}Q{HI(_FdaQW}*tMLt zM8%Go@Fl7rfU0E$d0EV+I+!LhqA$Xw2t(Q5>-+ZObn=Vk2K{VBW!Z7s0OAG3*~<}I zy6Q5~P)Yq;G?i2KZ&by?YZ_4^fC0|95wZOrK7-k-D4qc8JTr(&K8`g3=vG zKtRo(`M-ajX7`Va8~;iU{*@&EDZ@0hA0m+#mu<&t7r+5gc~`Ms<*V##=4(DR^b7Z zdr_+yDVt~G*2Y8cr&+9Cz|D~_whs9D1m#4&zL3GJumbA%|#&m*Bm zj3FQ}=Ja2-UoLS*MtPHJ5jPi{myM3n#v2aW=^9E*F_g4L9Kv@91%3C*Bim?U4J!-LVY8hOa4 zwQLy{8gO&5z4F+tDQRQOKkseYs)cY5Wzp6T=D`G)4)Zuoe&~0oETc|(bZR|7S)n<* z_;D7lfcI8Yr3wwHz2K+01@?woEwmL{l+B}W`%@Veb~7cKM&N)}06B1=8{ z59b!sIan@>rgdbh5YoEj)DM>NJ8KoX<;8$9FOrfpekW$v`5k+iVYvzpS$mzaaj!OG zrV|yIa|kh7h2B@OJFD0rCAAXNM4s7XA;VvuOqpV64@+ATcXU9#+UQWwS7srAdXCe? z8A`j;9H9)WQ(AV5(&tP>KIf{RBotlbm8Rm%zcQ&9sPj3x75!Pp+TNmwYVA7RD#?5y z?$iyW7_%Va$&UqTyE%-CHtDty8JfO)CSL#7k#K@q1J$ z!ta+srdWL@Z*RH?G0gsYi4u$xZZlI~z1T&ZTtX&Ae`#O9 zR%ftwOi{tvIDyg=*f{< zCn3yvOWCB*d~P3X(OJlx5pnYYAqseCr*Xj*({@JG`IUz&u^i3}TrNL^`DL-#02a@J zeBT1bW$DT@qZozwxGkypIB$U+21z&%7xIwe7WVAQE~L*ps;%s{j0a_ zYd9nq5hikpS=0pr52*r*kDH_G?<+_i64%&A`svBIc|Mcho4T0#>m!D|9V5P;P41Z6 zv1G#cn%%Let|84+-PN)?SAE|8f+!?x57jXm^LgT$wy_ zHIq*0=ApQEz%E^GV-agqidLgXXGy$R6|F2f3*L*A4egUgm2?{fuZXCx=PzDcUqgA= zsTFCZtNgrTzCnNdb774%9J7x3XI*0k^55JSj6`V7gh0XLjV! zQ-r!BR`GZsmhzD2;*y z#qDMdOt72E`e2u*-!V(=epda9tm>s%Txm#bjpp2}@zqQStO_~}PbZE+&02w&AE6Lq&G0FQ$9$D1E zsfdhzUbIuW^dlav(=Ao}u0#pj^QKTzizMLq*m-v);OvH;q7gTgxFzLC2zeg8TldNu zYfO-f^L<@zNWTeL1m%^6!zqtJfY#maHg0Rm5upE65h}Gy8r8TX%l6gzRZhNtf|h(noX4N9$ZuRq|qm!HU<{(SiFpAh{sJpUL9eA?0{ zJYq(cHb(y$YpV|JjJ$yIetAKP#PStV(iaRF6}H-?1`=NYF(w*4gyx$x6zGq+kqcss z+GR-2x)`O^l|)pHgwlqJJ0lCL1r_=q0U%K3cq^q99!(E#i=CVB8VQ+hCJ)?g{hm4m zItP;-hmKQEm+Xf!neIxkZZ$i=SpZ(%sd@zqPg#e;(zP-f1>%q!%< z4Tm8O*UiF^^`D?NchHProcF(9^2cO-akS`Ej1m#AC>%m(0TnQlJ`QMbGBRq5XbqYSf4yzDX zHEsb~=8_~VVcEEvrB!qSnIus%D8 zVar3+GJ{WQQ(MY|LwRvHBC_G-<&KTDIzU8D&zQp}~Y^0Rp4w zzMTMk0^%kP%rxp|nmu9(XXChVILm`?GrNVDdZv(UNNB6|&{_;M0<3}|OH6$Vhi}G~ z4Hz1RcUy>~2nM6h&j`r6H{vvEj64*3K#f0pls&~;}QHHYGpfOe5y3nR^tFSGHo0&eK? z;o6Q_6b$DylR3laLdkGqQc|j**bG4+3AH}EiGd>#$H%y}Kh;8<`h$=R%*WeJb<}VU z50dx|OzRbOl_Fr%h~9%MG2fE`&qK(JTsx|kxNgf~1mQQn{1{anThW`1w*m-pIR@!@ z>An`FiNJyHe4R;Qq|#yeG)no&!E^&g=v$MDgfZA{vCF1OXh_Wxy*wCk&fG3T@`dc| zm6$!}4sCY=klnLRsKK));xpPili@aoz`&-lz{Wl{)B!`=QeML7OdkgYxr-s`b%Lu!_4Sm$$M47ukyT7--y;clzSma;=CM>L9vA2!l zI{(S_zEjBAu*^L2Cu<60(d*s+7jEvmLd^i)h zBUm6NQVEOBIt>R z3=Vq)optFG!4w}(7A1NL6DS4lH2811w!|(49^L#{1RoAr^agm9U*aKTZj$}F))p?2 zOHSZ)0LxuqyZol#zrzAfG`~EwvpPD!Lh2NJ2C>fSrVB#x3<}-9>PpTK$rMz;7FaPb zZ(vLOm&M%v%W=#*gO$4MJ0b=bT2)7IOzo3;^NjI6G$%Yeys=+hab0cvG0_ zI5-o?;9tqM)Fv`YCNe3IJ4>+{R;xRzhH*gmvo^3WIF@-zm4y=!0rz_zj(oeSdC2c} zRRKyf;IZgca7AM{%p$#rndj^I$#lEq+0k97oQh=hS*qo$r%$hIJc^J}3Io;fzZ$BB zJBsQ?m&b%*DpqC0+3WQ3X4w+W1D?N578*H9Rn`B@8x0O}3|NpxD`ySY)_f~5vrwIgJd75R+5xCo*o51E?2Drgr5Dto z(k$W4sE;KZ7WPT*8AiLn{r%}g=FBILBy}kWtgN?;86Qp29Pyf@u^BicSSd(fq-6|AP@aXq zC@0rzn76OpcephCe&lbcMNAE(QVXQj7uG-LleZyY--%%;M8ujaZd@>~IKpMfUx!As zNLaV92JCr^tU$T^B`YZTni=&YvbpaLSLhgUAm0p1oz$Au>Qwql%7`jd;#frDnwSVg9j z)P@sCr_$tol$hWFTwMcxv|F}%N+g-xXyT$6Zy$&gEmwx93yKt_MT$x~q@SZ=p9&nR z+EJ#qEYN#duc&Lntm6_kdw$+lloNmV2qY~sGY;=oX&`W!GA18$LO49pU| zf=&6e0rbki$T#$%h&v}FnC-=gz0kM%YrGv%rdhc#ytsJ8PTN-yUKE-3WhZ95x9|f^ z;sz{UQM{I5P7mrC4F*qs2C7M&Lbfa!+jq)=Cv00DTE25nNDsuy=;{~Y_omQcXeL!Q zp)OMDt{!VEd~SoU6iOd#`GI6|y@e-H_}A!_JpQmuu6+i<*aFc}!##wWzY2G54jeQa zS?mxmjwx6YZY~IGlD>kZpB_PIN0BstNgMu|6nz=%y(aX%i-`B?D`xNIHF(V_!aMov z{ind}DN1<@ikq!7(SnwxkIRM!KcujJQ8=Ko;30~CWfQ}qwU)BVwBDuekj^A>cJo_$ z_638Zl2wG2UcVv-q?+HPds`H> z{y+<&3NLQjuXD|MGqtZ4*%kL9IV~Kth4)BS;gk*g(F+Ys1%7P}h2DR}>n!hmi?2-x z_uX&4D8C~;)=2^zV)7|{;t4Tel0*Q_o45|`N#_DCeP=|_dznOjtHckVgZ0grnl{?s zcgCnvf|hpF2?K(3tUDL!O15B>uOzvhpbn%OaRoQH#ldMjm57mzOzr1 z?w%ZOY73&TgIzz6v0tg0ef)dBoV8JySt8%zU|+hhC3+vf>Iz*s(qi;p96ihvozPS81l!HIxTl>ZQr zfVNY>z)(xSRrE&gf|qO3F#{-=%ZuqFiU|yp@v_sCQPq$m^#?)K#v2-#8SbU!X?H4}hjBO-fKp9L^o?hP#7>paUkD=H-h;8lD5c#XwhUXk?@VfG{MZ zH+vIfsYNIGV2R!EAp@CdS3B`kQ+ob+;aRi(v}xw^;mQA%#u5G_JT5jyp9zt_Xzu?7 zkc<-;sR&rg{~IiC(WAce+4D@F=!t;&l=s&Py`}fJJ9vELm(TAZihxaSjo-ER$Ij*| ziGabTA$-SkAX#)u76^yY?0G5>9w`B&u=9Cv$#LHCK`oZtD=esME<$#Fd?}@vE+m0}KYu=Puo(9U{l0IQ`L0w2^(3IatukJ9*B5*% z2VobPt~kb9TP+jA>%;l*_v2~PlnzjPzle8ew}HXj)e_?Uu*Z!t!b58dbiv}FWL3D6 zo;~N!RnlJ_UoF54-{`vYyVpXv67)zNF)x0~l3#x*1W_3?UnaDCg#smQIE(9^U3|-k zJ$Ej1c-GYI6iRcTx*do17WQP*o!P<5WleF2WVY{GfQvY>H7m?hp!NW$*Sl-q?eu(h z7Z}Dy+XGJ}Ka!z&Vph+fQ?v}tg+W=!m(6fJBQ)%ylHtUy`frq$vt6=x;h_wn6^ph! zl*$H;mHH?EsxTnHEO#WakD>Ep*0BC|#@8P~{OtL^m+@ z{bvU{(O=_A42N|QIyS>r+qljxH7siL;Q48~Q@i8BlF@awwzj<}Tsiphr2p_MM?|e2wn`L%uhL7oY+`Gj?2d>+ z@aF*71u*tnm2bg)WtLI3Mf5(?> zT`b%QMm_Jb)7lbR0rPeDn&mE&gGFVa;LVJYb8REhHm#X5OWLIPjyhf2?^QCI~ZhZ*ygC^$XS?)@@pCu zHVK|EDH<~bbf!Y%bq66@Yq3FtYZ9{)A9}J9XX#wx0aex<<_MUQnB@;r+y>XNCXLUN z4>J~2NT)lI)}*wyP1Hp?rbjvsb8{QPD+?Ip9{w0u5jmkxwmIS|N0bR&ClpwJCCp*@ zWjpErb{hsDfY&m&bPz6vkK=-U+UVcGel_!Gp(4PBvpXG+&yqIwO%8hvw^^a?=<~)| zB@vH1;FaJFk#0tq!Q#4qofJ&7(bV2KWzr`*P=9jaJSZ`I4#Nmjy4QI=C-tc&%p>jM zyNShJ^ruw&NI6}Kz85o9#Ez;7usWlh<~d;S%7=KoforH!og zjqLs>=u;AXdC{ANNqA7Xj^HtbJ#Pzh1z-EgZ1tK@3xcZ6x0@6W+X%gs5lvRpIlipSd z@e~l{SQH@Hi=pce%}Wf+rHF@m(gjYlDQtcimASgyeh0;rL_s6Aaqy%s-YS`>52*zC zF;_n<&^kxYn9=1yE2+J;^gVn5lKWT_oL>2$h3{vz6HpXYih^y>2JbL&gk!V*^fj*N zd*3t)o4U?U85QB!-tzNL)ow*_DH(w8Zu2Xi>B56`trNC?X-vnx@EjUqp36=ut~ ze$>XNi`o^vL4q@V_?H5A{)p*8ehj#-FMmWAo3H*|7@xS0{GY-8?^DLi$o{W$CLw0( zQ{g|cFR3WI*I9@QCdUochH*xL4@L1WEvWRpi(ikFJaMYCto_t?@IUn3gj>zu*LiqP z@@CAPab)OlmDAha614bRJT>CI0v-Hvhih7yo)Skv-MH&{#6)^)E0tDNH9OiyHMAUA zy@+eBa%r8=x+qbkhqrL=yX8+O9j#ZfPb-*Q4VvnJ+t%e-HK8pg zCs_5u=T1v)HvfLayAiUtjh{!*C3rLZB~fUh`V}WlEyhK4zLc&9s9&v*gQXND zFJR)&th|>9OdZ@O*vtP4ILCjh?Eg8|iTMo-jO_nLJwr)DaZV259ZtH+Ut^CjI5c<# zHHQ$NunCpC>DXJaX7sW9v;U@=SSohsu?j>99S0}#O+KjRLJvV;$PW@B}NLZByYpDS5J7D8WU0sWLj1k!~QwkH%2W=I;pTtkiR9$fgn6 z5t`xq&CtJ<9CmpcB$FDbPTS&JoEEr2yddDs_#$(xg~{ngiRoj1>FLCo()imzM1l5( zU-i&)6M-=MgD^{(!*FEpP=L9R*1}eHn=+~FD;2+Y;g5!Q2pcgQN{y+}oC7c?Q#;j( z$7Y#FhDraNQTjCYbnS1#C-nu@rX&>-lk7X?FWK+K67r!BjgWRf~Bql-w%7&G81idBLGK{ zB%ig2nm0U@TfZA;)C|Vui*Kc$eYh(J=&V0;(2sG+aup7PezaQLcT6HByg{RR5PE6- z(sUhPV#b%+TC>j?yK#BIClylG}bM1x^!gID*sY67Z=_G(c2qbE{IV{%R7B%WbeA zO;%p{p3^B_&_J~aoyCGP#^ne+v9UJO5djNdAg{m)(84Z`Nu*)5?0j!}x_zO=C>CpP2R;KgS)*TZiuOz_ zH5~nno>!yV+|F9xsuCf^$)4JhJsk=IC~w*{SIABuOu2{+zFIt(@Ug%nfcQ1j4V(21u!G9tp1j4vl0s!)8-NpOUMIFSSvL4*as8$(1ZwKjE06zc zSk3xh>5Gw#ozZ9ippl_0;134&m(B#pNlShPA|7*UdG}??$XpAGGm>aItk>B>IdU2y zctIPD$MyMzX|e0buNJtkU*5@WcY1;$xT#)@O%Bf8E_ZLwak@$1+?5~>%~+Ju3SgE2Ya3#l4ek0sR@$E$M95_w7pR1=C^Dl@u-Wl^wwT^D7|N;g zM$2iP5S#e^bY&rC8dd2=${?FWG7~E1&UZn*wn`s9+tvX+-@)p&O~yi-dSY+9Cf|-7 zLzHp-kbqpSp_m5EY0%%}k={8{279|I`B>^`BXDpnn?v-_+GR^S3Vv~&6qt^$c&Ms) zXbPEC4#N)_Er`;g_#h{m=Z*TJ^M|>1E{fA) ze!{=>uLt8l_HF)8(kp6drZ1@IVx@0w`8V=06EeMg$N^K?Y6HAVEHvS~^g>8;fHe#> z%3xwp7$w%q!u?qonWFrR*ei1ycObk`Hs--vd_8)`!%6qUjgbzWO}s21fP9^tesgHt zGzN)~pv0ylmu;YDB8O&)nmx6C0tYq4Yful>je(h7LJr4);)PhHK+$0o#Wd<7dSuvY z@7da$WZ+HwSU9>lS`y$3yaS<@3AZ{(4AY9ayG&Quz_&V>0^@wZTnE0@29dEK6(S?wHV(Szt&a*lpwLX z+(6R~xLErW^~zc4dRd&R(?tC#AF8v)kiq}-qgoG)zU-eu!TM~f{pV~1?|)+#pOnbX z+TP}~#oWvq@SnA?pKt#orN4tkrBm}aRelh7eOY1Jv@kRZ+;~dlMal}-DL@g4V_H11 z#?=DXBM?tK+iA}^|1rS`>-O&nDbl~lkqtr)sg2pXP$bSw@T*0&J-Y-nZ2~<@adyRZ zt%+})r;Iv=(};=8lq80pRyMmbG>N?t=?y^!1CpNLR()#)!^o))g`7@tJHAg}-EF>a zP>&hoF$7a!qYcLTae7MJ2S>1EQTKH=9skskuv^A~Qo=QG#om+(81is@s6~S+eiX~E zWh}p$t(K4Zwl0)rF%_8#H$~@*v^6!Oc~!<85Nf9Z$;xK`odWO%P>@CEg{L5pDAr!m zGR#KL2e8H`F8zi+DWRnS+y}|ynOCqs4C7gTKLzRkjA2OsIe$ag&h9^}cmra_WPs_B z13z-+N z(5L_ymm=QWZZ>lI^u4B*l+6#xz#XN23VJ2ls#NwhMvmBRYQdHw1)OOEWR1dFx%mm* zz&rq@G*jU(@{nixPbdO+4`xp~rwK69UfiGB(B_i!uT79@DQQ4om$_cNny>z;D!0D=qEiLjkPX($0qQ9S8-po^D=`4 zGPi{~m5;*>_XH|~T2B6EXgqV#3$gl~zNqVubEZ!l27z+6iysFG=k+Ib66LppWBdQK z3#R|ZF8-2>jJ1N1jpg4Nx=+b`ver+@n0>O=#YO051Yu!^ILoyV=mHez4zX~W6Z6!} z#d)(r;y3}Fi2(R5!4NWXk(}uPx{s9G4CoJc_s)*5&ApWY>E0E2IgG)<&&XYv+_ujh z#7>6j=0wujeM@FBE;Db9PG-5rsEHt>MC=KXvD?$fcOrXO(84}`XUyM!R(cii=}{^R z06*2;R`QWZE|}sY8QrueA&_$)5#JoM$c?LfL^RD|0J{MYQ+1_e`LXYijUx1Y-VWBd zxiDZyG@-C3rhcJBP%v#+3c#RfI9^sNBC(~*wi%)u51btX?`fBHXVIIsL|IkMltODV zp?!KV@f{IUa!j$T6}xnRa~wosvz$9{A;r#99|WlYAeFMkTi?+*c~ z^Se=eQd^?iL->(DvkEMc$dvvn!=%s52DtX`sC}AILBf?0zX?)J{iYmw_Zo=G2M4uq}D9P9;6^#CSDhKgn;06k0MKR){FlfORrO9B;2 zR(aB9%A0IP0Tb` zuQ|a?H79PlwCDq93%uBSfc-UcVQws4RVp$ue}V+l%17gk0P=_efQ?sa>H-N{dg2N_%Mzu0bfHIpqE(Q`bk)}?p>>-jm-J3vj+p|O%(rW908Yn9HTWD$z$%M@>^F3c{luUQ|{8p)ThH4nq? z?vjOXDROin6scJy=S^l0#SHqvVaY0VeZ?_lAgTTqDCM%P84K820^TIJ2?(*-Vi1hh z+C50b%jnKtkwDC-myh_x`(t(^p10+2 zU8qUlxJcmAJ(+RzB3g8QbJ1QzoDpwJ$S~ydDrAtbLiBx9Z>FmmBNikux_@O~P@GLw)n54R`CC?!ToJtu~RZwe`N1+ z{H+9k2z<<7BghFCYdW#tsSwNr;uFRokmR7r$S{b*wrvbQ5biIqT>)_^EiB717e|cd za4)^TKCwPHpS4}GHUYuOK~@W~iJh}ysvi)veb17~)#J5~VIaL9J)LhZwwi;~s9Ih? zI@dZ+V=%yVx?d`0>t@JtFR4^U4#UZ~r*+x-T8_|oaN*u`bW1Bvu35%rAaK~Xam;HZ zKT+X!xbY}N?ldK7YFeUhgOi<;(2};0^9&S~#nD;G*bxXCPSPOvgYnLxZhBn@Bqp|eke`yjai4z2z16;zdptv~A$ zf^s@VZ(HB!pPUpz%$Usc)3=iUD<}QOY@xmx;Li%6klyE18vwDOH30Be?ETm6)xV@% zpdfAiSqt=t^Jk-%iNhq{%%UcT1lBBY!mVr}&pyiD+lCnRAFOmH@C}jM-4bv9 zP9#ab8KG=>i4;1qSRD#g3a1G#y8R7sY>0hEtPq*MT<3}z-a8MXSMIyfH-G29_RGAY zEJ43et;|PyHheb1GZ6y*IVhG2pR_te&bSoG!uQN zZ)*+aPs%IE0?t#`MaYf=ud&9!@x=uq)Uw|OBu%q#a-^$->3cUEh;cb$J7-+9iDz|7 z2mm~qwG1SnY|fq|10yxwc$Q`sc{mF`f}%bMhaS~!|lhF^YJpL+}9!~p&3CiekX z6ELF;8na8HD{A0n{tI45$y-)-W8yyv!(-0suG>I055!TNuc5B$Kt~yIHE?^doM9|O z>tK0IP@3K_<~!dZCxfujHNc}*?oPijpf+>svM86UA9vU}nnn`?m|Vz|>=A0lFn?x2+K=ek>@ z>j>Y2|8YLESSmsOL8x{ARe$*Y$zA;4^yiOM#$T$WV5DbgWT#|m_xby8EmA#IMN&oh z(4uMnvMA9b>X9ukAD3OJGOrJ`Phk!O8X%x?PTJBRV2JjUwYXs=so~+RnpWfUM-nf~ z-TTMXG5gyJZwa3HAprOhB6S6OxPz0E@u#sTqdl3|+wBV;(8;w1JybUeaw&N)RiIU3 z#x$4}!A>uRQ^hxGTxrf9^K=b~h4pi}Vev*P($;MOxM&aWeJm9)b!&ZriVZ$`%Ea)@ z+lY^H<=H~Q6iR`$1RhGiVp~(D3y~Kw3b$G4uK22(KxB0zeHl<>b6;^EDvzRrTVOi< zZPMaecj=Tsd}GYOqQnj-2Dm_Pm8rTFVW9IPWbCSq#lqA={Hs^*k}Z~pFha#;1BEI< zaHo;N*Jv>kChzJGp&5uo%4y1m{R$h>5 zM>gtXaMR?(u^&k?4;+~5sTs&#B)i)N(32m%CX@3+oaW!3VjQgF48mw{hjU^nS@zF? zY{petdx1grSgfTf{W%GaPw(T%sj;|4B+=tRX}iigT@lb|1L#6cT!gGSUN>!yX$ zosh>y+Ax;?PEOg6Pf-m4Ls=z(Nq6=zHB?*V!}BTxdQI`a=d1*@5nfc~$x@`wmGp=E^qE<5Qn0&5$tL*ma@5Ojl%! z4tQeg7Q0}3V@T*L5(f^hsWVLMm{wM#O$HmaMK*fbaaJSd3$J=0Y6Vz+rq)3a9YlfnI*4GJ8Xa!8B-}ATxON6*3^I9`U{L7p3 zjMPgU^yhQir5LHKR8>!FlF?&$P!t&;Q2ey2RvRt5xFYF(o1ly$ zW5qr(k0Xr6w8Xe^lsYO!kp?79o{N>x>B;|C@qa6B<}7XS89D{;(FPSoU))DVjl<>0 zjHC$i$o38JW$JrQ_YFLTa+8aazguxomE9n5i4WBw+~&2Nm z`dJndz0^#KjIbG8Ea<`d3qAL8*=nHsBqH>_Wq)**U<){vUFnhBK_FEo6a~M4J&*?; z;t)i#SY)Aq-H!_clTwLTT?&bOF2=0NHC)vw*q=v4nN3ih**z4r>Xco$->rs((g^aH zOjd61XOS?WIRbVYHQ=;FPiO>s9%x+nbJBU|(0@E^Nvn`sfQ%ha{+?OV@V8?xovGx5 z6R1xd!vKN7bE}s7F6)5&Phn?2GeI%_m18z`l&*RCvESUf3{*{vqPM4(GH}%T^ z^{-8?Q+&2Z94c3k^RjrPhqzam zkB`Q(HK}^zl*W&)3Xq)QqQin!F|h&roPeAXAO#(Qgqo&eZ~ZZ?5Gw+JtL zNjlLC-0>=J0JQN05@D|6L!j=0hregUoW5+jCVsYmcHRPeP-7ik9(zF=E3f5%P|59W zlF)(9c!Cloco7Nlj$Y+~uNb}VRh*;eIeweje>a(Jj9S!zXq~HnhuZA=V~C_7=SUzivO*h;=X?df zt%M~uE8mzov(sYY&xCd5Glbrk43>S^oOX#EihcF!gwg>0Y!Eo!Y5Nsk?<}^UEDhYC zKB6@YaTQC75TIi*PPfBJ)}I$|2a_3_Z!gP-U)Dl42fz6g5{=GyGsFCJevfBAhK@n) zkvcM(aG*p>rWj%d|6h!qV|Qg?n5L6TMHSn&ZQHhO+o;$lwr$%^#kOtRlkT2b^JTi% zthK+KAMl=cKl{1w>w0(U%Ox@8Y3Z87MzPg=F(UIeD8g6@BFoJ6!5gp{<1t}rVDk7?|UL9*MJCvZe$GZ^4Be@df6$BO_=|E3bppMigqw<|im@*j- zrlUY(sk%EdKUhc#t_F5-O2_!PZ$_viqZGOjX4F~#GM9peUI+@?0U8nL%uWPBKs8%W zEnT`m2kgQ?uX~*uA!C>YMG%O>Yx{1en--UC*jYZ`H0fd)Tk(C>B9`Q=6~6zn?% zyDu(aFCEaoXin$G_^>#ojFQw_$lfZjbU)CGO~pp_GH3)o&uPlem}=5`{yj+aZj8!h z5Qi^hO%2}f!aYwb3F4G!*jy=$>i+MHeRNmq&8FusuTTO~A}R7II=KTTkBQ|Y=CgYq z164q8I^u9zLqxjS)R=zuUTQ+0b*l3RzsCdfNQXm6e})szQ1;T<58J^<)pqom5xcyu ztpZYkCa!I}@15f(TZ7tg&#(=77{eQTQi{`yf1X;Tk~$C?8NG*3xDiTGwT!KTiI7@^ zAoD`bTfD!IozqK(G4bt43C2r?MM5BCmfze|aa@FQ?j9z~aQG+U;3lr)*d{LNcla*% zz?J|Rxl_blXqJOH9hSuyRi2r#kHgrl<6nX?BV%}8lE51alYz$oez6)hvm!=8k1n{J z!xwB`xzmM`XctLsnFXO`^+}kg{IO%b!uysvf-2JLB5ylUaw9r>Zw!DUXL^Hox`mp~ zU|g#n$(Pi&y00c@2tSq9bS@DQ)g0pYIrg^vOAOcP=?m#V^Pi1csk(b7jWrxKk54UU z^|7v9Wls~}``;rX+!7;hqB%n10;$sm_`gDw+DNpMLv@uMFoVj6#eg2rcKo;!2QUnB$k>ILaEU zoaaxiS5@SecxG8THJbJlmN|gWBgK>KJWr(tv0-Suj%t%=(s1=mQE*rabf;Q|Vu`rY zsnorS7?ka!!tgtn6S5BneqU7}D!_}aRl9(Ojj<^Ct6>Fdg?IkoQOwPgOc3b8gzVe1`*{IR!e-gQn-{aO@ zpWCV@18jO+_@#%4;D4_}V8->}<9dK%5!UpFeW1J(`3(S6aso!=YJp0VnFD9t5IW6& zdj&^@DQ@R*q1CoMKKCk@N!PC=IOF-d>{e=q=KZe8U2RRs&6%baG1@d<)&3 zHR49AyIK*nt#t04{=o!rVwdarbVs1uWw;A|IAd;(Rr~&1{Ex4W_m1u&Lc7mbMyLmM zD0a!8!3Ku(M3$lv{iumFb3WZccb|Dyle@fXVE^GO$Q22-NtN2d#$BNUgU%x3w!RtE zZ>D(<@bE=ugQdIJTp^BA_>WuAt%aKwL1NfG&?PyCVjFB$?FPvShtJnYvVwuXYu#ecd;$5x*{cbz`hpV#+YX*FHn z7xzHP^B|PS{hi9~eDy>&=nMlJ7DXY1b(a`S^*q^8jV-1VuAAu6BvQL#{31W2lz^ZQ z0{;C_?TGj_>s%0`6~65hUyi-48A5I}L6Cg0;5CB{R4zC^(y#QI*?3RMW7o(ICAl&7F`GJp)+=tKG!;boQ_N z+({Thu`1mKcG0p?(f;U{D8V-~%)A3@p0mBP)9XqV!9=e#N(1x6Fh`ISPg;0!yhJil zwq!a2RTbG}p7I(aQP-+LMu%+boSnMPM8lR{FiIYwV%Vf^y4+0rqd);kI~9@~RvkVn{6FYCOehuVIr%_M!OPRyesr6x z=fU>`&w$z*&q~DUg^8sp!DS2Xq`ks*$t87*#|v-4FP8xcqD|Ib^B~w2fKRc-pORMo zfnaq(2*^mr*(m#|q9OLz4+X4s`wL?!hq?$UW@;qg=7RzJ-GBdx_d6IvB18q7$o>#z z$rf=S=PM+)ZHf;EOOHdc6#VN4X~$SXvMyQKsRhcB1SB$EhvGSM|OapES#6d&(|vJb7!6-#KVyoKVh50maeGP z9KwXbQ6G=fVI~%HCMAHCn(pzHhqQ$i@6%)C(D4y=Hj}8?eWOhqVo` zV!8ND7LM@=CWdboyFQ z1JR5U6hRGxaX~sUT28K-T37Gs`SP4sVrVKWltTFgf7W@wlwHbT>_h>iYU=kV=PNAa z+@5E|WMU=zl#H6Q?fnV1P-)=pDQKn;VijQ7ih#GDyJm55iO^;!*$fq1H?B0XGU<^O z7TpNlvd4-cdHy*)h(5jVHiN1$*%(Q~qP{!$gq&yxnmB z9#a>-!`m~;D;BMd)DBq`V>~tjNkdyPY^f_5VNegzv}!h?F6eh{L- zGfcs}4dmvJkfytQ_xcvDl0cM*j05ffNA$qBK>sR-*;@>K~2XM4*a@?TWVZV&SZo9~njVaK@TEXQC0D_vLSJ_nbYmq)O= zk*;5Oi}D8`L~nOFsFrM%md6IRwpyJ-yE-*wXG2s+jR*HA7}rE@$nIr=9yK>GD~QVt zwMR{k$+rauV%O%QCdBPF8%ff0*tmTD{x_x#P*8<;?rmAJutrjKy&`rjL>k;aQFBa} zR&tHqZ4hH&ro9rXc1hqVjDp6^VEi)e^3Is=$9T&h(=!i&pM5}{Vzi+0 z4+0N%r_~(q^`o&l^O5=Qv>bC?Zk9b44*)98GMyTkJJ^0alv>YfT71w1#T%7vyr6fp zEL+;SH)a?+SY_qWkUQ8$@=CfmB!N|(134YOu%WCm-@^92y?Lwqk!oO6c>?8WP;%MrK|PpL)_TJO{LVbw>|`uFm88ums|Fr zEn2REfd_}lh5=N1V@MIN!BAx~kF-z3i5%P3u>*S5n5O0~(IydUw@&3(tN?gXEbPV! zs#zMFJrHLF!{%H?KZEU2A-*)JkxH=0dTZxcWdu6}5v%CvviZ9bb4Iqe%>nE*y$Q>N z8FgryyCXCm*?J;|pHhrE23_5-V+y_V{L5w=mrg%VayBYLCl@Y@=JOZdVoawdW=f4x zn>+z4!2#xGBFzJa($kY6c_gfwJX>U(2%RMQ-}>OcZs&}|)AX*kUme0ZKAe`EZ5fe7 zix?tpYghKul`N5}pCM6k42JP9FP3~dXG5zYpm;X7tR(whtZ8QQARI5xi1)}|CRY9oNlQB*_AKkw{0BWW+O&Ym%+4vOmrM@7Y}INlsB zyQheBn}`+XVIskap&i9$vgC?~U{u0zEfegpOdK+e(9^UQx+W^uSJUK*P?$Rt@0N-gk-;yuhm)zF zyJ>kz(MNaWI-4~Wss%H;E!$l6hi>i({UHF{`c) zVrkc53$c#3apU)N$!}E|{6l~DV*dW2Y;b^*t~#S!f+O@QO|&|1vjCwr#oIwjXb(HC zWb>u?$!D>=5ySepBc>Lsx+mLtsgSn$mBBqD>Kfs5>m^Btc5`_{q;0~o`5vlF(fF{J z(s?I*B0ZJ|6<*b&FgNLK`=hq@BMP$c^}3v$Tn2+Lje!hu+z#Y%c0_6`vNiJUC`CkC z20eZ?7|gS0%-&v?O{ZRJ@5(%ZVExoSso_Q>sAv_VzLz73kglTf4l3>}hJ;AT2xQmn zQTaMH@x&$f&}kz>MtnV;&*wUrO!9drVGS92uj;G@Zi%^TJvm(xpo`3yUSV$~bzcwt z$PZ~ZKa*Yk27&+c)TgJnK6EwQe9Lz#361dN#SE?{)?)|rw*7Z9m+R0T>HCRt{~GP~vWOo+$2P7p`e#$}L92Wp5B#Q-#!lIqF?ba+dzfnkvwu?P}XzT9s~ z@@K4?QKKFaNK-BOq^tc8oy2P8_8j}E(UyoByaUsTU_64C8+?^HY2V~XyG9`Ly#{Rk zJLB6v%iJ2}4v>^EOr+}|rKapiJmoW2W)@Az=0f(nqx{_ytU z!sGK-vIlLKPO@Lh+CJrm`{&81#m*Wh%E6}DiQ@~!$iFXLo3)~uh7ccmF8ZY@C!v|% zbv8q&8CJ$h{8`gM$o)4$g}l_Jg!x0^3Uze|q!HkJWf@CwQ>;@2%?j?p(}=74d- zOk2+30Mu8Vj?_fAVs(x!f6E?#i%0v-_%>GKXbRBJfHN!*HkHGiT7%dlLPw5Nu2t+F z-!!~S2HBv2D(tX~(XSCETgo90{5Af@tc)qZfiq0a z831*n!91(*Fvo!IjLkM5=^oS_Fmgh|ElXw}rt6w@?T|Q2SRSY8J+(x*IM&qLky=F9 zC{on!w)4&6T8>U@v?+SxJ;I6+f`*xWX?_Y}i|@Sn7PDJS)GE^F;9cJ!+8RbT^4&8f za1S$4sOISH+-%gHX8mudRp>XW*gsHbf^XeD^wsRPFbPDOqDUN6&8*stliJ?3_rw4} zH4hfDCDL|V4h7BvcNr&08RwhJeIDQ4#DbSAgaGQx{53HJ&F+c}o`PhWc^x*&73SUR z2OQY_YeP&QfXxlaL=j$Fo&4x~a@!U!CJO5-g>?(qR*C(Q!iCS=FQhziC~6-xUlcZm z!03g8+P2IzMD)G2aKmRL|w1B{65j_2#q&UynZ97s=6#mb!FLf6)V0<1E)n?sIJtZ|~wCf&%hZHOL*`p-^yS zK3rAhvQd`G43(&w=rxT;IR1oNGRC8XYS$d+ruV`E9al?D3Fr~*suqKyl+4ih=TuPG zg(OD##W^_ka8Tt_Cd#Y*KjV=DNGtRe4Oc?%t-}xjBR=B|N2gOQQ@q;h=)i)JL@xzC zrPw;J0Rbs_^*$fnhc4Ew@_{{9!bRV~8t(|u%y_*hxTaQ9nwR9br&KvwPUztEtXvUf zgHag-aQjj*Lm$mUAJs#qW-!HhSXk=GFe}74X`K(`FvZgT`O-nf(*24ijGRf1+Ih-5 zk|vnWht{c*Bv@9YSMsL22qv(&N~ZgehuBf%Ds%di7h`H%VE4O_Bl?6=*lsYc8w{xd z$3$=F4e9 z>E9bE|oYUkhfHDuI5oB#$sy`ZTm*MSbu#8eD=Eep={4{qK73Lvf%0-waV}#1& zETyWJb<$;G8Hl42gqPK&mkR{KNro~3*E-ZjW!7;UgtAtzHS;LKCo=f6LHHv!0;2Ev zvFb{+#$d?@@C(`j=|c2qZfHMdXsfgZPI!2e5_~s`lmmR|RtON=Jv}vNQI+Hz!h-4& z7+mZw<*;HG>=@RyDRLJ})# zwRRB;v;#wtvxHpXN0P{O6%E}*7;ZSfeT7#4vL*R;7D3C0Gl~~UW{xFVS+v(DlqdbB zIX26vk5r|Q&@3n{mBYv<=A#)(tafE%LxgJGzdqDP?iJFmXRWIMy+v#x&p36&?Fx4~ z`Ew=|J7y1g+)Tr*8K}7?sTL6kYf^7JR#4IUGZpE`tgnQdDd6b{agIE5uZgcyjCoq^ z^U{cdDQOK&8e(1QFYAdxmF9r8%1k~vJ$3`B% zBd!+LiGHB^fkOfHq=AUj>W|svj+i~G$hn4^rz9w(aB`SZAGA0jM(vTJ2hTBvraM~S zlJ6f?+&cJt8@Hl+vcYhDRHEK%pu9NBcV-6$Xd?@InbUpexPW;b5zz)N-~LzvD&LIc zST=VAMX|->b}0Y+^-BiHlo|4UE8TD*@{yJlc1GeWsTXIve?hwY<~sOxo7?QH59`ui zDD*;yI*;R>qp|lUo6_$;b9WRDt^jFdARywO{?C6Obfx|O z4ARIOSp08jV6>9Xe+c$?<~J{!i;Cn6^K(o66KNEOY6_JJ@fhNPFr~zSkT#k@bjUke z@kn`Jxn4l-`Xj@I4fAK!KBb1;&D_ciJx&IuHoTsW-A9W6A=`63& zEl_4Fm7<#(_%1mexpbSQ4NSWXn94Upujjd@4Sf?73(rpx&*d8}?5d1clFUR$MPw7x z=BDJW*W79sAS|&%JL>vpi<*xrDf{uCTf+ zH*idLF*1=Xt(G-&RU&qAREQ3piVPf#Z|MfFQ+XEk1p}$7*j1i26MWEZp!R#e#-Oe%?ORklzb0`NrOZAE7D0ncZ zKEC0QqZw1NhdUsTrHv5}RXlpx7IMX|y&)ukyL`wr#VN$L?>ZYLJOcO(^?3MC{~0*z z${Z)0_{pcbfc{VCgyR42ocvd^Y*EY0TY2b_&&)NUV^v#R9J)QC79Zb(8ey;$G9NfV z_>RASz&}FjiBx0tV%W$3rP;Dd(z$<3`nf)&2*bN?CmX=);i!|7;(W7}zJ;;>E5jayQftki9@@Sj2Kd?EIj2_^S> z&y*gqkl1-ew79nO+|LvH^>7Jlp zWy9?DC21hGN1|M@K!G%-45+l2a6V4brNa0h^}428aW@lv8A8=IQuOLfJ~o7m>y~yx zgJSGhn32>!m~Upk`Y;2B>7!5Wq~w&vVcBqmNx-66PW3s1$_jgyJH^S3n}z}rWEh(X zHkL4=g948=fTK+AWU-i~PIKF-cjXgck-nVzzi6pUXLhCM>m~CD&~>dqk3tA-tO9pq zUY9r^$7S}dIoxH^#S>Ds>xAgT*X=4>jn3jMN8 z5-iMEe{kpR(o6xVNqNeBsM3YZ=eBSAan<@-CRM%@(ZYhsd>6$?C&UPbGzm^?l3M)t z(&=(8wT>efXSvVP1@zq{D~mYw>H4A4;4t`eh=6`m%Lsq#k_>0CtWjtC6u&Bxe+PgXh+!^; zh@sD2c5#_3R%c^WLtBU=>V%I*ux7M|y{Ar+RWK_8FzNbI991;H+d$owIC2FqLx(Qp zKN5#B#!W6&jSkWfO)YPW^0Hx(EOknA>FHbsod^1ysp2NEVCgeL^NCX6UQnJhS^c+t z#1SzJlrK&jmXpAUn}B?6ikrV|f`TM^6$~91^}rNw+6<1JN#PVrf!Ccuq7*FaIr0 z_ioRuray@!)sfJ{tmX6cr$TSZrzfGOckNwyGv!&q)5%TF`cg-d zR`ohbV%)1B)sbV2JM#ydY*dhbDh?kuj<>_Sq0A>D{g zImz7&J$4K?6r%8zMEge8Y?g=eN5nWCPWmlH+Q@muATaU^Tr_SnTCOHilxX&}$;L+C zy&DBq6PVy8P6BWKTQHCKtS(=`Ph9AOg7z&a+=zB<+}YiVc=u8g$Y@Z&>rVlO+Pq2qu-A9TT!KOi*`wy;*hFdS)3(maFmiup1V|qkkJ=L?NHXGHJ%etVZ zcICw(adWsitAVef{Qxyq34b`W0k|6hX_NP4Zp7Gsp5N)QH$Sx5_ocdoD*C;U*y>jF z@H_QULq>=CO+4uEuh$p(1UMuKKtK&tP-USpG_QBhQ}KM0zSs(!SH>994*Uv~_|!z> zdxN6`FgK(FA|7xCc4{u+)E)(pU^XYYAs(-#%Pn)E;5+=^gSb9cD-CxW_Ig|``C(>T zi;}~ng)Oh8#Fe-${=o>30e^{e?JR|!5fsvQ^fE_UZSfSo>`gK=m2-kFZExhx%C6(h z*|5~kupT}{dUw+KmoJBSU6F!t+Y_yjy@>!H(@q4RviMem=#_sCqK-pmv0)MHiP5HuMeV1Ad6OrVK2PfXX-JgcN(ITJAs8>SiXQAtQMB-Vn6P zw~PrXRt|=zI(vNnP$djF^~@ScI5H!}uIeLHCSJ6T_ov>OeP;ObAKDsueHBXJ{9&@^ z8BEFpfX?M5y45e>$}sqKY1lBH!!<#M)=#FK66(5-(*!s)BCbbMTJG{~&;2(S#uLYe zq4AyfS9Y{e#@{ULV6F^LrumbfZ?gCbo-hnJZe#=YXhdB&J;JED+ZL6#U~gF*;wT7C z3kT>UByasx#fRYSrx?A9*%0SyNs$Gr_$whK3qalOS)=C|6lLSdmOMe64r|?^1cp2J z`hlJp@WRB_9(5Ph;tON-`Ra=CwB103^HR&DC{3IJ`FNg(u57EPk<#7jjLK+)C#46a zV0xT83$?S`JZ-XDhxa7jX`?Apy%eF$ppaDRG#VO zELY)U-lwacp5%^o4)3cP@)@Mvz{buGMwTst+lyw(tO&>pQJw_F@^9?tkjE-t+D$O^ zM5PvNVZqjh&JM*e+vkgvw}ESXzJxpHmr)?qx7JLg(5kC#ENv}qtaX zdXDDRVrkv^k8Ur9)`ZdT(Iy|bw9&|Uy!#~J#m|p>qi^n+7p_MPvmwXj&%<2dNc)H= z!^S~x!OF{YsRQ|DAl+UGJKD0@?c>BJ)i~U%e*G=oCi>h(2QzkEN^eNIPQYU?A5lVr zjbhpCjLczvrSQePC2%b!ncbx6Fe1p~FO3+Kr7Jb{C22!W6ak`}|B@^ zr~Rtxyz4->u*RanMf0v$C9>Jy$`#)U@-jol7*uHIw93D^QhR#LWdb+$_0x28-6ZmNSAWIh8`>i%n&_;HtQHIYD5m?LBpJs3Faqu?N~x! zbd}>MSr-I~DHX(V;c!hcM|}eFTFvKf-y^L;OjM3I2bFPM$S)dr(3_`kz;b-qthjDM zYZ*&0LwEED_A?pgk{}WL+{6fOA;+vr!;8}kW6Yv zWf&HfU5NF`kMztSAFiG;^Z1Yzis;LS`uwGmK+=!EE|sB z@`~1CDIXnT$*9tx37vr9os-BbkdV-^@bd~H?fK+mb~fWqh%`|a@tlCcukN( z4o-f+@dh{FFOXx8P0vx>mNOX^sw-V=rF|bKS8i1xqv3HnL3mGWIzVt z6m1&(w6V%79cAHA)%y2N)i95Bb)a_A|IJQfKSY!ZAVb%6bzaU@dO6Lx<#g#w>&G`#C z{j) zVv$NFs=z`(p?!iE2nualmR>`%`-qRDkz(GtRYAUxjW^3=bDR*BQ0F+o{NTKy30Z{n zsAJ?);HcMvp>Bywu)S#*Fc5qX_&jUcwv7bs_tQ5~C4KkLo9Q?%sf%;{cAVoNl*=J~ z%V!bk==2h%qJ{Smk(}3`&+7uTn>MT77GQ-gnNOh~K~f!i$OCs>;+j+y^Pku}EXdrH ziYF3qZ1SNVH;t?vg^24}XS=S6wtzu~kW5aybEMjdAzzhqL0c8}6#;_$7Y(y;P#cG; zfC@6`$HKB~ML9TeiFhNdhphleKd(2^E~9GZFq5Z#+&9xxUJ+gEsS~6ozSlaW-DYS;v`I@L9?CDYvr;!KS~2 zFKNQZhVpX-_@BiJ$6IbfVu!M2{Z+xq3b;bR0Z~`DJ@qUN(pX!m243rJ@LHIfQdQ4x|Gepm#8hhk2sLdtk=5O9Qu*$_a%{{rcf8t z&J(AW+H1X%TXA@zT!ea-WWi{G=7M5k3PPOX_^EtT0IuBPtLB2-d5sb{4kccAzBci9 z6VPRq=Z540+<0k?pbw#p@5{>Wi9EJLYqb9$V zIYDsUPGs%DcxhU%>x02R-l;&rrE457I;uU3)Q~4EjE^Y!s zYy*OCr3tuw8G3V?Z)Cwu|I8F%hlQ2l1EvrRii5v>HD9jDg6WDH7BwfZ1y_YUyvzdx z)UNX=16ff$_(-QmR#r|!^=G^rYeB$o#ZxEQ$B*yI%DhZ+|G>8qqt)=tTzwn01wAo|~CnkFd5LKCi(65?M`cL(O> zIoYeZXu2BJ%bWKW2|9f30Dyw>()h@pp38K}i0$9A4dHcjaKKR=cal`yb`iHuHgj^z z%>cJ1U(O!J!Gh)cZ=B$5@-}j?CK({HrFjHr+R0oP`H}mR-<&zJ#Zcx9@Mb(Z(kXK! z&LG)rz&=;GTw^9B&jtgvqwo#;=xRPqd5h^OjD$<_d z7RxGtx%wSbK^`?O5V=}oNC_*%8&IPJ*#xV~97v~dJ}dVZO=-Gsrx3l!X^8&SWnFy{ z%wADc*2}lG65C5vXRaYXF{986_d;>vSrAjH7A-f{NWNPj!=MkW%as0~=g2gR`@_S) zbH4&+;5Oz8BCGqdF7UPh9ZWWH%=F-oW|Tli-pia|>k5UQUyqPr!}=zw&i{+;#7zRc z4kXewqJZ3va$6=L;x$N@x2`iqZjM#?ch>p7hcsJ)!UR;h;-U;Q*bt*$aL61kCQXwg zR>k|R1NRHDC&bAkeR2azw=b7+4cwFs+2TBrC(`)E>f&n{nRp6qZ*ncIP8h2L7u2H2 zgCpYW#=(k^5`MVQM(*n`ofPd6>8RN$$=vD`w5=Rh=eBmAlMgJ#w`P(q7HyuT#?%4!SGU}+4}G#%;8!Wiz=~0ULheUl#EI3))0@^_LBIx)UElt zOwmufRlEqG)s$j=eyGBuY>xG)(Hs{kVh0=w{1QelJ(Pw($qnPQDFZ&s6uZ~pOvnSC z>i1(TBm7z6n`3-J6!=Cs@ClXPFp8X@px@HzH`Wvvc@q*AM&`}HhPN${oGDHq&z66J z(j-YSB5NKPKetnt)ejwf1W`h_W3N3T;H=r+q>)-1LAI>?Qoi;VxMlejY+GptV;@Vy zj>sudQ&*r(*Oi|Vc)0fNbuc?_cHUxDl+VRaF4JB%UJ{%?9(R|xA*q#-9V_+<%Ntq> zK>Bk>HlI7j&;MO$%TsZQYYRkvRvl7@h`cl0{@yOdvk5Z0LE^%dAlxWQLintEx z{kQA`m-3dq$T9fh-pl8MM1;!osXMGzlq>ve7fNa1wChYeO;}U_lWNSJOmALuV%gJ? z(tEfR13H^`L)QE_2Kns^rS0&ep2Js(#XK*i<`_+|fbj2Bm88J7_j_#<#t8Jt$>pI0lW135I z&xf`T9WHuL=+>Kqr$1}Xyps_|Xx|=lV81MgOg?*~+eoLvB{8lbaBVEB zv&n_b4k`S>WEhxwwN4js}l~5-L zt6L3U-5f+#FhUGrA<5}MQFwb+appPcS=3H&Do42}npGB|svPfB^8s#uQT`EZBL_JFk7m9yM2Q>872azNpb(jkfG zjfS@^)h#8d)%1+LwHCduS!Y$)WBs)aH(Pyz&b0em;pjc*fu3+!jdA;%`_tEX^^jJy zC@=H}XRF4<)x}zqB-H-9bT zwac@hJz;0&`s!qg1_4H|rZ91B;`(aUE+r73WN8L>D9B&>=-u8Gs=Y|NfPSK-9S;=~ z`IL_G~Fz{gSl0DLvk*l?Mbt!41Mhox? z6`Wft87BY0o*;$^6=+&~(HT?78tLvBRk>QPCUqLxWG+{i3A|Iwr97${8+%s{4sUOaq~qQxACpO$Hl2<>r_NZ(@glDah%5gj;}qGa*V2Ufq-<$|ZvkQt2o0GJ0^L*prA(2hHKhsBlM`5;beXLhxh(id?+0 zbt2@CP^*i*TETp+Sb3(&X0*6Nw+@kjC&}s}t>W+|4lVqT9kbzjs{p-)v4u-G~m3A>xZjv_o^>z=viCXR^PdvzS`rK~Qyzy#|!09c)-(MMY2d0f4 z#y9$E3?FJZ278WaeF4+Ea2(~>*h;p8YoPW^;00Ivk89>NAijw=gpV*y$aU?Y6wYNC z@}B<1QQxEL00NhI9UJj4mCp2u_R4F5r^<{464y;qeIv zN(FWV22**zSOk22?pI31{RJIz3~wSEMYjqs`gI-WrZa_S!$ST+8D8bOUm+t&^_a%a1(a8IT$nl{Dd> zdGcbwP0}C^s+%lW#L_8GOI9dB+y{G!x^Mj?%hJ_q6_zE`JcIML?{6Pni^=bvf+lN7 z+SJU6vZ?Z_mX50S4yy!Y`weUl~!fI>5IK5WJ<$#E`0FkVnaZ z3lxDyw>_paql?{H&ZmDa4|UGoEyR?|-;oYc4(-$;ar0O3w}l$`572+3H{`;=om$|k zYCUQ?#jq%e+ z$lNJ(d*W0o@4i}bv+ccN#eYMl*Z5Yru<3OH^Ep6fRr#i5j4YRKSMc!{V1Lj(E=k}% ziaj>JF?oFbXJL+V-E8#tj|ApiIprvH!ss7{?MpMZ~95SB57EBi%KyN16#D&mH^`S5mKD3~p-ly7@FUFRE6_X<98d zFPQn%@t5u-lt1dc*RMTYt!2A)SzT9mZ9P_3eSVsnvNdFE^84St#-_dUb$?%9e|t^& zJw5io;sV)|eoh4bLZ&2FWB?lqx~p1aoy42X@A_)m)OyU7AlF5W37I?$>OV`D1tkf> zw36?K7W)t&!jv8~^jvtbDxSrIeZ#7fN-z+1raAlj3&0N`LH>!0oKlNm3{#-jzHE7r=Y*)z5pPk_x9o(|BP3p#8_b?;GoD#sv_Dh5EqBPKF$TI{PU%O}0PhqT^3a=x^LF_)9@i!Y{ zYSUbrQe$7*lw$CT3#fGKm~JWHBC_T?@Y&T1%?CT4o4P!Z0y35+iGrSQPnc>2y7w<@;j1SRK0rX)Fq7Lzm^glD~6ZDzO%!mIppHzvm zQ-*YjX1b&77Uo4;hNpW{C!^Cp&Wzy*`0x`WogJ5&_hA;}s(1W!&F&#}BR#q28q6#B z^dFwi7@BW^GD@KOT70C~^WU4T3=-V7myHI!n-6%Mq@83->;sib2@>A$3QcOJtxN3C;N+O`_4MZQ#YEg+2Il9w4v(+!35IdR#^jXVS zh`DqyWk(5%?du{Y=QyYCzWLd0&lGeu`$&QyQDs+dS}JAiG6HOyw=>7)l#KS0r{^l)Ya39W&WcgY*(i7DnS8=dU$Hg%KijQT>6#<5o_=bPW)a5L%k4y*c7T-0Ts;wF zu)tx&%I_m?(k!k6Q(zaiw+qP}nwr!hTw*Rti+qP}nwyV0T z`_#tFoQv5LCnh5ECht~cUaVZ-yPl_wKBYv9Po>f&2604nP&J{ST{zNe9eQflS0aXN zT?n(Q!)xjE6L4jHiLsor?+nkcM&a#9R;%pT5@mB=0ee$=&&%XXSP1qqY9zEcfX^k# zEvJuItK!6GZy5O5!rzl2(rK=b$li93XTwh|1Oq( zI%-Zzm-2+&`*%;amxH<4Wbr#?kCTVF4shVYO3L)RrU9 z!7qK1J;A}COQrrze)sSwo)NfFO?$#vr?x2X;*vpYt4MaMf!Jnhoj*z{xUp(Ui19HJ zZ9B7UuPG&V@NbV3<1o(U(>wihkPz<1vkmF~=%LjevcV@h@wL+7E`x@RGP6lEdu;cb z<^4u-chg+zD?5bi&fv@WO^mbvx@oqtq=&cq+@|(};(9JoWPOx`JW5-LIc&&c} zV1FUoBY)3s-&J`SoL1syi&R7w{vn70{u?px`s*c9rY>g3$qzcy>RWquCxGXNeJr4M zUl0{{&obC(!i4UQ%f9;bqA!)Ad*e)h(QYefvE!+7FpaFGT%p$aEj%J1nR`2$vgf&)<9n{SJHh^pj=khkGPUGOP?o6~K^D8>sGxZH! zOB?B-9CX9?i4K>!CkUw=wI?x((wNjy)C7butod4f_=>w$+(lItM}II>82Krrb2y_* zJ6$Ks{c}L`AO1HEfP*kf!fW~0jYi1Eejh6_f^Y(bmvPBPSifl^K&8M(^xD`+1 zdncj;H7gLgX)$7wc#C*PiANLigAvTk>P6Jd$=O;kvM)ps2{pi(QfdU3UI~imL6Ye7 zD1L0899G5=1FJ|$k|lW=iH+zWD{fXiMsUoSS(QZvYZmoM4-J|^u@_-BS5!JNOJl;6 zWvsM|6~4=v;7+d6ZZkgAD-s4Ler17n=%s39*+&RVY$lt9y+syYE=LT#L7ePUdTWx{ zb5*kpHP=rV>>kEaWHYV=jXiEFB`Mb)yToUPv2aIo*_J61~gd7yQ)Rg>_lTs-x}r_4A8mwx_Ue zzoKA?y0yv6BTv-8s1A=a(GkCeq9vZu!-3^SN5cm9kXypx+i?^Tk?$*39pdGFRMe*C zl4j{VSjAK7^a$Uw6prd9rp4Sbnon=&AyBsNX`E`Nk z{xvA$b*;}umzXwlErJ#siJ@tMchvdWg6F)29V=?;GEy-%)}&&*2|#Ykzbji^U82bk6A~$E9k;oA&pBFGutg1bzNYC6eo`B#RV@D=jh%l z?~I=7dON!#!y>iw)mU2e>JHvYEhA63Qr;Ykt6Ki|Tc?P|%3!tjV5Fy~HI#5*N9aGv z^4w$>Sc(XA(plRwNu>9Z-JhJ)TsRKKc~KVu;qZ%3&^`HwUS0``Y_^?rvEL)4 zdb`_CZ+jRP@1?k*Tae7D^HX18EZefDDqMVnkA51({%R%R7YWE-IgE3+uVNkBO|&`) zAFQ5DuL*T~ll|P@PL%q>4v43&(ts`PgP)-XI>InJ)NZo3NK(}$A1i;mJWYbA&#ZMbdP0iK-%XrBRBYb44o!UX0% zuCr{a{A@bGOfY7YKuQD#Wm6iKK>8m!Kn5_!!9UxKvCvnZ1mHDSE}VvX29C81M`%}{ z)cQ@e4*POfpYXe8*M1#klmdNuFT62pzem4CCP2930k!gPqq?8^=W8DJDB<%ko z?zKPK(Tg>P@TenMtK76GKqqsu4&$9WY!;zqHb~T@PK)HEi5%5S$MnFi>N&d=u>lro zrf4Wgbw6q@OMkW{zZ;WA)xJhIr2VHiajkAdtbX(>dx&K9KAj!kjUJsipu}=yLvyNV zRz{{C@g!TF-h}tmd`dX&$rlP54vc0Bp!}E*Fgvt7Iln+h)qYfs->>E0x_?+fv!H*V3Jc5lE+nO~%xVhYsK zw`!<%ZctUMS1+g{cO2uhvTkE;5$>hmiHn763622rW+WW8PK@ww^Jb7aj9Tsd6w-wu zbuz)LIx6iH^3l)j)l3^s3rn^~*^Y8LAA)^0`LUyxfa|OR>8zHZ3+44>nm`^YDy+?y zr7yrv=i6+M4iD50L60H!4rFN!uZ?sY{bnkoV#york=pJ( zqu$Z%O_0F8;ElwfnX&rEc9&;g+P)`~?*;p|E6}@646Rm#u6B%gJDptXBmVt(1*#*DS7AR<6wqDT^%6952VfvIRJWy@EpKZauQ1RYz^z{^hzyG0MH5 z{GJKl+=aTel-IDPW^+l8CRS*my6L2AQaAIg`mn7K>^IYKF2$0#@e%LkAlA_}{X)#M zmBphoMKkK!>dk2Gb$eshnoQ@Rw3D{L$TL=Km+&z;yb4$StA$C=xF*BNz-RR^rTB-g zeRc4LrUT0=k7qrBj&TvovJ}z2Z2eFj&WzHao8DtxY)ag=m|(da}%04Kbh zuq;a``%)H19J(=IS9JX1rU%HHV>O(IUu;J4cX>tTxcO)vIT_swbkm0TehN0Ubo9mq zK8R$`gnYx9-a%~L!M(G;A*#C^#vR({W%im`r@UN8Ed*s=IL2tKTS= zKcUF8ghEfY=j07<^Nf8>P|dB=wxcdg!W_5sGxgh#O_xR1;RaDY_H_C-R{*I?7s!Os z8u)bqJ&F{BmglQ5rpUW#9X2PQ<66n3mwT?QVUs7+#zOky&DV>F8Odt1J_wz_EctUp`tn9ZW zg1`Q8;r~f|`GUqf{P*~k`SRwiewFo%-XnAO(0%ds?CUy@5G8XF{vP1x^UC@O^_|Q= z*!*9*zj4P~zzGr%(B03b(*KV&CKCsHb1RGgL`ti8y8MTG?!VPWF^N+@KG`2zh13#h z$a7-rJj^^uRVz%jJ&c&MokpbNA?1KEDZX-?FoeGtk=(^xr+H zOAWkDI~MEp|74G(a6^*bU}?lrD@8*l6lGPkhM+}U8o2IuW=)E3wSS1*ZcEoX*=zOE zDsorC1naj3PdKm=R+v8gIg!VovRxPr=w%0A@RvelkFz9eDhd8H7oR*&Ld%=(odD^q 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Ⓢa-LCqNy9Qpfh~AY3Xmh!Fbq2BPgne?k*Be- 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 literal 0 HcmV?d00001 diff --git a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt index 4d82362c..1fc7ff57 100644 --- a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt +++ b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt @@ -18,20 +18,13 @@ package org.asteroidos.sync.dbus import android.content.Context -import android.media.AudioManager -import android.os.Build import android.os.Handler -import android.os.HandlerThread -import android.os.Message -import android.util.Log import androidx.media3.common.MediaItem import androidx.media3.common.Player.* import com.google.common.collect.Lists import com.google.common.hash.Hashing -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext import org.asteroidos.sync.media.IMediaService import org.asteroidos.sync.media.MediaSupervisor import org.freedesktop.dbus.DBusPath @@ -42,14 +35,17 @@ import org.mpris.MediaPlayer2 import org.mpris.mediaplayer2.Player import java.nio.charset.Charset import java.util.Collections +import java.util.Date class MediaService(private val mCtx: Context, private val supervisor: MediaSupervisor, private val connectionProvider: IDBusConnectionProvider) : IMediaService, MediaPlayer2, Player { private val mNReceiver: NotificationService.NotificationReceiver? = null private val hashing = Hashing.goodFastHash(64) + private val busSuffix = Hashing.murmur3_32_fixed(42).hashLong(Date().time).toString() + override fun sync() { connectionProvider.acquireDBusConnection { connection: DBusConnection -> - connection.requestBusName("org.mpris.MediaPlayer2.AsteroidOSSync") + connection.requestBusName("org.mpris.MediaPlayer2.x$busSuffix") connection.exportObject("/org/mpris/MediaPlayer2", this@MediaService) } } @@ -57,13 +53,13 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper override fun unsync() { connectionProvider.acquireDBusConnection { connection: DBusConnection -> connection.unExportObject("/org/mpris/MediaPlayer2") - connection.releaseBusName("org.mpris.MediaPlayer2.AsteroidOSSync") + connection.releaseBusName("org.mpris.MediaPlayer2.x$busSuffix") } } override fun onReset() { connectionProvider.acquireDBusConnection { connection: DBusConnection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Metadata", Variant(metadata)) as Map>?, Collections.emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Metadata", Variant(metadata, "a{sv}")) as Map>?, emptyList())) } } @@ -95,79 +91,78 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper return "Android" } - override fun getSupportedUriSchemes(): List { - return emptyList() - } + override fun getSupportedUriSchemes(): List = emptyList() - override fun getSupportedMimeTypes(): List { - return emptyList() - } + override fun getSupportedMimeTypes(): List = emptyList() override fun Raise() {} override fun Quit() {} override fun getPlaybackStatus(): String { - var status = "Stopped" - val controller = supervisor.mediaController - if (controller != null) { - runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { - if (controller.isPlaying) status = "Playing" else if (controller.playbackState == STATE_READY && !controller.playWhenReady) status = "Paused" - } + val controller = supervisor.mediaController ?: return "Stopped" + return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isPlaying) return@runBlocking "Playing" else if (controller.playbackState == STATE_READY && !controller.playWhenReady) return@runBlocking "Paused" else return@runBlocking "Stopped" } - Log.i("MediaService", "Status: $status") - return status } override fun getLoopStatus(): String { - val controller = supervisor.mediaController - return if (controller != null) { - Handler(controller.applicationLooper).run { - when (controller.repeatMode) { - REPEAT_MODE_ALL -> "Playlist" - REPEAT_MODE_ONE -> "Track" - REPEAT_MODE_OFF -> "None" - else -> throw IllegalStateException("Unexpected value: " + controller.repeatMode) - } + val controller = supervisor.mediaController ?: return "None" + return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + return@runBlocking when (controller.repeatMode) { + REPEAT_MODE_ALL -> "Playlist" + REPEAT_MODE_ONE -> "Track" + REPEAT_MODE_OFF -> "None" + else -> throw IllegalStateException("Unexpected value: ${controller.repeatMode}") } - } else "None" + } } override fun setLoopStatus(_property: String) { - val controller = supervisor.mediaController - if (controller != null - && controller.isCommandAvailable(COMMAND_SET_REPEAT_MODE)) { - Handler(controller.applicationLooper).run { - when (_property) { - "None" -> controller.repeatMode = REPEAT_MODE_OFF - "Track" -> controller.repeatMode = REPEAT_MODE_ONE - "Playlist" -> controller.repeatMode = REPEAT_MODE_ALL + val controller = supervisor.mediaController ?: return + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_SET_REPEAT_MODE)) { + controller.repeatMode = when (_property) { + "None" -> REPEAT_MODE_OFF + "Track" -> REPEAT_MODE_ONE + "Playlist" -> REPEAT_MODE_ALL + else -> throw IllegalStateException("Unexpected value: $_property") } } } } override fun getRate(): Double { - val controller = supervisor.mediaController - return controller?.playbackParameters?.speed?.toDouble() ?: 1.0 + val controller = supervisor.mediaController ?: return 1.0 + return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + return@runBlocking controller.playbackParameters.speed.toDouble() + } } override fun setRate(_property: Double) { - val controller = supervisor.mediaController - if (controller != null - && controller.isCommandAvailable(COMMAND_SET_SPEED_AND_PITCH)) { - controller.setPlaybackSpeed(_property.toFloat()) + val controller = supervisor.mediaController ?: return + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_SET_SPEED_AND_PITCH)) { + controller.setPlaybackSpeed(_property.toFloat()) + } } } override fun isShuffle(): Boolean { - val controller = supervisor.mediaController - return controller?.shuffleModeEnabled ?: false + val controller = supervisor.mediaController ?: return false + return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)) { + return@runBlocking controller.shuffleModeEnabled + } else { + return@runBlocking false + } + } } override fun setShuffle(_property: Boolean) { - val controller = supervisor.mediaController - if (controller != null - && controller.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)) { - controller.shuffleModeEnabled = _property + val controller = supervisor.mediaController ?: return + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)) { + controller.shuffleModeEnabled = _property + } } } @@ -178,34 +173,35 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper return DBusPath("/" + packageName.replace('.', '/') + "/" + mediaId) } + private val currentMediaIdObjectPath get() = mediaToPath(supervisor.mediaController?.connectedToken?.packageName ?: "", supervisor.mediaController?.currentMediaItem) + override fun getMetadata(): Map> { - val controller = supervisor.mediaController - if (controller == null || controller.currentMediaItem == null) return Collections.singletonMap>("mpris:trackid", Variant(DBusPath("/org/mpris/MediaPlayer2/TrackList/NoTrack"))) - var result: Map> - runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { - result = java.util.Map.of( - "mpris:trackid", Variant(currentMediaIdObjectPath), - "mpris:length", Variant(controller.contentDuration * 1000) - ) + val dummy = Collections.singletonMap>("mpris:trackid", Variant(DBusPath("/org/mpris/MediaPlayer2/TrackList/NoTrack"))) + + val controller = supervisor.mediaController ?: return dummy + return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.currentMediaItem != null) { + return@runBlocking java.util.Map.of( + "mpris:trackid", Variant(currentMediaIdObjectPath), + "mpris:length", Variant(controller.contentDuration * 1000) + ) + } else { + return@runBlocking dummy + } } - return result } - private val currentMediaIdObjectPath get() = mediaToPath(supervisor.mediaController?.connectedToken?.packageName ?: "", supervisor.mediaController?.currentMediaItem) - override fun getVolume(): Double { // TODO:XXX: - var result: Double val controller = supervisor.mediaController ?: return 0.0 - runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { - result = controller.volume.toDouble() + return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + return@runBlocking controller.volume.toDouble() } - return result } override fun setVolume(_property: Double) { - val controller = supervisor.mediaController - if (controller != null) { + val controller = supervisor.mediaController ?: return + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_SET_VOLUME)) { controller.volume = _property.toFloat() } @@ -213,10 +209,14 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } override fun getPosition(): Long { - val controller = supervisor.mediaController - return if (controller != null && controller.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)) { - controller.currentPosition * 1000L - } else 0L + val controller = supervisor.mediaController ?: return 0L + return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)) { + return@runBlocking controller.currentPosition * 1000L + } else { + return@runBlocking 0L + } + } } override fun getMinimumRate(): Double { @@ -229,113 +229,116 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper override fun canGoNext(): Boolean { val controller = supervisor.mediaController ?: return false - var result: Boolean - runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { - result = controller.isCommandAvailable(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) && controller.hasNextMediaItem() + return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + return@runBlocking controller.isCommandAvailable(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) && controller.hasNextMediaItem() } - return result } override fun canGoPrevious(): Boolean { val controller = supervisor.mediaController ?: return false - var result: Boolean - runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { - result = controller.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) && controller.hasPreviousMediaItem() + return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + return@runBlocking controller.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) && controller.hasPreviousMediaItem() } - return result } override fun canPlay(): Boolean { val controller = supervisor.mediaController ?: return false - var result: Boolean - runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { - result = controller.isCommandAvailable(COMMAND_PLAY_PAUSE) + return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + return@runBlocking controller.isCommandAvailable(COMMAND_PLAY_PAUSE) } - return result } override fun canPause(): Boolean { val controller = supervisor.mediaController ?: return false - var result: Boolean - runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { - result = controller.isCommandAvailable(COMMAND_PLAY_PAUSE) + return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + return@runBlocking controller.isCommandAvailable(COMMAND_PLAY_PAUSE) } - return result } override fun canSeek(): Boolean { val controller = supervisor.mediaController ?: return false - var result: Boolean - runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { - result = controller.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) - && controller.isCurrentMediaItemSeekable + return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + return@runBlocking controller.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) && controller.isCurrentMediaItemSeekable } - return result } - override fun canControl(): Boolean = true + override fun canControl(): Boolean = supervisor.mediaController != null override fun Next() { - val controller = supervisor.mediaController - if (controller != null && controller.isCommandAvailable(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)) { - controller.seekToNextMediaItem() + val controller = supervisor.mediaController ?: return + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)) { + controller.seekToNextMediaItem() + } } } override fun Previous() { - val controller = supervisor.mediaController - if (controller != null && controller.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)) { - controller.seekToPreviousMediaItem() + val controller = supervisor.mediaController ?: return + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)) { + controller.seekToPreviousMediaItem() + } } } override fun Pause() { - val controller = supervisor.mediaController - if (controller != null && controller.isCommandAvailable(COMMAND_PLAY_PAUSE)) { - controller.pause() + val controller = supervisor.mediaController ?: return + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_PLAY_PAUSE)) { + controller.pause() + } } } override fun PlayPause() { - val controller = supervisor.mediaController - if (controller != null && controller.isCommandAvailable(COMMAND_PLAY_PAUSE)) { - if (controller.isPlaying) { - controller.pause() - } else { - controller.play() + val controller = supervisor.mediaController ?: return + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_PLAY_PAUSE)) { + if (controller.isPlaying) { + controller.pause() + } else { + controller.play() + } } } - Log.i("MediaService", "PlayPause: ${controller != null}, ${controller?.isCommandAvailable(COMMAND_PLAY_PAUSE) ?: false}") } override fun Stop() { - val controller = supervisor.mediaController - if (controller != null && controller.isCommandAvailable(COMMAND_STOP)) { - controller.stop() + val controller = supervisor.mediaController ?: return + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_STOP)) { + controller.stop() + } else { + controller.pause() + } } } override fun Play() { - val controller = supervisor.mediaController - if (controller != null && controller.isCommandAvailable(COMMAND_PLAY_PAUSE)) { - controller.play() + val controller = supervisor.mediaController ?: return + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_PLAY_PAUSE)) { + controller.play() + } } - Log.i("MediaService", "Play: ${controller != null}, ${controller?.isCommandAvailable(COMMAND_PLAY_PAUSE) ?: false}") } override fun Seek(Offset: Long) { - val controller = supervisor.mediaController - if (controller != null && controller.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) - && controller.isCurrentMediaItemSeekable) { - controller.seekTo(controller.currentPosition + Offset / 1000L) + val controller = supervisor.mediaController ?: return + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)) { + controller.seekTo(controller.currentPosition + Offset / 1000L) + } } } override fun SetPosition(TrackId: DBusPath, Position: Long) { - val controller = supervisor.mediaController - if (controller != null && controller.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) - && controller.isCurrentMediaItemSeekable) { - controller.seekTo(Position / 1000L) + val controller = supervisor.mediaController ?: return + runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)) { + controller.seekTo(Position / 1000L) + } } } @@ -353,31 +356,31 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper override fun onIsPlayingChanged(isPlaying: Boolean) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(playbackStatus)) as Map>?, Collections.emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(playbackStatus)) as Map>?, emptyList())) } } override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(playbackStatus)) as Map>?, Collections.emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(playbackStatus)) as Map>?, emptyList())) } } override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Metadata", Variant(metadata)) as Map>?, Collections.emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Metadata", Variant(metadata, "a{sv}")) as Map>?, emptyList())) } } override fun onPlaybackStateChanged(playbackState: Int) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(playbackStatus)) as Map>?, Collections.emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(playbackStatus)) as Map>?, emptyList())) } } override fun onVolumeChanged(volume: Float) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Volume", Variant(playbackStatus)) as Map>?, Collections.emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Volume", Variant(playbackStatus)) as Map>?, emptyList())) } } @@ -391,7 +394,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper "CanSeek", Variant(canSeek()), "CanControl", Variant(canControl()), ) - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", map, Collections.emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", map, emptyList())) } } } \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt b/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt index 39f031d4..11f3034d 100644 --- a/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt +++ b/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt @@ -24,7 +24,6 @@ import android.content.IntentFilter import android.util.Log import com.google.common.collect.BiMap import com.google.common.collect.HashBiMap -import com.google.common.hash.Hashing import org.asteroidos.sync.NotificationPreferences import org.asteroidos.sync.NotificationPreferences.NotificationOption import org.asteroidos.sync.services.INotificationHandler @@ -131,8 +130,4 @@ class NotificationService(private val mCtx: Context, private val connectionProvi postNotification(context, intent) } } - - companion object { - private val murmur32 = Hashing.murmur3_32_fixed(0) - } } \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/media/IMediaService.kt b/app/src/main/java/org/asteroidos/sync/media/IMediaService.kt index a179adb8..67837e8a 100644 --- a/app/src/main/java/org/asteroidos/sync/media/IMediaService.kt +++ b/app/src/main/java/org/asteroidos/sync/media/IMediaService.kt @@ -19,7 +19,10 @@ package org.asteroidos.sync.media import androidx.media3.common.Player import org.asteroidos.sync.connectivity.IService +import org.freedesktop.dbus.annotations.DBusIgnore interface IMediaService : IService, Player.Listener { + @DBusIgnore + fun onReset() } \ No newline at end of file diff --git a/app/src/main/java/org/mpris/mediaplayer2/Player.java b/app/src/main/java/org/mpris/mediaplayer2/Player.java index d9f0fbda..40a824e1 100644 --- a/app/src/main/java/org/mpris/mediaplayer2/Player.java +++ b/app/src/main/java/org/mpris/mediaplayer2/Player.java @@ -39,7 +39,7 @@ public interface Player extends DBusInterface { public void setShuffle(boolean _property); @DBusBoundProperty(name = "Metadata", access = Access.READ) - public Map getMetadata(); + public Map> getMetadata(); @DBusBoundProperty(name = "Volume", access = Access.READ) public double getVolume(); From b92560cc1e1d77c95e5aae4f7454b9c10e6ac686 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Sat, 4 May 2024 07:52:58 +0200 Subject: [PATCH 05/10] Replace bound properties with manual property handling --- .../org/asteroidos/sync/dbus/MediaService.kt | 143 +++++++++++++----- app/src/main/java/org/mpris/MediaPlayer2.java | 40 ++--- .../java/org/mpris/mediaplayer2/Player.java | 72 ++------- 3 files changed, 133 insertions(+), 122 deletions(-) diff --git a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt index 1fc7ff57..c966fb83 100644 --- a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt +++ b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt @@ -29,11 +29,13 @@ import org.asteroidos.sync.media.IMediaService import org.asteroidos.sync.media.MediaSupervisor import org.freedesktop.dbus.DBusPath import org.freedesktop.dbus.connections.impl.DBusConnection +import org.freedesktop.dbus.exceptions.DBusException import org.freedesktop.dbus.interfaces.Properties.PropertiesChanged import org.freedesktop.dbus.types.Variant import org.mpris.MediaPlayer2 import org.mpris.mediaplayer2.Player import java.nio.charset.Charset +import java.util.Arrays import java.util.Collections import java.util.Date @@ -41,7 +43,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper private val mNReceiver: NotificationService.NotificationReceiver? = null private val hashing = Hashing.goodFastHash(64) - private val busSuffix = Hashing.murmur3_32_fixed(42).hashLong(Date().time).toString() + private val busSuffix = "x" + Hashing.murmur3_32_fixed(42).hashLong(Date().time).toString() override fun sync() { connectionProvider.acquireDBusConnection { connection: DBusConnection -> @@ -59,52 +61,52 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper override fun onReset() { connectionProvider.acquireDBusConnection { connection: DBusConnection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Metadata", Variant(metadata, "a{sv}")) as Map>?, emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Metadata", Variant(getMetadata(), "a{sv}")) as Map>?, listOf())) } } - override fun canQuit(): Boolean { + fun canQuit(): Boolean { return false } - override fun isFullscreen(): Boolean { + fun isFullscreen(): Boolean { return false } - override fun setFullscreen(_property: Boolean) { + fun setFullscreen(_property: Boolean) { } - override fun canSetFullscreen(): Boolean { + fun canSetFullscreen(): Boolean { return false } - override fun canRaise(): Boolean { + fun canRaise(): Boolean { return false } - override fun hasTrackList(): Boolean { + fun hasTrackList(): Boolean { // TODO: Track List!!! :grin: return false } - override fun getIdentity(): String { + fun getIdentity(): String { return "Android" } - override fun getSupportedUriSchemes(): List = emptyList() + fun getSupportedUriSchemes(): List = listOf() - override fun getSupportedMimeTypes(): List = emptyList() + fun getSupportedMimeTypes(): List = listOf() override fun Raise() {} override fun Quit() {} - override fun getPlaybackStatus(): String { + fun getPlaybackStatus(): String { val controller = supervisor.mediaController ?: return "Stopped" return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isPlaying) return@runBlocking "Playing" else if (controller.playbackState == STATE_READY && !controller.playWhenReady) return@runBlocking "Paused" else return@runBlocking "Stopped" } } - override fun getLoopStatus(): String { + fun getLoopStatus(): String { val controller = supervisor.mediaController ?: return "None" return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking when (controller.repeatMode) { @@ -116,7 +118,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - override fun setLoopStatus(_property: String) { + fun setLoopStatus(_property: String) { val controller = supervisor.mediaController ?: return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_SET_REPEAT_MODE)) { @@ -130,14 +132,14 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - override fun getRate(): Double { + fun getRate(): Double { val controller = supervisor.mediaController ?: return 1.0 return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.playbackParameters.speed.toDouble() } } - override fun setRate(_property: Double) { + fun setRate(_property: Double) { val controller = supervisor.mediaController ?: return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_SET_SPEED_AND_PITCH)) { @@ -146,7 +148,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - override fun isShuffle(): Boolean { + fun isShuffle(): Boolean { val controller = supervisor.mediaController ?: return false return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)) { @@ -157,7 +159,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - override fun setShuffle(_property: Boolean) { + fun setShuffle(_property: Boolean) { val controller = supervisor.mediaController ?: return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)) { @@ -175,7 +177,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper private val currentMediaIdObjectPath get() = mediaToPath(supervisor.mediaController?.connectedToken?.packageName ?: "", supervisor.mediaController?.currentMediaItem) - override fun getMetadata(): Map> { + fun getMetadata(): Map> { val dummy = Collections.singletonMap>("mpris:trackid", Variant(DBusPath("/org/mpris/MediaPlayer2/TrackList/NoTrack"))) val controller = supervisor.mediaController ?: return dummy @@ -191,7 +193,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - override fun getVolume(): Double { + fun getVolume(): Double { // TODO:XXX: val controller = supervisor.mediaController ?: return 0.0 return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { @@ -199,7 +201,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - override fun setVolume(_property: Double) { + fun setVolume(_property: Double) { val controller = supervisor.mediaController ?: return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_SET_VOLUME)) { @@ -208,7 +210,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - override fun getPosition(): Long { + fun getPosition(): Long { val controller = supervisor.mediaController ?: return 0L return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)) { @@ -219,50 +221,50 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - override fun getMinimumRate(): Double { + fun getMinimumRate(): Double { return .25 } - override fun getMaximumRate(): Double { + fun getMaximumRate(): Double { return 2.0 } - override fun canGoNext(): Boolean { + fun canGoNext(): Boolean { val controller = supervisor.mediaController ?: return false return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.isCommandAvailable(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) && controller.hasNextMediaItem() } } - override fun canGoPrevious(): Boolean { + fun canGoPrevious(): Boolean { val controller = supervisor.mediaController ?: return false return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) && controller.hasPreviousMediaItem() } } - override fun canPlay(): Boolean { + fun canPlay(): Boolean { val controller = supervisor.mediaController ?: return false return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.isCommandAvailable(COMMAND_PLAY_PAUSE) } } - override fun canPause(): Boolean { + fun canPause(): Boolean { val controller = supervisor.mediaController ?: return false return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.isCommandAvailable(COMMAND_PLAY_PAUSE) } } - override fun canSeek(): Boolean { + fun canSeek(): Boolean { val controller = supervisor.mediaController ?: return false return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) && controller.isCurrentMediaItemSeekable } } - override fun canControl(): Boolean = supervisor.mediaController != null + fun canControl(): Boolean = supervisor.mediaController != null override fun Next() { val controller = supervisor.mediaController ?: return @@ -346,6 +348,75 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper override fun getObjectPath() = "/org/mpris/MediaPlayer2" + @Suppress("UNCHECKED_CAST") + override fun Get(p0: String?, p1: String?): A { + return when (p1) { + "CanQuit" -> canQuit() as A + "Fullscreen" -> isFullscreen() as A + "CanSetFullscreen" -> canSetFullscreen() as A + "CanRaise" -> canRaise() as A + "HasTrackList" -> hasTrackList() as A + "Identity" -> getIdentity() as A + "SupportedUriSchemes" -> getSupportedUriSchemes() as A + "SupportedMimeTypes" -> getSupportedMimeTypes() as A + "PlaybackStatus" -> getPlaybackStatus() as A + "LoopStatus" -> getLoopStatus() as A + "Rate" -> getRate() as A + "Shuffle" -> isShuffle() as A + "Metadata" -> getMetadata() as A + "Volume" -> getVolume() as A + "Position" -> getPosition() as A + "MinimumRate" -> getMinimumRate() as A + "MaximumRate" -> getMaximumRate() as A + "CanGoNext" -> canGoNext() as A + "CanGoPrevious" -> canGoPrevious() as A + "CanPlay" -> canPlay() as A + "CanPause" -> canPause() as A + "CanSeek" -> canSeek() as A + "CanControl" -> canControl() as A + else -> throw DBusException("cannot get $p1") + } + } + + override fun Set(p0: String?, p1: String?, p2: A) { + when (p1) { + else -> throw DBusException("cannot set $p1") + } + } + + override fun GetAll(p0: String?): MutableMap> { + return when (p0) { + "org.mpris.MediaPlayer2" -> mutableMapOf( + "CanQuit" to Variant(canQuit(), "b"), + "Fullscreen" to Variant(isFullscreen(), "b"), + "CanSetFullscreen" to Variant(canSetFullscreen(), "b"), + "CanRaise" to Variant(canRaise(), "b"), + "HasTrackList" to Variant(hasTrackList(), "b"), + "Identity" to Variant(getIdentity(), "s"), + "SupportedUriSchemes" to Variant(getSupportedUriSchemes(), "as"), + "SupportedMimeTypes" to Variant(getSupportedMimeTypes(), "as"), + ) + "org.mpris.MediaPlayer2.Player" -> mutableMapOf( + "PlaybackStatus" to Variant(getPlaybackStatus(), "s"), + "LoopStatus" to Variant(getLoopStatus(), "s"), + "Rate" to Variant(getRate(), "d"), + "Shuffle" to Variant(isShuffle(), "b"), + "Metadata" to Variant(getMetadata(), "a{sv}"), + "Volume" to Variant(getVolume(), "d"), + "Position" to Variant(getPosition(), "x"), + "MinimumRate" to Variant(getMinimumRate(), "d"), + "MaximumRate" to Variant(getMaximumRate(), "d"), + "CanGoNext" to Variant(canGoNext(), "b"), + "CanGoPrevious" to Variant(canGoPrevious(), "b"), + "CanPlay" to Variant(canPlay(), "b"), + "CanPause" to Variant(canPause(), "b"), + "CanSeek" to Variant(canSeek(), "b"), + "CanControl" to Variant(canControl(), "b"), + ) + else -> mutableMapOf() + } + } + override fun isRemote(): Boolean = false override fun onPositionDiscontinuity(oldPosition: PositionInfo, newPosition: PositionInfo, reason: Int) { @@ -356,31 +427,31 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper override fun onIsPlayingChanged(isPlaying: Boolean) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(playbackStatus)) as Map>?, emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(getPlaybackStatus())) as Map>?, listOf())) } } override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(playbackStatus)) as Map>?, emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(getPlaybackStatus())) as Map>?, listOf())) } } override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Metadata", Variant(metadata, "a{sv}")) as Map>?, emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Metadata", Variant(getMetadata(), "a{sv}")) as Map>?, listOf())) } } override fun onPlaybackStateChanged(playbackState: Int) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(playbackStatus)) as Map>?, emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(getPlaybackStatus())) as Map>?, listOf())) } } override fun onVolumeChanged(volume: Float) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Volume", Variant(playbackStatus)) as Map>?, emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Volume", Variant(getPlaybackStatus())) as Map>?, listOf())) } } @@ -394,7 +465,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper "CanSeek", Variant(canSeek()), "CanControl", Variant(canControl()), ) - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", map, emptyList())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", map, listOf())) } } } \ No newline at end of file diff --git a/app/src/main/java/org/mpris/MediaPlayer2.java b/app/src/main/java/org/mpris/MediaPlayer2.java index 3892d9e0..ce40cf45 100644 --- a/app/src/main/java/org/mpris/MediaPlayer2.java +++ b/app/src/main/java/org/mpris/MediaPlayer2.java @@ -2,42 +2,24 @@ import java.util.List; import org.freedesktop.dbus.TypeRef; -import org.freedesktop.dbus.annotations.DBusBoundProperty; import org.freedesktop.dbus.annotations.DBusProperty; import org.freedesktop.dbus.annotations.DBusProperty.Access; import org.freedesktop.dbus.interfaces.DBusInterface; +import org.freedesktop.dbus.interfaces.Properties; /** * Auto-generated class. */ -public interface MediaPlayer2 extends DBusInterface { - - @DBusBoundProperty(name = "CanQuit", access = Access.READ) - public boolean canQuit(); - - @DBusBoundProperty(name = "Fullscreen", access = Access.READ) - public boolean isFullscreen(); - @DBusBoundProperty(name = "Fullscreen", access = Access.WRITE) - public void setFullscreen(boolean _property); - - @DBusBoundProperty(name = "CanSetFullscreen", access = Access.READ) - public boolean canSetFullscreen(); - - @DBusBoundProperty(name = "CanRaise", access = Access.READ) - public boolean canRaise(); - - @DBusBoundProperty(name = "HasTrackList", access = Access.READ) - public boolean hasTrackList(); - - @DBusBoundProperty(name = "Identity", access = Access.READ) - public String getIdentity(); - - @DBusBoundProperty(name = "SupportedUriSchemes", access = Access.READ) - public List getSupportedUriSchemes(); - - @DBusBoundProperty(name = "SupportedMimeTypes", access = Access.READ) - public List getSupportedMimeTypes(); - +@DBusProperty(name = "CanQuit", type = Boolean.class, access = Access.READ) +@DBusProperty(name = "Fullscreen", type = Boolean.class, access = Access.READ_WRITE) +@DBusProperty(name = "CanSetFullscreen", type = Boolean.class, access = Access.READ) +@DBusProperty(name = "CanRaise", type = Boolean.class, access = Access.READ) +@DBusProperty(name = "HasTrackList", type = Boolean.class, access = Access.READ) +@DBusProperty(name = "Identity", type = String.class, access = Access.READ) +@DBusProperty(name = "DesktopEntry", type = String.class, access = Access.READ) +@DBusProperty(name = "SupportedUriSchemes", type = MediaPlayer2.PropertySupportedUriSchemesType.class, access = Access.READ) +@DBusProperty(name = "SupportedMimeTypes", type = MediaPlayer2.PropertySupportedMimeTypesType.class, access = Access.READ) +public interface MediaPlayer2 extends DBusInterface, Properties { public void Raise(); diff --git a/app/src/main/java/org/mpris/mediaplayer2/Player.java b/app/src/main/java/org/mpris/mediaplayer2/Player.java index 40a824e1..29a1b1df 100644 --- a/app/src/main/java/org/mpris/mediaplayer2/Player.java +++ b/app/src/main/java/org/mpris/mediaplayer2/Player.java @@ -3,14 +3,11 @@ import java.util.Map; import org.freedesktop.dbus.DBusPath; import org.freedesktop.dbus.TypeRef; -import org.freedesktop.dbus.annotations.DBusBoundProperty; import org.freedesktop.dbus.annotations.DBusInterfaceName; import org.freedesktop.dbus.annotations.DBusProperty; import org.freedesktop.dbus.annotations.DBusProperty.Access; -import org.freedesktop.dbus.annotations.PropertiesEmitsChangedSignal; import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.interfaces.DBusInterface; -import org.freedesktop.dbus.interfaces.Properties; import org.freedesktop.dbus.messages.DBusSignal; import org.freedesktop.dbus.types.Variant; @@ -18,62 +15,23 @@ * Auto-generated class. */ @DBusInterfaceName("org.mpris.MediaPlayer2.Player") +@DBusProperty(name = "PlaybackStatus", type = String.class, access = Access.READ) +@DBusProperty(name = "LoopStatus", type = String.class, access = Access.READ_WRITE) +@DBusProperty(name = "Rate", type = Double.class, access = Access.READ_WRITE) +@DBusProperty(name = "Shuffle", type = Boolean.class, access = Access.READ_WRITE) +@DBusProperty(name = "Metadata", type = Player.PropertyMetadataType.class, access = Access.READ) +@DBusProperty(name = "Volume", type = Double.class, access = Access.READ_WRITE) +@DBusProperty(name = "Position", type = Long.class, access = Access.READ) +@DBusProperty(name = "MinimumRate", type = Double.class, access = Access.READ) +@DBusProperty(name = "MaximumRate", type = Double.class, access = Access.READ) +@DBusProperty(name = "CanGoNext", type = Boolean.class, access = Access.READ) +@DBusProperty(name = "CanGoPrevious", type = Boolean.class, access = Access.READ) +@DBusProperty(name = "CanPlay", type = Boolean.class, access = Access.READ) +@DBusProperty(name = "CanPause", type = Boolean.class, access = Access.READ) +@DBusProperty(name = "CanSeek", type = Boolean.class, access = Access.READ) +@DBusProperty(name = "CanControl", type = Boolean.class, access = Access.READ) public interface Player extends DBusInterface { - @DBusBoundProperty(name = "PlaybackStatus", access = Access.READ) - public String getPlaybackStatus(); - - @DBusBoundProperty(name = "LoopStatus", access = Access.READ) - public String getLoopStatus(); - @DBusBoundProperty(name = "LoopStatus", access = Access.WRITE) - public void setLoopStatus(String _property); - - @DBusBoundProperty(name = "Rate", access = Access.READ) - public double getRate(); - @DBusBoundProperty(name = "Rate", access = Access.WRITE) - public void setRate(double _property); - - @DBusBoundProperty(name = "Shuffle", access = Access.READ) - public boolean isShuffle(); - @DBusBoundProperty(name = "Shuffle", access = Access.WRITE) - public void setShuffle(boolean _property); - - @DBusBoundProperty(name = "Metadata", access = Access.READ) - public Map> getMetadata(); - - @DBusBoundProperty(name = "Volume", access = Access.READ) - public double getVolume(); - @DBusBoundProperty(name = "Volume", access = Access.WRITE) - public void setVolume(double _property); - - @DBusBoundProperty(name = "Position", access = Access.READ) - public long getPosition(); - - @DBusBoundProperty(name = "MinimumRate", access = Access.READ) - public double getMinimumRate(); - - @DBusBoundProperty(name = "MaximumRate", access = Access.READ) - public double getMaximumRate(); - - @DBusBoundProperty(name = "CanGoNext", access = Access.READ) - public boolean canGoNext(); - - @DBusBoundProperty(name = "CanGoPrevious", access = Access.READ) - public boolean canGoPrevious(); - - @DBusBoundProperty(name = "CanPlay", access = Access.READ) - public boolean canPlay(); - - @DBusBoundProperty(name = "CanPause", access = Access.READ) - public boolean canPause(); - - @DBusBoundProperty(name = "CanSeek", access = Access.READ) - public boolean canSeek(); - - @DBusBoundProperty(name = "CanControl", access = Access.READ) - public boolean canControl(); - - public void Next(); public void Previous(); From e2983d7823b48866cbfbfc43cebc90921228912b Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Sat, 4 May 2024 09:34:47 +0200 Subject: [PATCH 06/10] Simplify MPRIS property access --- app/build.gradle.kts | 8 +- .../org/asteroidos/sync/dbus/MediaService.kt | 258 ++++++++---------- .../asteroidos/sync/dbus/MyDBusProperty.kt | 21 ++ .../sync/dbus/NotificationService.kt | 4 - 4 files changed, 139 insertions(+), 152 deletions(-) create mode 100644 app/src/main/java/org/asteroidos/sync/dbus/MyDBusProperty.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d3a5abdd..09cd23fd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -76,7 +76,7 @@ repositories { } dependencies { - implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) + implementation(kotlin("reflect")) testImplementation("junit:junit:4.13.2") implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.legacy:legacy-support-v4:1.0.0") @@ -92,8 +92,8 @@ dependencies { implementation("androidx.media3:media3-common:1.3.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1-Beta") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.8.1-Beta") - api(fileTree("src/main/libs") { include("*.jar") }) - - compileOnly("org.slf4j:slf4j-api:2.0.7") implementation("uk.uuid.slf4j:slf4j-android:2.0.7-0") + compileOnly("org.slf4j:slf4j-api:2.0.7") + + api(fileTree("libs") { include("*.jar") }) } diff --git a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt index c966fb83..e9b41807 100644 --- a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt +++ b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt @@ -18,9 +18,11 @@ package org.asteroidos.sync.dbus import android.content.Context +import android.os.Build import android.os.Handler import androidx.media3.common.MediaItem import androidx.media3.common.Player.* +import com.google.common.base.Optional import com.google.common.collect.Lists import com.google.common.hash.Hashing import kotlinx.coroutines.android.asCoroutineDispatcher @@ -29,15 +31,18 @@ import org.asteroidos.sync.media.IMediaService import org.asteroidos.sync.media.MediaSupervisor import org.freedesktop.dbus.DBusPath import org.freedesktop.dbus.connections.impl.DBusConnection -import org.freedesktop.dbus.exceptions.DBusException import org.freedesktop.dbus.interfaces.Properties.PropertiesChanged import org.freedesktop.dbus.types.Variant import org.mpris.MediaPlayer2 import org.mpris.mediaplayer2.Player import java.nio.charset.Charset -import java.util.Arrays +import java.time.format.DateTimeFormatter import java.util.Collections import java.util.Date +import java.util.GregorianCalendar +import kotlin.reflect.KMutableProperty +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.memberProperties class MediaService(private val mCtx: Context, private val supervisor: MediaSupervisor, private val connectionProvider: IDBusConnectionProvider) : IMediaService, MediaPlayer2, Player { private val mNReceiver: NotificationService.NotificationReceiver? = null @@ -59,54 +64,47 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - override fun onReset() { - connectionProvider.acquireDBusConnection { connection: DBusConnection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Metadata", Variant(getMetadata(), "a{sv}")) as Map>?, listOf())) - } - } + @MyDBusProperty("org.mpris.MediaPlayer2") + val CanQuit get() = false - fun canQuit(): Boolean { - return false - } + @MyDBusProperty("org.mpris.MediaPlayer2") + var Fullscreen: Boolean + get() = false + set(_property) {} - fun isFullscreen(): Boolean { - return false - } + @MyDBusProperty("org.mpris.MediaPlayer2") + val CanSetFullscreen get() = false - fun setFullscreen(_property: Boolean) { - } + @MyDBusProperty("org.mpris.MediaPlayer2") + val CanRaise get() = false - fun canSetFullscreen(): Boolean { - return false - } + // TODO: Track List!!! :grin: + @MyDBusProperty("org.mpris.MediaPlayer2") + val HasTrackList get() = false - fun canRaise(): Boolean { - return false - } + @MyDBusProperty("org.mpris.MediaPlayer2") + val Identity get() = "Android" - fun hasTrackList(): Boolean { - // TODO: Track List!!! :grin: - return false - } + @MyDBusProperty("org.mpris.MediaPlayer2", "as") + val SupportedUriSchemes: List get() = listOf() - fun getIdentity(): String { - return "Android" - } - - fun getSupportedUriSchemes(): List = listOf() - - fun getSupportedMimeTypes(): List = listOf() + @MyDBusProperty("org.mpris.MediaPlayer2", "as") + val SupportedMimeTypes: List get() = listOf() override fun Raise() {} + override fun Quit() {} - fun getPlaybackStatus(): String { + + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + val PlaybackStatus: String get() { val controller = supervisor.mediaController ?: return "Stopped" return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isPlaying) return@runBlocking "Playing" else if (controller.playbackState == STATE_READY && !controller.playWhenReady) return@runBlocking "Paused" else return@runBlocking "Stopped" } } - fun getLoopStatus(): String { + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + var LoopStatus: String get() { val controller = supervisor.mediaController ?: return "None" return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking when (controller.repeatMode) { @@ -116,9 +114,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper else -> throw IllegalStateException("Unexpected value: ${controller.repeatMode}") } } - } - - fun setLoopStatus(_property: String) { + } set(_property) { val controller = supervisor.mediaController ?: return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_SET_REPEAT_MODE)) { @@ -132,14 +128,13 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - fun getRate(): Double { + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + var Rate: Double get() { val controller = supervisor.mediaController ?: return 1.0 return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.playbackParameters.speed.toDouble() } - } - - fun setRate(_property: Double) { + } set(_property) { val controller = supervisor.mediaController ?: return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_SET_SPEED_AND_PITCH)) { @@ -148,7 +143,8 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - fun isShuffle(): Boolean { + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + var Shuffle: Boolean get() { val controller = supervisor.mediaController ?: return false return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)) { @@ -157,9 +153,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper return@runBlocking false } } - } - - fun setShuffle(_property: Boolean) { + } set(_property) { val controller = supervisor.mediaController ?: return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_SET_SHUFFLE_MODE)) { @@ -177,31 +171,43 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper private val currentMediaIdObjectPath get() = mediaToPath(supervisor.mediaController?.connectedToken?.packageName ?: "", supervisor.mediaController?.currentMediaItem) - fun getMetadata(): Map> { + @MyDBusProperty("org.mpris.MediaPlayer2.Player", "a{sv}") + val Metadata: Map> get() { val dummy = Collections.singletonMap>("mpris:trackid", Variant(DBusPath("/org/mpris/MediaPlayer2/TrackList/NoTrack"))) val controller = supervisor.mediaController ?: return dummy return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.currentMediaItem != null) { - return@runBlocking java.util.Map.of( - "mpris:trackid", Variant(currentMediaIdObjectPath), - "mpris:length", Variant(controller.contentDuration * 1000) + val metadata = mutableMapOf>( + "mpris:trackid" to Variant(currentMediaIdObjectPath), + "mpris:length" to Variant(controller.contentDuration * 1000L), ) + + controller.mediaMetadata.albumTitle?.let { metadata["xesam:album"] = Variant(it) } + controller.mediaMetadata.artist?.let { metadata["xesam:artist"] = Variant(listOf(it), "as") } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + controller.mediaMetadata.recordingYear?.let { metadata["xesam:contentCreated"] = Variant(GregorianCalendar(it, 0, 1).toZonedDateTime().format(DateTimeFormatter.ISO_INSTANT)) } + } + controller.mediaMetadata.discNumber?.let { metadata["xesam:discNumber"] = Variant(it) } + controller.mediaMetadata.genre?.let { metadata["xesam:genre"] = Variant(it) } + controller.mediaMetadata.title?.let { metadata["xesam:title"] = Variant(it) } + controller.mediaMetadata.trackNumber?.let { metadata["xesam:trackNumber"] = Variant(it) } + + return@runBlocking metadata } else { return@runBlocking dummy } } } - fun getVolume(): Double { + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + var Volume: Double get() { // TODO:XXX: val controller = supervisor.mediaController ?: return 0.0 return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.volume.toDouble() } - } - - fun setVolume(_property: Double) { + } set(_property) { val controller = supervisor.mediaController ?: return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_SET_VOLUME)) { @@ -210,7 +216,8 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - fun getPosition(): Long { + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + val Position: Long get() { val controller = supervisor.mediaController ?: return 0L return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)) { @@ -221,50 +228,54 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - fun getMinimumRate(): Double { - return .25 - } + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + val MinimumRate get() = .25 - fun getMaximumRate(): Double { - return 2.0 - } + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + val MaximumRate get() = 2.0 - fun canGoNext(): Boolean { + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + val CanGoNext: Boolean get() { val controller = supervisor.mediaController ?: return false return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.isCommandAvailable(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) && controller.hasNextMediaItem() } } - fun canGoPrevious(): Boolean { + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + val CanGoPrevious: Boolean get() { val controller = supervisor.mediaController ?: return false return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) && controller.hasPreviousMediaItem() } } - fun canPlay(): Boolean { + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + val CanPlay: Boolean get() { val controller = supervisor.mediaController ?: return false return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.isCommandAvailable(COMMAND_PLAY_PAUSE) } } - fun canPause(): Boolean { + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + val CanPause: Boolean get() { val controller = supervisor.mediaController ?: return false return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.isCommandAvailable(COMMAND_PLAY_PAUSE) } } - fun canSeek(): Boolean { + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + val CanSeek: Boolean get() { val controller = supervisor.mediaController ?: return false return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { return@runBlocking controller.isCommandAvailable(COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM) && controller.isCurrentMediaItemSeekable } } - fun canControl(): Boolean = supervisor.mediaController != null + @MyDBusProperty("org.mpris.MediaPlayer2.Player") + val CanControl get() = supervisor.mediaController != null override fun Next() { val controller = supervisor.mediaController ?: return @@ -349,74 +360,28 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper override fun getObjectPath() = "/org/mpris/MediaPlayer2" @Suppress("UNCHECKED_CAST") - override fun Get(p0: String?, p1: String?): A { - return when (p1) { - "CanQuit" -> canQuit() as A - "Fullscreen" -> isFullscreen() as A - "CanSetFullscreen" -> canSetFullscreen() as A - "CanRaise" -> canRaise() as A - "HasTrackList" -> hasTrackList() as A - "Identity" -> getIdentity() as A - "SupportedUriSchemes" -> getSupportedUriSchemes() as A - "SupportedMimeTypes" -> getSupportedMimeTypes() as A - "PlaybackStatus" -> getPlaybackStatus() as A - "LoopStatus" -> getLoopStatus() as A - "Rate" -> getRate() as A - "Shuffle" -> isShuffle() as A - "Metadata" -> getMetadata() as A - "Volume" -> getVolume() as A - "Position" -> getPosition() as A - "MinimumRate" -> getMinimumRate() as A - "MaximumRate" -> getMaximumRate() as A - "CanGoNext" -> canGoNext() as A - "CanGoPrevious" -> canGoPrevious() as A - "CanPlay" -> canPlay() as A - "CanPause" -> canPause() as A - "CanSeek" -> canSeek() as A - "CanControl" -> canControl() as A - else -> throw DBusException("cannot get $p1") - } - } + override fun Get(p0: String?, p1: String?): A = + MediaService::class.memberProperties + .find { it.findAnnotation()?.dBusInterface == p0 && it.name == p1 } + ?.getter?.call(this) as A override fun Set(p0: String?, p1: String?, p2: A) { - when (p1) { - else -> throw DBusException("cannot set $p1") - } - } - - override fun GetAll(p0: String?): MutableMap> { - return when (p0) { - "org.mpris.MediaPlayer2" -> mutableMapOf( - "CanQuit" to Variant(canQuit(), "b"), - "Fullscreen" to Variant(isFullscreen(), "b"), - "CanSetFullscreen" to Variant(canSetFullscreen(), "b"), - "CanRaise" to Variant(canRaise(), "b"), - "HasTrackList" to Variant(hasTrackList(), "b"), - "Identity" to Variant(getIdentity(), "s"), - "SupportedUriSchemes" to Variant(getSupportedUriSchemes(), "as"), - "SupportedMimeTypes" to Variant(getSupportedMimeTypes(), "as"), - ) - "org.mpris.MediaPlayer2.Player" -> mutableMapOf( - "PlaybackStatus" to Variant(getPlaybackStatus(), "s"), - "LoopStatus" to Variant(getLoopStatus(), "s"), - "Rate" to Variant(getRate(), "d"), - "Shuffle" to Variant(isShuffle(), "b"), - "Metadata" to Variant(getMetadata(), "a{sv}"), - "Volume" to Variant(getVolume(), "d"), - "Position" to Variant(getPosition(), "x"), - "MinimumRate" to Variant(getMinimumRate(), "d"), - "MaximumRate" to Variant(getMaximumRate(), "d"), - "CanGoNext" to Variant(canGoNext(), "b"), - "CanGoPrevious" to Variant(canGoPrevious(), "b"), - "CanPlay" to Variant(canPlay(), "b"), - "CanPause" to Variant(canPause(), "b"), - "CanSeek" to Variant(canSeek(), "b"), - "CanControl" to Variant(canControl(), "b"), - ) - else -> mutableMapOf() - } + val property = MediaService::class.memberProperties.find { it.findAnnotation()?.dBusInterface == p0 && it.name == p1 } as? KMutableProperty<*> + property!!.setter.call(this, p2); } + override fun GetAll(p0: String?): MutableMap> = getProperties(p0!!, Optional.absent()) + + private fun getProperties(dBusInterface: String, names: Optional> = Optional.absent()): MutableMap> = + MediaService::class.memberProperties + .filter { it.findAnnotation()?.dBusInterface == dBusInterface && (if (names.isPresent) it.name in names.get() else true) } + .associate { it.name to (if (it.findAnnotation()!!.dBusTypeSig.isNotEmpty()) + Variant(it.getter.call(this), + it.findAnnotation()!!.dBusTypeSig) + else + Variant(it.getter.call(this))) } + .toMutableMap() + override fun isRemote(): Boolean = false override fun onPositionDiscontinuity(oldPosition: PositionInfo, newPosition: PositionInfo, reason: Int) { @@ -427,45 +392,50 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper override fun onIsPlayingChanged(isPlaying: Boolean) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(getPlaybackStatus())) as Map>?, listOf())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf("PlaybackStatus"))), listOf())) } } override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(getPlaybackStatus())) as Map>?, listOf())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf("PlaybackStatus"))), listOf())) } } override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Metadata", Variant(getMetadata(), "a{sv}")) as Map>?, listOf())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf("Metadata"))), listOf())) } } override fun onPlaybackStateChanged(playbackState: Int) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("PlaybackStatus", Variant(getPlaybackStatus())) as Map>?, listOf())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf("PlaybackStatus"))), listOf())) } } override fun onVolumeChanged(volume: Float) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", Collections.singletonMap("Volume", Variant(getPlaybackStatus())) as Map>?, listOf())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf("Volume"))), listOf())) } } override fun onAvailableCommandsChanged(availableCommands: Commands) { connectionProvider.acquireDBusConnection { connection -> - val map: Map> = java.util.Map.of( - "CanGoNext", Variant(canGoNext()), - "CanGoPrevious", Variant(canGoPrevious()), - "CanPlay", Variant(canPlay()), - "CanPause", Variant(canPause()), - "CanSeek", Variant(canSeek()), - "CanControl", Variant(canControl()), - ) - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", map, listOf())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf( + "CanGoNext", + "CanGoPrevious", + "CanPlay", + "CanPause", + "CanSeek", + "CanControl", + ))), listOf())) + } + } + + override fun onReset() { + connectionProvider.acquireDBusConnection { connection: DBusConnection -> + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.absent()), listOf())) } } } \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/dbus/MyDBusProperty.kt b/app/src/main/java/org/asteroidos/sync/dbus/MyDBusProperty.kt new file mode 100644 index 00000000..ca2a804b --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/MyDBusProperty.kt @@ -0,0 +1,21 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.dbus + +annotation class MyDBusProperty(val dBusInterface: String, val dBusTypeSig: String = "") diff --git a/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt b/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt index 11f3034d..33798c8b 100644 --- a/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt +++ b/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt @@ -41,10 +41,6 @@ class NotificationService(private val mCtx: Context, private val connectionProvi private val mapping: BiMap = HashBiMap.create() private var mNReceiver: NotificationReceiver? = null - init { - connectionProvider.acquireDBusConnectionLater({ notify: DBusConnection -> Log.w("DBusNotificationService", Arrays.toString(notify.names)) }, 1000) - } - override fun postNotification(context: Context, intent: Intent) { val event = intent.getStringExtra("event") if (event == "posted") { From 96afc2fd0c15645d43b58a3d38fbcefe6b1f1c68 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Sat, 4 May 2024 10:43:01 +0200 Subject: [PATCH 07/10] Make synchronization more efficient --- .../sync/connectivity/SlirpService.java | 70 +++++++++---------- .../org/asteroidos/sync/dbus/MediaService.kt | 5 +- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java index 5b168a4e..8cbcb3da 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java @@ -33,21 +33,16 @@ import org.asteroidos.sync.dbus.IDBusConnectionCallback; import org.asteroidos.sync.dbus.IDBusConnectionProvider; import org.asteroidos.sync.utils.AsteroidUUIDS; -import org.freedesktop.dbus.connections.SASL; import org.freedesktop.dbus.connections.impl.AndroidDBusConnectionBuilder; import org.freedesktop.dbus.connections.impl.DBusConnection; -import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder; import org.freedesktop.dbus.exceptions.DBusException; import java.io.FileDescriptor; -import java.io.IOException; -import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.UUID; import java.util.function.Consumer; -import kotlin.ParameterName; import kotlin.Unit; import kotlin.jvm.functions.Function1; @@ -73,6 +68,8 @@ public class SlirpService implements IConnectivityService, IDBusConnectionProvid private DBusConnection dBusConnection = null; + private final int DBUS_HANDLER_MSG_OPEN = 1; + public SlirpService(Context ctx, IAsteroidDevice device) { mDevice = device; mCtx = ctx; @@ -83,14 +80,7 @@ public SlirpService(Context ctx, IAsteroidDevice device) { @Override public void handleMessage(@NonNull Message msg) { switch (msg.arg1) { - case 0 -> { - if (dBusConnection == null) - break; - - dBusConnection.disconnect(); - dBusConnection = null; - } - case 1 -> { + case DBUS_HANDLER_MSG_OPEN -> { if (dBusConnection != null) break; @@ -106,10 +96,10 @@ public void handleMessage(@NonNull Message msg) { }; dBusConnectionRunnable = dBusConnectionCallback -> { - final Message message = new Message(); - message.arg1 = 1; - message.obj = "tcp:host=127.0.0.1,bind=*,port=55556,family=ipv4"; - dBusHandler.sendMessage(message); + if (dBusConnection == null) { + Log.e("SlirpService", "D-Bus connection not ready yet"); + return; + } try { dBusConnectionCallback.handleConnection(dBusConnection); @@ -132,21 +122,22 @@ public void handleMessage(@NonNull Message msg) { continue; } - synchronized (SlirpService.this) { - rx.clear(); - long read = vdeRecv(rx, 0, mtu - 3); - assert read <= (mtu - 3); - if (read > 0) { + rx.clear(); + long read; + synchronized (rx) { + read = vdeRecv(rx, 0, mtu - 3); + } + assert read <= (mtu - 3); + if (read > 0) { // Log.d("SlirpService", "Received " + read + " bytes"); - byte[] data = new byte[(int) read]; - rx.get(data); - mDevice.send(AsteroidUUIDS.SLIRP_OUTGOING_CHAR, data, SlirpService.this); - } else { - Log.e("SlirpService", "Read error: " + read); - } + byte[] data = new byte[(int) read]; + rx.get(data); + mDevice.send(AsteroidUUIDS.SLIRP_OUTGOING_CHAR, data, SlirpService.this); + } else { + Log.e("SlirpService", "Read error: " + read); } } catch (Exception e) { - Log.e("SlirpService", "Poller exception", e); + Log.e("SlirpService", "Not ready yet", e); } } }); @@ -158,15 +149,20 @@ public void handleMessage(@NonNull Message msg) { mDevice.registerCallback(AsteroidUUIDS.SLIRP_INCOMING_CHAR, data -> { resetMtu(); - synchronized (SlirpService.this) { - tx.clear(); - tx.put(data); + tx.clear(); + tx.put(data); + synchronized (tx) { vdeSend(tx, 0, data.length); -// Log.d("SlirpService", "Sent " + data.length + " bytes"); } +// Log.d("SlirpService", "Sent " + data.length + " bytes"); }); slirpThread.start(); + + final Message message = new Message(); + message.arg1 = DBUS_HANDLER_MSG_OPEN; + message.obj = "tcp:host=127.0.0.1,bind=*,port=55556,family=ipv4"; + dBusHandler.sendMessage(message); } private void startNative(int mtu) { @@ -178,13 +174,15 @@ private void startNative(int mtu) { } private void resetMtu() { - synchronized (SlirpService.this) { + synchronized (rx) { int newMtu = mDevice.getMtu(); if (mtu != newMtu) { mtu = newMtu; - finalizeNative(); - startNative(mtu - 3); + synchronized (tx) { + finalizeNative(); + startNative(mtu - 3); + } } } } diff --git a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt index e9b41807..40cff5e7 100644 --- a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt +++ b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt @@ -48,9 +48,10 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper private val mNReceiver: NotificationService.NotificationReceiver? = null private val hashing = Hashing.goodFastHash(64) - private val busSuffix = "x" + Hashing.murmur3_32_fixed(42).hashLong(Date().time).toString() + private lateinit var busSuffix: String override fun sync() { + busSuffix = "x" + Hashing.murmur3_32_fixed(42).hashLong(Date().time).toString() connectionProvider.acquireDBusConnection { connection: DBusConnection -> connection.requestBusName("org.mpris.MediaPlayer2.x$busSuffix") connection.exportObject("/org/mpris/MediaPlayer2", this@MediaService) @@ -404,7 +405,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf("Metadata"))), listOf())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf("Metadata", "PlaybackStatus"))), listOf())) } } From 9a74b3f7124a573ff74e59f0421aed293abb1431 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Sat, 4 May 2024 10:51:10 +0200 Subject: [PATCH 08/10] Use device volume instead of content volume in MediaService --- .../java/org/asteroidos/sync/dbus/MediaService.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt index 40cff5e7..cf4c4f18 100644 --- a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt +++ b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt @@ -206,13 +206,17 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper // TODO:XXX: val controller = supervisor.mediaController ?: return 0.0 return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { - return@runBlocking controller.volume.toDouble() + if (controller.isCommandAvailable(COMMAND_GET_DEVICE_VOLUME)) { + return@runBlocking (controller.deviceVolume - controller.deviceInfo.minVolume).toDouble() / controller.deviceInfo.maxVolume + } else { + return@runBlocking 1.0 + } } } set(_property) { val controller = supervisor.mediaController ?: return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { - if (controller.isCommandAvailable(COMMAND_SET_VOLUME)) { - controller.volume = _property.toFloat() + if (controller.isCommandAvailable(COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS)) { + controller.setDeviceVolume(controller.deviceInfo.minVolume + ((controller.deviceInfo.maxVolume - controller.deviceInfo.minVolume) * _property).toInt(), 0) } } } @@ -415,7 +419,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } } - override fun onVolumeChanged(volume: Float) { + override fun onDeviceVolumeChanged(volume: Int, muted: Boolean) { connectionProvider.acquireDBusConnection { connection -> connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf("Volume"))), listOf())) } From dbe7b9333e168a6a13b8a3d44a383a522983bbe8 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Sat, 4 May 2024 11:08:31 +0200 Subject: [PATCH 09/10] Reconnect D-Bus upon connection loss --- .../sync/connectivity/SlirpService.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java index 8cbcb3da..3c5c76b6 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java @@ -38,6 +38,7 @@ import org.freedesktop.dbus.exceptions.DBusException; import java.io.FileDescriptor; +import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.UUID; @@ -81,8 +82,14 @@ public SlirpService(Context ctx, IAsteroidDevice device) { public void handleMessage(@NonNull Message msg) { switch (msg.arg1) { case DBUS_HANDLER_MSG_OPEN -> { - if (dBusConnection != null) - break; + if (dBusConnection != null) { + try { + dBusConnection.connect(); + } catch (IOException e) { + Log.e("SlirpService", "Failed to establish a D-Bus connection", e); + } + return; + } try { dBusConnection = AndroidDBusConnectionBuilder @@ -189,6 +196,10 @@ private void resetMtu() { @Override public void sync() { + final Message message = new Message(); + message.arg1 = DBUS_HANDLER_MSG_OPEN; + message.obj = "tcp:host=127.0.0.1,bind=*,port=55556,family=ipv4"; + dBusHandler.sendMessage(message); } @Override From 92f781208a0f68a633e94b4bae5465ae728243b7 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Sun, 5 May 2024 18:46:54 +0200 Subject: [PATCH 10/10] Separate the D-Bus infrastructure from SlirpService --- app/src/main/AndroidManifest.xml | 2 +- app/src/main/assets/icons.properties | 112 ++++++++ .../asteroidos/sync/PermissionsActivity.java | 4 +- .../sync/connectivity/SlirpService.java | 93 +------ .../org/asteroidos/sync/dbus/DBusConnector.kt | 123 +++++++++ .../dbus/DBusNotificationListenerService.kt | 160 +++++++++++ .../sync/dbus/IDBusConnectionProvider.kt | 3 +- .../org/asteroidos/sync/dbus/MediaService.kt | 63 ++++- .../sync/dbus/NotificationService.kt | 129 --------- .../asteroidos/sync/media/MediaSupervisor.kt | 10 +- .../asteroidos/sync/services/NLService.java | 257 ------------------ .../sync/services/SynchronizationService.java | 13 +- .../sync/utils/IconToPackageMapper.kt | 38 +++ .../java/org/mpris/mediaplayer2/Player.java | 3 +- 14 files changed, 507 insertions(+), 503 deletions(-) create mode 100644 app/src/main/assets/icons.properties create mode 100644 app/src/main/java/org/asteroidos/sync/dbus/DBusConnector.kt create mode 100644 app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationListenerService.kt delete mode 100644 app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt delete mode 100644 app/src/main/java/org/asteroidos/sync/services/NLService.java create mode 100644 app/src/main/java/org/asteroidos/sync/utils/IconToPackageMapper.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 026a17b3..3b18097c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -68,7 +68,7 @@ android:configChanges="uiMode"/> diff --git a/app/src/main/assets/icons.properties b/app/src/main/assets/icons.properties new file mode 100644 index 00000000..2ca17f9f --- /dev/null +++ b/app/src/main/assets/icons.properties @@ -0,0 +1,112 @@ +# +# AsteroidOSSync +# Copyright (c) 2024 AsteroidOS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +default=ios-mail + +code.name.monkey.retromusic=ios-musical-notes +com.android.chrome=logo-chrome +com.android.dialer=ios-call +com.android.mms=ios-text +com.android.vending=md-appstore +com.chrome.beta=logo-chrome +com.chrome.dev=logo-chrome +com.devhd.feedly=logo-rss +com.dropbox.android=logo-dropbox +com.facebook.groups=logo-facebook +com.facebook.katana=logo-facebook +com.facebook.Mentions=logo-facebook +com.facebook.orca=ios-text +com.facebook.work=logo-facebook +com.google.android.apps.docs.editors.docs=ios-document +com.google.android.apps.giant=md-analytics +com.google.android.apps.maps=ios-map +com.google.android.apps.messaging=ios-text +com.google.android.apps.photos=ios-images +com.google.android.apps.plus=logo-googleplus +com.google.android.calendar=ios-calendar +com.google.android.contacts=ios-contacts +com.google.android.dialer=ios-call +com.google.android.gm=ios-mail +com.google.android.googlequicksearchbox=logo-google +com.google.android.music=ios-musical-notes +com.google.android.talk=ios-quote +com.google.android.videos=ios-film +com.google.android.youtube=logo-youtube +com.instagram.android=logo-instagram +com.instagram.boomerang=logo-instagram +com.instagram.layout=logo-instagram +com.jb.gosms=ios-text +com.joelapenna.foursquared=logo-foursquare +com.keylesspalace.tusky=md-mastodon +com.keylesspalace.tusky.test=md-mastodon +com.linkedin.android.jobs.jobseeker=logo-linkedin +com.linkedin.android.learning=logo-linkedin +com.linkedin.android=logo-linkedin +com.linkedin.android.salesnavigator=logo-linkedin +com.linkedin.Coworkers=logo-linkedin +com.linkedin.leap=logo-linkedin +com.linkedin.pulse=logo-linkedin +com.linkedin.recruiter=logo-linkedin +com.mattermost.rnbeta=logo-mattermost +com.mattermost.rn=logo-mattermost +com.maxfour.music=ios-musical-notes +com.microsoft.office.lync15=logo-skype +com.microsoft.xboxone.smartglass.beta=logo-xbox +com.microsoft.xboxone.smartglass=logo-xbox +com.noinnion.android.greader.reader=logo-rss +com.pinterest=logo-pinterest +com.playstation.mobilemessenger=logo-playstation +com.playstation.remoteplay=logo-playstation +com.playstation.video=logo-playstation +com.reddit.frontpage=logo-reddit +com.runtastic.android=ios-walk +com.runtastic.android.pro2=ios-walk +com.scee.psxandroid=logo-playstation +com.sec.android.app.music=ios-musical-notes +com.skype.android.access=logo-skype +com.skype.raider=logo-skype +com.snapchat.android=logo-snapchat +com.sonyericsson.conversations=ios-text +com.spotify.music=ios-musical-notes +com.tinder=md-flame +com.tumblr=logo-tumblr +com.twitter.android=logo-twitter +com.valvesoftware.android.steam.community=logo-steam +com.vimeo.android.videoapp=logo-vimeo +com.whatsapp=logo-whatsapp +com.yahoo.mobile.client.android.atom=logo-yahoo +com.yahoo.mobile.client.android.finance=logo-yahoo +com.yahoo.mobile.client.android.im=logo-yahoo +com.yahoo.mobile.client.android.mail=logo-yahoo +com.yahoo.mobile.client.android.search=logo-yahoo +com.yahoo.mobile.client.android.sportacular=logo-yahoo +com.yahoo.mobile.client.android.weather=logo-yahoo +de.number26.android=ios-card +flipboard.app=logo-rss +im.vector.app=ios-chatbubbles-outline +net.etuldan.sparss.floss=logo-rss +net.frju.flym=logo-rss +net.slideshare.mobile=logo-linkedin +org.buffer.android=logo-buffer +org.kde.kdeconnect_tp=md-phone-portrait +org.telegram.messenger=ios-paper-plane +org.thoughtcrime.securesms=logo-signal +org.thunderdog.challegram=ios-paper-plane +org.wordpress.android=logo-wordpress +tv.twitch.android.app=logo-twitch +ws.xsoh.etar=ios-calendar \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/PermissionsActivity.java b/app/src/main/java/org/asteroidos/sync/PermissionsActivity.java index 54472e70..c6e53930 100644 --- a/app/src/main/java/org/asteroidos/sync/PermissionsActivity.java +++ b/app/src/main/java/org/asteroidos/sync/PermissionsActivity.java @@ -38,7 +38,7 @@ import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; -import org.asteroidos.sync.services.NLService; +import org.asteroidos.sync.dbus.DBusNotificationListenerService; import java.util.ArrayList; @@ -219,7 +219,7 @@ public void setContext(Context ctx) { @Override public boolean hasAnyPermissionsToGrant() { - ComponentName cn = new ComponentName(mCtx, NLService.class); + ComponentName cn = new ComponentName(mCtx, DBusNotificationListenerService.class); String flat = Settings.Secure.getString(mCtx.getContentResolver(), "enabled_notification_listeners"); return (flat == null || !flat.contains(cn.flattenToString())); } diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java index 3c5c76b6..17295439 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java @@ -19,35 +19,21 @@ package org.asteroidos.sync.connectivity; import android.content.Context; -import android.os.Handler; -import android.os.HandlerThread; import android.os.Message; import android.system.Os; import android.system.OsConstants; import android.system.StructPollfd; import android.util.Log; -import androidx.annotation.NonNull; - import org.asteroidos.sync.asteroid.IAsteroidDevice; -import org.asteroidos.sync.dbus.IDBusConnectionCallback; -import org.asteroidos.sync.dbus.IDBusConnectionProvider; import org.asteroidos.sync.utils.AsteroidUUIDS; -import org.freedesktop.dbus.connections.impl.AndroidDBusConnectionBuilder; -import org.freedesktop.dbus.connections.impl.DBusConnection; -import org.freedesktop.dbus.exceptions.DBusException; import java.io.FileDescriptor; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.UUID; -import java.util.function.Consumer; - -import kotlin.Unit; -import kotlin.jvm.functions.Function1; -public class SlirpService implements IConnectivityService, IDBusConnectionProvider { +public class SlirpService implements IConnectivityService { private final IAsteroidDevice mDevice; @@ -55,68 +41,18 @@ public class SlirpService implements IConnectivityService, IDBusConnectionProvid private final Thread slirpThread; - private final HandlerThread dBusHandlerThread; - - private final Handler dBusHandler; - private volatile int mtu; private final ByteBuffer rx = ByteBuffer.allocateDirect(1500); private final ByteBuffer tx = ByteBuffer.allocateDirect(1500); - private final Consumer dBusConnectionRunnable; - - private DBusConnection dBusConnection = null; - - private final int DBUS_HANDLER_MSG_OPEN = 1; + public static final String SLIRP_DBUS_ADDRESS = "tcp:host=127.0.0.1,bind=*,port=55556,family=ipv4"; public SlirpService(Context ctx, IAsteroidDevice device) { mDevice = device; mCtx = ctx; - dBusHandlerThread = new HandlerThread("D-Bus Connection"); - dBusHandlerThread.start(); - dBusHandler = new Handler(dBusHandlerThread.getLooper()) { - @Override - public void handleMessage(@NonNull Message msg) { - switch (msg.arg1) { - case DBUS_HANDLER_MSG_OPEN -> { - if (dBusConnection != null) { - try { - dBusConnection.connect(); - } catch (IOException e) { - Log.e("SlirpService", "Failed to establish a D-Bus connection", e); - } - return; - } - - try { - dBusConnection = AndroidDBusConnectionBuilder - .forAddress((String) msg.obj).build(); - } catch (DBusException e) { - Log.e("SlirpService", "Failed to establish a D-Bus connection", e); - } - } - } - } - }; - - dBusConnectionRunnable = dBusConnectionCallback -> { - if (dBusConnection == null) { - Log.e("SlirpService", "D-Bus connection not ready yet"); - return; - } - - try { - dBusConnectionCallback.handleConnection(dBusConnection); - } catch (DBusException e) { - Log.e("SlirpService", "D-Bus error", e); - } catch (Throwable e) { - Log.w("SlirpService", "Runtime error in D-Bus callback", e); - } - }; - slirpThread = new Thread(() -> { FileDescriptor fd = getVdeFd(); StructPollfd pollfd = new StructPollfd(); @@ -165,19 +101,14 @@ public void handleMessage(@NonNull Message msg) { }); slirpThread.start(); - - final Message message = new Message(); - message.arg1 = DBUS_HANDLER_MSG_OPEN; - message.obj = "tcp:host=127.0.0.1,bind=*,port=55556,family=ipv4"; - dBusHandler.sendMessage(message); } private void startNative(int mtu) { initNative(mtu - 14); - vdeAddFwd(false, "0.0.0.0", 45722, "10.0.2.3", 22); - vdeAddFwd(false, "0.0.0.0", 55555, "10.0.2.3", 55555); - vdeAddFwd(false, "0.0.0.0", 55556, "10.0.2.3", 55556); + vdeAddFwd(false, "0.0.0.0", 45722, "10.0.2.3", 22); // 457EROID 22H (Asteroid SSH) +// vdeAddFwd(false, "0.0.0.0", 55555, "10.0.2.3", 55555); // system bus + vdeAddFwd(false, "0.0.0.0", 55556, "10.0.2.3", 55556); // session bus } private void resetMtu() { @@ -196,10 +127,6 @@ private void resetMtu() { @Override public void sync() { - final Message message = new Message(); - message.arg1 = DBUS_HANDLER_MSG_OPEN; - message.obj = "tcp:host=127.0.0.1,bind=*,port=55556,family=ipv4"; - dBusHandler.sendMessage(message); } @Override @@ -245,14 +172,4 @@ public UUID getServiceUUID() { private native long vdeSend(ByteBuffer buffer, long offset, long count); private native FileDescriptor getVdeFd(); - - @Override - public void acquireDBusConnection(@NonNull Function1 dBusConnectionConsumer) { - dBusHandler.post(() -> dBusConnectionRunnable.accept(dBusConnectionConsumer::invoke)); - } - - @Override - public void acquireDBusConnectionLater(@NonNull Function1 dBusConnectionConsumer, long delay) { - dBusHandler.postDelayed(() -> dBusConnectionRunnable.accept(dBusConnectionConsumer::invoke), delay); - } } diff --git a/app/src/main/java/org/asteroidos/sync/dbus/DBusConnector.kt b/app/src/main/java/org/asteroidos/sync/dbus/DBusConnector.kt new file mode 100644 index 00000000..2c4d96e5 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/DBusConnector.kt @@ -0,0 +1,123 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.dbus + +import android.os.Handler +import android.os.HandlerThread +import android.os.Message +import android.util.Log +import com.google.common.base.Stopwatch +import org.freedesktop.dbus.connections.impl.AndroidDBusConnectionBuilder +import org.freedesktop.dbus.connections.impl.DBusConnection +import org.freedesktop.dbus.exceptions.DBusException +import java.io.IOException + +class DBusConnector(val address: String) : IDBusConnectionProvider { + + private val dBusHandlerThread: HandlerThread = HandlerThread("D-Bus connection") + + private val dBusHandler: Handler + + private var dBusConnection: DBusConnection? = null + + private var dBusCbId: Long = 0 + + init { + dBusHandlerThread.start() + dBusHandler = object: Handler(dBusHandlerThread.looper) { + override fun handleMessage(msg: Message) { + when (msg.arg1) { + DBUS_HANDLER_MSG_CLOSE -> { + if (dBusConnection != null) { + dBusConnection!!.disconnect() + Log.i("SlirpService", "Closed the D-Bus connection") + } + } + + DBUS_HANDLER_MSG_OPEN -> { + Log.i("SlirpService", "(Re-)opening a D-Bus connection") + val timer = Stopwatch.createStarted() + if (dBusConnection != null) { + try { + dBusConnection!!.connect() + } catch (e: IOException) { + Log.e("SlirpService", "Failed to establish a D-Bus connection", e) + } + Log.i("SlirpService", "Done re-opening the D-Bus connection; took " + timer.stop()) + return + } + try { + dBusConnection = AndroidDBusConnectionBuilder + .forAddress(msg.obj as String).build() + } catch (e: DBusException) { + Log.e("SlirpService", "Failed to establish a D-Bus connection", e) + } + Log.i("SlirpService", "Done opening a D-Bus connection; took " + timer.stop()) + } + } + } + } + } + + private fun acquireDBusConnectionImpl(dBusConnectionConsumer: (connection: DBusConnection) -> Unit) { + if (dBusConnection == null) { + Log.w("SlirpService", "D-Bus connection not set up yet") + return + } + + val cbId: Long + synchronized(this) { cbId = dBusCbId++ } + val timer = Stopwatch.createStarted() + Log.d("SlirpService", "D-Bus callback start: $cbId") + try { + dBusConnectionConsumer(dBusConnection!!) + } catch (e: DBusException) { + Log.e("SlirpService", "D-Bus error", e) + } catch (e: Throwable) { + Log.e("SlirpService", "Runtime error in D-Bus callback", e) + } + Log.d("SlirpService", "D-Bus callback end: " + cbId + "; took " + timer.stop()) + } + + override fun acquireDBusConnection(dBusConnectionConsumer: (connection: DBusConnection) -> Unit) { + dBusHandler.post { acquireDBusConnectionImpl(dBusConnectionConsumer) } + } + + override fun acquireDBusConnectionLater(dBusConnectionConsumer: (connection: DBusConnection) -> Unit, delay: Long) { + dBusHandler.postDelayed({ acquireDBusConnectionImpl(dBusConnectionConsumer) }, delay) + } + + override fun sync() { + val msg = Message() + msg.arg1 = DBUS_HANDLER_MSG_OPEN + msg.obj = address + dBusHandler.sendMessage(msg) + } + + override fun unsync() { + val msg = Message() + msg.arg1 = DBUS_HANDLER_MSG_CLOSE + dBusHandler.sendMessage(msg) + } + + companion object { + private const val DBUS_HANDLER_MSG_CLOSE = 0 + private const val DBUS_HANDLER_MSG_OPEN = 1 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationListenerService.kt b/app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationListenerService.kt new file mode 100644 index 00000000..d3f09e5a --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationListenerService.kt @@ -0,0 +1,160 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.dbus + +import android.app.Notification +import android.content.ComponentName +import android.content.pm.PackageManager +import android.os.Handler +import android.os.Looper +import android.service.notification.NotificationListenerService +import android.service.notification.StatusBarNotification +import androidx.core.app.NotificationCompat +import com.google.common.collect.BiMap +import com.google.common.collect.HashBiMap +import org.asteroidos.sync.NotificationPreferences +import org.asteroidos.sync.NotificationPreferences.NotificationOption.DEFAULT +import org.asteroidos.sync.NotificationPreferences.NotificationOption.NORMAL_VIBRATION +import org.asteroidos.sync.NotificationPreferences.NotificationOption.RINGTONE_VIBRATION +import org.asteroidos.sync.NotificationPreferences.NotificationOption.SILENT_NOTIFICATION +import org.asteroidos.sync.NotificationPreferences.NotificationOption.STRONG_VIBRATION +import org.asteroidos.sync.connectivity.SlirpService +import org.asteroidos.sync.utils.IconToPackageMapper +import org.asteroidos.sync.utils.NotificationParser +import org.freedesktop.Notifications +import org.freedesktop.dbus.connections.impl.DBusConnection +import org.freedesktop.dbus.interfaces.DBusSigHandler +import org.freedesktop.dbus.types.UInt32 +import org.freedesktop.dbus.types.Variant + +class DBusNotificationListenerService : NotificationListenerService(), DBusSigHandler { + @Volatile + private var listenerConnected = false + + private lateinit var connector: DBusConnector + + private lateinit var iconMapper: IconToPackageMapper + + private val mapping: BiMap = HashBiMap.create() + + override fun onCreate() { + iconMapper = IconToPackageMapper(baseContext) + connector = DBusConnector(SlirpService.SLIRP_DBUS_ADDRESS) // hardcoded (for now) + } + + override fun onNotificationPosted(sbn: StatusBarNotification?) { + super.onNotificationPosted(sbn) + + if (sbn == null) + return + + val notification = sbn.notification + val packageName = sbn.packageName + + val appName: String + try { + val pm = applicationContext.packageManager + val ai = pm.getApplicationInfo(packageName, 0) + appName = pm.getApplicationLabel(ai).toString() + } catch (e: PackageManager.NameNotFoundException) { + return + } + if (notification.priority < Notification.PRIORITY_DEFAULT || + (notification.flags and Notification.FLAG_ONGOING_EVENT != 0 + && !allowedOngoingApps.contains(packageName)) || + NotificationCompat.getLocalOnly(notification) || + NotificationCompat.isGroupSummary(notification)) return + + NotificationPreferences.putPackageToSeen(applicationContext, packageName) + val notificationOption = NotificationPreferences.getNotificationPreferenceForApp(applicationContext, packageName) + if (notificationOption == NotificationPreferences.NotificationOption.NO_NOTIFICATIONS) return + + val notifParser = NotificationParser(notification) + val summary = notifParser.summary + val body = notifParser.body + val vibration: String = when (notificationOption) { + SILENT_NOTIFICATION -> "notif_silent" + NORMAL_VIBRATION, DEFAULT, null -> "notif_normal" + STRONG_VIBRATION -> "notif_strong" + RINGTONE_VIBRATION -> "ringtone" + else -> throw IllegalArgumentException("Not all options handled") + } + + connector.acquireDBusConnection { notify: DBusConnection -> + synchronized(mapping) { + mapping[sbn.key] = notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications::class.java) + .Notify(appName, mapping.getOrDefault(sbn.key, UInt32(0)), iconMapper.iconForPackage(packageName), summary, body, emptyList(), + mutableMapOf( + "x-nemo-feedback" to Variant(vibration), + "x-nemo-preview-body" to Variant(body), + "x-nemo-preview-summary" to Variant(summary), + "urgency" to Variant(3.toByte())) as Map>?, 0) + } + } + } + + override fun onNotificationRemoved(sbn: StatusBarNotification?) { + super.onNotificationRemoved(sbn) + + val key = sbn?.key ?: return + // Avoid an infinite loop when the user dismisses the notification on the watch + if (mapping.containsKey(key)) { + var id: UInt32? + synchronized(mapping) { + id = mapping[key] + mapping.remove(key) + } + connector.acquireDBusConnection { notify: DBusConnection -> + notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications::class.java) + .CloseNotification(id) + } + } + } + + override fun onListenerDisconnected() { + listenerConnected = false + connector.unsync() + // Notification listener disconnected - requesting rebind + requestRebind(ComponentName(this, NotificationListenerService::class.java)) + } + + override fun onListenerConnected() { + listenerConnected = true + connector.sync() + + connector.acquireDBusConnection { notify: DBusConnection -> notify.addSigHandler(Notifications.NotificationClosed::class.java, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications::class.java), this@DBusNotificationListenerService) } + } + + override fun handle(s: Notifications.NotificationClosed?) { + synchronized(mapping) { + if (s != null + && s.reason.toInt() == 2 + && mapping.containsValue(s.id)) { + val key = mapping.inverse()[s.id] + mapping.inverse().remove(s.id) + Handler(Looper.getMainLooper()).post { cancelNotification(key) } + } + } + } + + companion object { + + private val allowedOngoingApps = listOf("com.google.android.apps.maps", "org.thoughtcrime.securesms") + } +} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.kt b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.kt index b2d08459..dbab89f9 100644 --- a/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.kt +++ b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.kt @@ -17,9 +17,10 @@ */ package org.asteroidos.sync.dbus +import org.asteroidos.sync.connectivity.IService import org.freedesktop.dbus.connections.impl.DBusConnection -interface IDBusConnectionProvider { +interface IDBusConnectionProvider : IService { fun acquireDBusConnection(dBusConnectionConsumer: (connection: DBusConnection) -> Unit) fun acquireDBusConnectionLater(dBusConnectionConsumer: (connection: DBusConnection) -> Unit, delay: Long) } \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt index cf4c4f18..a0403f26 100644 --- a/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt +++ b/app/src/main/java/org/asteroidos/sync/dbus/MediaService.kt @@ -20,7 +20,9 @@ package org.asteroidos.sync.dbus import android.content.Context import android.os.Build import android.os.Handler +import android.util.Log import androidx.media3.common.MediaItem +import androidx.media3.common.MediaMetadata import androidx.media3.common.Player.* import com.google.common.base.Optional import com.google.common.collect.Lists @@ -45,24 +47,39 @@ import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.memberProperties class MediaService(private val mCtx: Context, private val supervisor: MediaSupervisor, private val connectionProvider: IDBusConnectionProvider) : IMediaService, MediaPlayer2, Player { - private val mNReceiver: NotificationService.NotificationReceiver? = null private val hashing = Hashing.goodFastHash(64) - private lateinit var busSuffix: String + private var busSuffix: String? = null override fun sync() { - busSuffix = "x" + Hashing.murmur3_32_fixed(42).hashLong(Date().time).toString() - connectionProvider.acquireDBusConnection { connection: DBusConnection -> + if (busSuffix != null) { + Log.w("MediaService", "Already in sync") + return + } + busSuffix = Hashing.murmur3_32_fixed(42).hashLong(Date().time).toString() +// busSuffix = "asteroid-os-sync" + connectionProvider.sync() + connectionProvider.acquireDBusConnectionLater({ connection: DBusConnection -> connection.requestBusName("org.mpris.MediaPlayer2.x$busSuffix") connection.exportObject("/org/mpris/MediaPlayer2", this@MediaService) - } + + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.absent()), listOf())) + + Log.i("MediaService", "Registered with D-Bus") + }, 500) } override fun unsync() { + if (busSuffix == null) { + Log.w("MediaService", "Already in de-sync") + return + } connectionProvider.acquireDBusConnection { connection: DBusConnection -> connection.unExportObject("/org/mpris/MediaPlayer2") connection.releaseBusName("org.mpris.MediaPlayer2.x$busSuffix") } + connectionProvider.unsync() + busSuffix = null } @MyDBusProperty("org.mpris.MediaPlayer2") @@ -99,9 +116,16 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper @MyDBusProperty("org.mpris.MediaPlayer2.Player") val PlaybackStatus: String get() { val controller = supervisor.mediaController ?: return "Stopped" - return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { - if (controller.isPlaying) return@runBlocking "Playing" else if (controller.playbackState == STATE_READY && !controller.playWhenReady) return@runBlocking "Paused" else return@runBlocking "Stopped" + val ret = runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + if (controller.isPlaying) + return@runBlocking "Playing" + else if (controller.playbackState == STATE_READY && !controller.playWhenReady) + return@runBlocking "Paused" + else + return@runBlocking "Stopped" } + Log.i("MediaService", "Get playback status: $ret") + return ret } @MyDBusProperty("org.mpris.MediaPlayer2.Player") @@ -177,7 +201,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper val dummy = Collections.singletonMap>("mpris:trackid", Variant(DBusPath("/org/mpris/MediaPlayer2/TrackList/NoTrack"))) val controller = supervisor.mediaController ?: return dummy - return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { + val metadata = runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.currentMediaItem != null) { val metadata = mutableMapOf>( "mpris:trackid" to Variant(currentMediaIdObjectPath), @@ -199,15 +223,22 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper return@runBlocking dummy } } + Log.i("MediaService", "GetMetadata: " + if (metadata["mpris:trackid"] == dummy["mpris:trackid"]) "dummy" else "available") + return metadata } + @Volatile + private var volCached = -1 + @MyDBusProperty("org.mpris.MediaPlayer2.Player") var Volume: Double get() { - // TODO:XXX: val controller = supervisor.mediaController ?: return 0.0 return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_GET_DEVICE_VOLUME)) { - return@runBlocking (controller.deviceVolume - controller.deviceInfo.minVolume).toDouble() / controller.deviceInfo.maxVolume + if (volCached < 0) { + volCached = controller.deviceVolume + } + return@runBlocking (volCached - controller.deviceInfo.minVolume).toDouble() / controller.deviceInfo.maxVolume } else { return@runBlocking 1.0 } @@ -216,7 +247,8 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper val controller = supervisor.mediaController ?: return runBlocking(Handler(controller.applicationLooper).asCoroutineDispatcher()) { if (controller.isCommandAvailable(COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS)) { - controller.setDeviceVolume(controller.deviceInfo.minVolume + ((controller.deviceInfo.maxVolume - controller.deviceInfo.minVolume) * _property).toInt(), 0) + val vol = controller.deviceInfo.minVolume + ((controller.deviceInfo.maxVolume - controller.deviceInfo.minVolume) * _property).toInt() + controller.setDeviceVolume(vol, 0) } } } @@ -409,7 +441,13 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { connectionProvider.acquireDBusConnection { connection -> - connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf("Metadata", "PlaybackStatus"))), listOf())) + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf("Metadata"))), listOf())) + } + } + + override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) { + connectionProvider.acquireDBusConnection { connection -> + connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf("Metadata"))), listOf())) } } @@ -420,6 +458,7 @@ class MediaService(private val mCtx: Context, private val supervisor: MediaSuper } override fun onDeviceVolumeChanged(volume: Int, muted: Boolean) { + volCached = volume connectionProvider.acquireDBusConnection { connection -> connection.sendMessage(PropertiesChanged(objectPath, "org.mpris.MediaPlayer2.Player", getProperties("org.mpris.MediaPlayer2.Player", Optional.of(listOf("Volume"))), listOf())) } diff --git a/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt b/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt deleted file mode 100644 index 33798c8b..00000000 --- a/app/src/main/java/org/asteroidos/sync/dbus/NotificationService.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * AsteroidOSSync - * Copyright (c) 2024 AsteroidOS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.asteroidos.sync.dbus - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.util.Log -import com.google.common.collect.BiMap -import com.google.common.collect.HashBiMap -import org.asteroidos.sync.NotificationPreferences -import org.asteroidos.sync.NotificationPreferences.NotificationOption -import org.asteroidos.sync.services.INotificationHandler -import org.freedesktop.Notifications -import org.freedesktop.Notifications.NotificationClosed -import org.freedesktop.dbus.connections.impl.DBusConnection -import org.freedesktop.dbus.interfaces.DBusSigHandler -import org.freedesktop.dbus.types.UInt32 -import org.freedesktop.dbus.types.Variant -import java.util.Arrays -import java.util.Map -import java.util.Objects - -class NotificationService(private val mCtx: Context, private val connectionProvider: IDBusConnectionProvider) : INotificationHandler, DBusSigHandler { - private val mapping: BiMap = HashBiMap.create() - private var mNReceiver: NotificationReceiver? = null - - override fun postNotification(context: Context, intent: Intent) { - val event = intent.getStringExtra("event") - if (event == "posted") { - val packageName = intent.getStringExtra("packageName") - NotificationPreferences.putPackageToSeen(context, packageName) - val notificationOption = NotificationPreferences.getNotificationPreferenceForApp(context, packageName) - if (notificationOption == NotificationOption.NO_NOTIFICATIONS) return - val key = intent.getStringExtra("key")!! - val appName = intent.getStringExtra("appName") - val appIcon = intent.getStringExtra("appIcon") - val summary = intent.getStringExtra("summary") - val body = intent.getStringExtra("body") - val vibration: String - vibration = if (notificationOption == NotificationOption.SILENT_NOTIFICATION) "notif_silent" else if (notificationOption == null || notificationOption == NotificationOption.NORMAL_VIBRATION || notificationOption == NotificationOption.DEFAULT) "notif_normal" else if (notificationOption == NotificationOption.STRONG_VIBRATION) "notif_strong" else if (notificationOption == NotificationOption.RINGTONE_VIBRATION) "ringtone" else throw IllegalArgumentException("Not all options handled") - connectionProvider.acquireDBusConnection { notify: DBusConnection -> - synchronized(mapping) { - mapping[key] = notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications::class.java) - .Notify(appName, mapping.getOrDefault(key, UInt32(0)), appIcon, summary, body, emptyList(), - Map.of>( - "x-nemo-feedback", Variant(vibration), - "x-nemo-preview-body", Variant(body), - "x-nemo-preview-summary", Variant(summary), - "urgency", Variant(3.toByte())), 0) - } - } - } else if (event == "removed") { - val key = Objects.requireNonNull(intent.getStringExtra("key")) - // Avoid an infinite loop when the user dismisses the notification on the watch - if (mapping.containsKey(key)) { - var id: UInt32? - synchronized(mapping) { - id = mapping[key] - mapping.remove(key) - } - connectionProvider.acquireDBusConnection { notify: DBusConnection -> - notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications::class.java) - .CloseNotification(id) - } - } - } - } - - override fun handle(s: NotificationClosed?) { - synchronized(mapping) { - if (s != null - && s.reason.toInt() == 2 - && mapping.containsValue(s.id)) { - val dismiss = Intent("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE") - dismiss.putExtra("command", "dismiss") - dismiss.putExtra("key", mapping.inverse()[s.id]) - mapping.inverse().remove(s.id) - mCtx.sendBroadcast(dismiss) - } - } - } - - override fun sync() { - if (mNReceiver == null) { - val filter = IntentFilter() - filter.addAction("org.asteroidos.sync.NOTIFICATION_LISTENER") - mNReceiver = NotificationReceiver() - mCtx.registerReceiver(mNReceiver, filter) - val i = Intent("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE") - i.putExtra("command", "refresh") - mCtx.sendBroadcast(i) - } - connectionProvider.acquireDBusConnection { notify: DBusConnection -> notify.addSigHandler(NotificationClosed::class.java, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications::class.java), this@NotificationService) } - } - - override fun unsync() { - connectionProvider.acquireDBusConnection { notify: DBusConnection -> notify.removeSigHandler(NotificationClosed::class.java, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications::class.java), this@NotificationService) } - if (mNReceiver != null) { - try { - mCtx.unregisterReceiver(mNReceiver) - } catch (ignored: IllegalArgumentException) { - } - mNReceiver = null - } - } - - internal inner class NotificationReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - postNotification(context, intent) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/asteroidos/sync/media/MediaSupervisor.kt b/app/src/main/java/org/asteroidos/sync/media/MediaSupervisor.kt index 852e50d1..855e1b45 100644 --- a/app/src/main/java/org/asteroidos/sync/media/MediaSupervisor.kt +++ b/app/src/main/java/org/asteroidos/sync/media/MediaSupervisor.kt @@ -31,14 +31,11 @@ import androidx.media3.session.MediaController import androidx.media3.session.SessionToken import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.guava.await import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.asteroidos.sync.connectivity.IService -import org.asteroidos.sync.services.NLService -import java.util.Objects +import org.asteroidos.sync.dbus.DBusNotificationListenerService class MediaSupervisor(private val mCtx: Context) : IService, OnActiveSessionsChangedListener { @@ -76,6 +73,7 @@ class MediaSupervisor(private val mCtx: Context) : IService, OnActiveSessionsCha mediaControllerPackageName = mediaController.connectedToken?.packageName } } else { + this@MediaSupervisor.mediaController?.removeListener(mMediaCallback!!) mediaControllerPackageName = null mediaController = null mMediaCallback!!.onReset() @@ -86,11 +84,11 @@ class MediaSupervisor(private val mCtx: Context) : IService, OnActiveSessionsCha if (mMediaSessionManager == null) { try { mMediaSessionManager = mCtx.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager - val controllers = mMediaSessionManager?.getActiveSessions(ComponentName(mCtx, NLService::class.java)) + val controllers = mMediaSessionManager?.getActiveSessions(ComponentName(mCtx, DBusNotificationListenerService::class.java)) val handler = Handler(Looper.getMainLooper()) handler.post { onActiveSessionsChanged(controllers) - mMediaSessionManager?.addOnActiveSessionsChangedListener(this, ComponentName(mCtx, NLService::class.java)) + mMediaSessionManager?.addOnActiveSessionsChangedListener(this, ComponentName(mCtx, DBusNotificationListenerService::class.java)) } } catch (e: SecurityException) { Log.w(TAG, "No Notification Access") diff --git a/app/src/main/java/org/asteroidos/sync/services/NLService.java b/app/src/main/java/org/asteroidos/sync/services/NLService.java deleted file mode 100644 index a1104861..00000000 --- a/app/src/main/java/org/asteroidos/sync/services/NLService.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * AsteroidOSSync - * Copyright (c) 2023 AsteroidOS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.asteroidos.sync.services; - -import android.app.Notification; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Handler; -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; - -import androidx.core.app.NotificationCompat; - -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; - -import org.asteroidos.sync.utils.NotificationParser; - -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -public class NLService extends NotificationListenerService { - private NLServiceReceiver nlServiceReceiver; - private Map iconFromPackage; - private volatile boolean listenerConnected = false; - - @Override - public void onCreate() { - super.onCreate(); - nlServiceReceiver = new NLServiceReceiver(); - IntentFilter filter = new IntentFilter(); - filter.addAction("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE"); - registerReceiver(nlServiceReceiver, filter); - - iconFromPackage = new Hashtable<>(); - iconFromPackage.put("code.name.monkey.retromusic", "ios-musical-notes"); - iconFromPackage.put("com.android.chrome", "logo-chrome"); - iconFromPackage.put("com.android.dialer", "ios-call"); - iconFromPackage.put("com.android.mms", "ios-text"); - iconFromPackage.put("com.android.vending", "md-appstore"); - iconFromPackage.put("com.chrome.beta", "logo-chrome"); - iconFromPackage.put("com.chrome.dev", "logo-chrome"); - iconFromPackage.put("com.devhd.feedly", "logo-rss"); - iconFromPackage.put("com.dropbox.android", "logo-dropbox"); - iconFromPackage.put("com.facebook.groups", "logo-facebook"); - iconFromPackage.put("com.facebook.katana", "logo-facebook"); - iconFromPackage.put("com.facebook.Mentions", "logo-facebook"); - iconFromPackage.put("com.facebook.orca", "ios-text"); - iconFromPackage.put("com.facebook.work", "logo-facebook"); - iconFromPackage.put("com.google.android.apps.docs.editors.docs", "ios-document"); - iconFromPackage.put("com.google.android.apps.giant", "md-analytics"); - iconFromPackage.put("com.google.android.apps.maps", "ios-map"); - iconFromPackage.put("com.google.android.apps.messaging", "ios-text"); - iconFromPackage.put("com.google.android.apps.photos", "ios-images"); - iconFromPackage.put("com.google.android.apps.plus", "logo-googleplus"); - iconFromPackage.put("com.google.android.calendar", "ios-calendar"); - iconFromPackage.put("com.google.android.contacts", "ios-contacts"); - iconFromPackage.put("com.google.android.dialer", "ios-call"); - iconFromPackage.put("com.google.android.gm", "ios-mail"); - iconFromPackage.put("com.google.android.googlequicksearchbox", "logo-google"); - iconFromPackage.put("com.google.android.music", "ios-musical-notes"); - iconFromPackage.put("com.google.android.talk", "ios-quote"); - iconFromPackage.put("com.google.android.videos", "ios-film"); - iconFromPackage.put("com.google.android.youtube", "logo-youtube"); - iconFromPackage.put("com.instagram.android", "logo-instagram"); - iconFromPackage.put("com.instagram.boomerang", "logo-instagram"); - iconFromPackage.put("com.instagram.layout", "logo-instagram"); - iconFromPackage.put("com.jb.gosms", "ios-text"); - iconFromPackage.put("com.joelapenna.foursquared", "logo-foursquare"); - iconFromPackage.put("com.keylesspalace.tusky", "md-mastodon"); - iconFromPackage.put("com.keylesspalace.tusky.test", "md-mastodon"); - iconFromPackage.put("com.linkedin.android.jobs.jobseeker", "logo-linkedin"); - iconFromPackage.put("com.linkedin.android.learning", "logo-linkedin"); - iconFromPackage.put("com.linkedin.android", "logo-linkedin"); - iconFromPackage.put("com.linkedin.android.salesnavigator", "logo-linkedin"); - iconFromPackage.put("com.linkedin.Coworkers", "logo-linkedin"); - iconFromPackage.put("com.linkedin.leap", "logo-linkedin"); - iconFromPackage.put("com.linkedin.pulse", "logo-linkedin"); - iconFromPackage.put("com.linkedin.recruiter", "logo-linkedin"); - iconFromPackage.put("com.mattermost.rnbeta", "logo-mattermost"); - iconFromPackage.put("com.mattermost.rn", "logo-mattermost"); - iconFromPackage.put("com.maxfour.music", "ios-musical-notes"); - iconFromPackage.put("com.microsoft.office.lync15", "logo-skype"); - iconFromPackage.put("com.microsoft.xboxone.smartglass.beta", "logo-xbox"); - iconFromPackage.put("com.microsoft.xboxone.smartglass", "logo-xbox"); - iconFromPackage.put("com.noinnion.android.greader.reader", "logo-rss"); - iconFromPackage.put("com.pinterest", "logo-pinterest"); - iconFromPackage.put("com.playstation.mobilemessenger", "logo-playstation"); - iconFromPackage.put("com.playstation.remoteplay", "logo-playstation"); - iconFromPackage.put("com.playstation.video", "logo-playstation"); - iconFromPackage.put("com.reddit.frontpage", "logo-reddit"); - iconFromPackage.put("com.runtastic.android", "ios-walk"); - iconFromPackage.put("com.runtastic.android.pro2", "ios-walk"); - iconFromPackage.put("com.scee.psxandroid", "logo-playstation"); - iconFromPackage.put("com.sec.android.app.music", "ios-musical-notes"); - iconFromPackage.put("com.skype.android.access", "logo-skype"); - iconFromPackage.put("com.skype.raider", "logo-skype"); - iconFromPackage.put("com.snapchat.android", "logo-snapchat"); - iconFromPackage.put("com.sonyericsson.conversations", "ios-text"); - iconFromPackage.put("com.spotify.music", "ios-musical-notes"); - iconFromPackage.put("com.tinder", "md-flame"); - iconFromPackage.put("com.tumblr", "logo-tumblr"); - iconFromPackage.put("com.twitter.android", "logo-twitter"); - iconFromPackage.put("com.valvesoftware.android.steam.community", "logo-steam"); - iconFromPackage.put("com.vimeo.android.videoapp", "logo-vimeo"); - iconFromPackage.put("com.whatsapp", "logo-whatsapp"); - iconFromPackage.put("com.yahoo.mobile.client.android.atom", "logo-yahoo"); - iconFromPackage.put("com.yahoo.mobile.client.android.finance", "logo-yahoo"); - iconFromPackage.put("com.yahoo.mobile.client.android.im", "logo-yahoo"); - iconFromPackage.put("com.yahoo.mobile.client.android.mail", "logo-yahoo"); - iconFromPackage.put("com.yahoo.mobile.client.android.search", "logo-yahoo"); - iconFromPackage.put("com.yahoo.mobile.client.android.sportacular", "logo-yahoo"); - iconFromPackage.put("com.yahoo.mobile.client.android.weather", "logo-yahoo"); - iconFromPackage.put("de.number26.android", "ios-card"); - iconFromPackage.put("flipboard.app", "logo-rss"); - iconFromPackage.put("im.vector.app", "ios-chatbubbles-outline"); - iconFromPackage.put("net.etuldan.sparss.floss", "logo-rss"); - iconFromPackage.put("net.frju.flym", "logo-rss"); - iconFromPackage.put("net.slideshare.mobile", "logo-linkedin"); - iconFromPackage.put("org.buffer.android", "logo-buffer"); - iconFromPackage.put("org.kde.kdeconnect_tp", "md-phone-portrait"); - iconFromPackage.put("org.telegram.messenger", "ios-paper-plane"); - iconFromPackage.put("org.thoughtcrime.securesms", "logo-signal"); - iconFromPackage.put("org.thunderdog.challegram", "ios-paper-plane"); - iconFromPackage.put("org.wordpress.android", "logo-wordpress"); - iconFromPackage.put("tv.twitch.android.app", "logo-twitch"); - iconFromPackage.put("ws.xsoh.etar", "ios-calendar"); - } - - @Override - public void onDestroy() { - super.onDestroy(); - unregisterReceiver(nlServiceReceiver); - iconFromPackage.clear(); - } - - @Override - public void onNotificationPosted(StatusBarNotification sbn) { - final Notification notification = sbn.getNotification(); - String packageName = sbn.getPackageName(); - - String[] allowedOngoingApps = {"com.google.android.apps.maps", "org.thoughtcrime.securesms"}; - if ((notification.priority < Notification.PRIORITY_DEFAULT) || - ((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0 - && !Arrays.asList(allowedOngoingApps).contains(packageName)) || - (NotificationCompat.getLocalOnly(notification)) || - (NotificationCompat.isGroupSummary(notification))) - return; - - NotificationParser notifParser = new NotificationParser(notification); - String summary = notifParser.summary; - String body = notifParser.body; - String key = sbn.getKey(); - String appIcon = iconFromPackage.get(packageName); - - String appName = ""; - try { - final PackageManager pm = getApplicationContext().getPackageManager(); - ApplicationInfo ai = pm.getApplicationInfo(packageName, 0); - appName = pm.getApplicationLabel(ai).toString(); - } catch (PackageManager.NameNotFoundException ignored) { - } - - if (summary == null) summary = ""; - else summary = summary.trim(); - if (body == null) body = ""; - else body = body.trim(); - if (packageName == null) packageName = ""; - if (appIcon == null) appIcon = ""; - - Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER"); - i.putExtra("event", "posted"); - i.putExtra("packageName", packageName); - i.putExtra("key", key); - i.putExtra("appName", appName); - i.putExtra("appIcon", appIcon); - i.putExtra("summary", summary); - i.putExtra("body", body); - - sendBroadcast(i); - } - - @Override - public void onNotificationRemoved(StatusBarNotification sbn) { - Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER"); - i.putExtra("event", "removed"); - i.putExtra("key", sbn.getKey()); - sendBroadcast(i); - } - - @Override - public void onListenerDisconnected() { - listenerConnected = false; - // Notification listener disconnected - requesting rebind - requestRebind(new ComponentName(this, NotificationListenerService.class)); - } - - @Override - public void onListenerConnected() { - listenerConnected = true; - } - - @SuppressWarnings("StatementWithEmptyBody") - class NLServiceReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (Objects.equals(intent.getStringExtra("command"), "refresh")) { - Handler handler = new Handler(); - handler.postDelayed(() -> { - while (!listenerConnected) { - // Sleep the spin - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - Thread.onSpinWait(); - }else { - // Will not delay here, as we can cause the entire UI to freeze - } - } - StatusBarNotification[] notifs = getActiveNotifications(); - for (StatusBarNotification notif : notifs) - onNotificationPosted(notif); - }, 500); - } else if (Objects.equals(intent.getStringExtra("command"), "dismiss")) { - String key = intent.getStringExtra("key"); - if (key != null) { - new Handler().post(() -> cancelNotification(key)); - } - } - } - } -} diff --git a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java index 44f50fd3..2ffaf0ec 100644 --- a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java +++ b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java @@ -52,8 +52,9 @@ import org.asteroidos.sync.connectivity.SlirpService; import org.asteroidos.sync.connectivity.TimeService; import org.asteroidos.sync.connectivity.WeatherService; +import org.asteroidos.sync.dbus.DBusConnector; +import org.asteroidos.sync.dbus.IDBusConnectionProvider; import org.asteroidos.sync.dbus.MediaService; -import org.asteroidos.sync.dbus.NotificationService; import org.asteroidos.sync.media.MediaSupervisor; import java.util.ArrayList; @@ -282,21 +283,21 @@ public void onCreate() { mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(defaultDevMacAddr); } - SlirpService slirpService = new SlirpService(getApplicationContext(), this); - if (bleServices.isEmpty()) { // Register Services registerBleService(new WeatherService(getApplicationContext(), this)); registerBleService(new ScreenshotService(getApplicationContext(), this)); registerBleService(new TimeService(getApplicationContext(), this)); - registerBleService(slirpService); + registerBleService(new SlirpService(getApplicationContext(), this)); } + final IDBusConnectionProvider mediaDBusConnector = new DBusConnector(SlirpService.SLIRP_DBUS_ADDRESS); + if (nonBleServices.isEmpty()) { nonBleServices.add(new SilentModeService(getApplicationContext())); - nonBleServices.add(new NotificationService(getApplicationContext(), slirpService)); +// nonBleServices.add(mediaDBusConnector); MediaSupervisor supervisor = new MediaSupervisor(getApplicationContext()); - MediaService service = new MediaService(getApplicationContext(), supervisor, slirpService); + MediaService service = new MediaService(getApplicationContext(), supervisor, mediaDBusConnector); supervisor.setMMediaCallback(service); nonBleServices.add(service); nonBleServices.add(supervisor); diff --git a/app/src/main/java/org/asteroidos/sync/utils/IconToPackageMapper.kt b/app/src/main/java/org/asteroidos/sync/utils/IconToPackageMapper.kt new file mode 100644 index 00000000..0f2fe88b --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/utils/IconToPackageMapper.kt @@ -0,0 +1,38 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.asteroidos.sync.utils + +import android.content.Context +import java.util.Properties + +class IconToPackageMapper(val context: Context) { + + private val mapping = Properties() + + private val defaultIcon: String + + init { + mapping.load(context.assets.open("icons.properties")) + defaultIcon = mapping.getProperty("default") + } + + fun iconForPackage(pkg: String): String { + return mapping.getProperty(pkg, defaultIcon) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/mpris/mediaplayer2/Player.java b/app/src/main/java/org/mpris/mediaplayer2/Player.java index 29a1b1df..fbc7e5bb 100644 --- a/app/src/main/java/org/mpris/mediaplayer2/Player.java +++ b/app/src/main/java/org/mpris/mediaplayer2/Player.java @@ -8,6 +8,7 @@ import org.freedesktop.dbus.annotations.DBusProperty.Access; import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.interfaces.DBusInterface; +import org.freedesktop.dbus.interfaces.Properties; import org.freedesktop.dbus.messages.DBusSignal; import org.freedesktop.dbus.types.Variant; @@ -30,7 +31,7 @@ @DBusProperty(name = "CanPause", type = Boolean.class, access = Access.READ) @DBusProperty(name = "CanSeek", type = Boolean.class, access = Access.READ) @DBusProperty(name = "CanControl", type = Boolean.class, access = Access.READ) -public interface Player extends DBusInterface { +public interface Player extends DBusInterface, Properties { public void Next();