├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.html ├── README.md ├── README.txt ├── cmake_uninstall.cmake.in ├── lib ├── CMakeLists.txt ├── airplay_video.c ├── airplay_video.h ├── byteutils.c ├── byteutils.h ├── compat.c ├── compat.h ├── crypto.c ├── crypto.h ├── dnssd.c ├── dnssd.h ├── dnssdint.h ├── fairplay.h ├── fairplay_playfair.c ├── fcup_request.h ├── global.h ├── http_handlers.h ├── http_request.c ├── http_request.h ├── http_response.c ├── http_response.h ├── httpd.c ├── httpd.h ├── llhttp │ ├── CMakeLists.txt │ ├── LICENSE-MIT │ ├── api.c │ ├── http.c │ ├── llhttp.c │ └── llhttp.h ├── logger.c ├── logger.h ├── mirror_buffer.c ├── mirror_buffer.h ├── netutils.c ├── netutils.h ├── pairing.c ├── pairing.h ├── playfair │ ├── CMakeLists.txt │ ├── LICENSE.md │ ├── hand_garble.c │ ├── modified_md5.c │ ├── omg_hax.c │ ├── omg_hax.h │ ├── playfair.c │ ├── playfair.h │ └── sap_hash.c ├── raop.c ├── raop.h ├── raop_buffer.c ├── raop_buffer.h ├── raop_handlers.h ├── raop_ntp.c ├── raop_ntp.h ├── raop_rtp.c ├── raop_rtp.h ├── raop_rtp_mirror.c ├── raop_rtp_mirror.h ├── sockets.h ├── srp.c ├── srp.h ├── stream.h ├── threads.h ├── utils.c └── utils.h ├── renderers ├── CMakeLists.txt ├── audio_renderer.c ├── audio_renderer.h ├── video_renderer.c ├── video_renderer.h └── x_display_fix.h ├── uxplay.1 ├── uxplay.cpp └── uxplay.spec /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vscode/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if ( APPLE ) 2 | cmake_minimum_required( VERSION 3.13 ) 3 | else () 4 | cmake_minimum_required( VERSION 3.10 ) 5 | endif () 6 | 7 | project( uxplay ) 8 | 9 | message( STATUS "Project name: " ${PROJECT_NAME} ) 10 | 11 | include(GNUInstallDirs) 12 | 13 | set ( CMAKE_CXX_STANDARD 11 ) 14 | 15 | if (ZOOMFIX ) 16 | message (STATUS "cmake option ZOOMFIX is no longer used (if needed, ZOOMFIX is automatically applied if X11 libraries are present)" ) 17 | endif() 18 | 19 | if ( ( UNIX AND NOT APPLE ) OR USE_X11 ) 20 | if ( NOT NO_X11_DEPS ) 21 | find_package( X11 ) 22 | if ( X11_FOUND ) 23 | message (STATUS "Will compile using X11 Libraries (use cmake option -DNO_X11_DEPS=ON if X11 dependence is not wanted)" ) 24 | link_libraries( ${X11_LIBRARIES} ) 25 | include_directories( ${X11_INCLUDE_DIR} ) 26 | else () 27 | message (STATUS "X11 libraries not found, will compile without X11 dependence" ) 28 | endif () 29 | else() 30 | message (STATUS "will compile without X11 dependence" ) 31 | endif() 32 | endif() 33 | 34 | if( UNIX AND NOT APPLE ) 35 | add_definitions( -DSUPPRESS_AVAHI_COMPAT_WARNING ) 36 | # convert AirPlay colormap 1:3:7:1 to sRGB (1:1:7:1), needed on Linux and BSD 37 | add_definitions( -DFULL_RANGE_RGB_FIX ) 38 | else() 39 | set( CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE ) 40 | endif() 41 | 42 | add_subdirectory( lib/llhttp ) 43 | add_subdirectory( lib/playfair ) 44 | add_subdirectory( lib ) 45 | add_subdirectory( renderers ) 46 | 47 | if ( GST_MACOS ) 48 | add_definitions( -DGST_MACOS ) 49 | message ( STATUS "define GST_MACOS" ) 50 | endif() 51 | 52 | add_executable( uxplay uxplay.cpp ) 53 | target_link_libraries( uxplay 54 | renderers 55 | airplay 56 | ) 57 | 58 | install( TARGETS uxplay RUNTIME DESTINATION bin ) 59 | install( FILES uxplay.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) 60 | install( FILES README.md README.txt README.html LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR} ) 61 | install( FILES lib/llhttp/LICENSE-MIT DESTINATION ${CMAKE_INSTALL_DOCDIR}/llhttp ) 62 | 63 | # uninstall target 64 | if(NOT TARGET uninstall) 65 | configure_file( 66 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" 67 | "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" 68 | IMMEDIATE @ONLY) 69 | 70 | add_custom_target(uninstall 71 | COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) 72 | endif() 73 | -------------------------------------------------------------------------------- /cmake_uninstall.cmake.in: -------------------------------------------------------------------------------- 1 | if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") 2 | message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") 3 | endif() 4 | 5 | file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) 6 | string(REGEX REPLACE "\n" ";" files "${files}") 7 | foreach(file ${files}) 8 | message(STATUS "Uninstalling $ENV{DESTDIR}${file}") 9 | if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 10 | execute_process( 11 | COMMAND "@CMAKE_COMMAND@" -E remove "$ENV{DESTDIR}${file}" 12 | OUTPUT_VARIABLE rm_out 13 | RESULT_VARIABLE rm_retval 14 | ) 15 | if(NOT "${rm_retval}" STREQUAL 0) 16 | message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") 17 | endif() 18 | else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 19 | message(STATUS "File $ENV{DESTDIR}${file} does not exist.") 20 | endif() 21 | endforeach() 22 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | include_directories( playfair llhttp ) 3 | 4 | message( STATUS "*** CFLAGS \"" ${CMAKE_C_FLAGS} "\" from build environment will be postpended to CMAKE_CFLAGS" ) 5 | 6 | # Common x86/x86_64 cflags 7 | if( NOT NO_MARCH_NATIVE AND CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)" ) 8 | set( CMAKE_C_FLAGS "-Ofast -march=native ${CMAKE_C_FLAGS}" ) 9 | message( STATUS "Using CFLAGS with -march=native" ) 10 | message( STATUS "*** ONLY USE THIS WHEN COMPILING ON THE MACHINE THAT WILL RUN UXPLAY" ) 11 | message( STATUS " run \"cmake -DNO_MARCH_NATIVE=ON\" to switch off this compiler option" ) 12 | else() 13 | message( STATUS "Not using -march=native" ) 14 | set( CMAKE_C_FLAGS "-O2 ${CMAKE_C_FLAGS}" ) 15 | endif() 16 | 17 | # Common Linux cflags 18 | if ( UNIX AND NOT APPLE ) 19 | set( CMAKE_C_FLAGS "-DSTANDALONE -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DTARGET_POSIX -D_LINUX -fPIC -DPIC -D_REENTRANT -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall ${CMAKE_C_FLAGS}" ) 20 | endif() 21 | 22 | if ( WIN32 ) 23 | message( STATUS "Building for Windows " ) 24 | set( CMAKE_C_FLAGS "-DSTANDALONE -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DTARGET_POSIX -D_WIN32 -fPIC -DPIC -D_REENTRANT -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall ${CMAKE_C_FLAGS}" ) 25 | endif() 26 | 27 | message( STATUS "using CMAKE_CFLAGS: " ${CMAKE_C_FLAGS} ) 28 | 29 | #activate the NOHOLD feature to drop existing connections if a third connection is made 30 | add_definitions( -DNOHOLD ) 31 | 32 | INCLUDE (CheckIncludeFiles) 33 | if( WIN32 ) 34 | CHECK_INCLUDE_FILES ("winsock2.h" WINSOCK2 ) 35 | else() 36 | # for BSD Unix (e.g. FreeBSD) 37 | CHECK_INCLUDE_FILES ("sys/endian.h" BSD ) 38 | if ( BSD ) 39 | add_definitions( -DSYS_ENDIAN_H ) 40 | endif ( BSD ) 41 | endif() 42 | 43 | if( APPLE ) 44 | set( ENV{PKG_CONFIG_PATH} "/usr/local/lib/pkgconfig" ) # standard location, and Brew 45 | set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/opt/homebrew/lib/pkgconfig" ) # Brew for M1 macs 46 | set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:$ENV{HOMEBREW_PREFIX}/lib/pkgconfig" ) # Brew using prefix 47 | set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/opt/local/lib/pkgconfig/" ) # MacPorts 48 | set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/opt/openssl@3/lib/pkgconfig" ) # Brew openssl 49 | set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/opt/homebrew/opt/openssl@3/lib/pkgconfig" ) # Brew M1 openssl 50 | set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:$ENV{HOMEBREW_PREFIX}/opt/openssl@3/lib/pkgconfig" ) # Brew using prefix openssl 51 | message( "PKG_CONFIG_PATH (Apple, lib) = " $ENV{PKG_CONFIG_PATH} ) 52 | find_program( PKG_CONFIG_EXECUTABLE pkg-config PATHS /Library/FrameWorks/GStreamer.framework/Commands ) 53 | message( "PKG_CONFIG_EXECUTABLE " ${PKG_CONFIG_EXECUTABLE} ) 54 | endif() 55 | find_package(PkgConfig REQUIRED) 56 | 57 | aux_source_directory(. play_src) 58 | set(DIR_SRCS ${play_src}) 59 | 60 | add_library( airplay STATIC 61 | ${DIR_SRCS} 62 | ) 63 | 64 | if ( APPLE ) 65 | target_link_libraries( airplay 66 | pthread 67 | playfair 68 | llhttp ) 69 | elseif( WIN32 ) 70 | target_link_libraries( airplay 71 | pthread 72 | playfair 73 | llhttp 74 | wsock32 75 | iphlpapi 76 | ws2_32 ) 77 | else() 78 | target_link_libraries( airplay PUBLIC 79 | pthread 80 | playfair 81 | llhttp ) 82 | endif() 83 | 84 | # libplist 85 | 86 | if( APPLE ) 87 | # use static linking 88 | pkg_search_module(PLIST REQUIRED libplist-2.0) 89 | find_library( LIBPLIST libplist-2.0.a REQUIRED ) 90 | message( STATUS "(Static linking) LIBPLIST " ${LIBPLIST} ) 91 | target_link_libraries ( airplay ${LIBPLIST} ) 92 | elseif( WIN32) 93 | pkg_search_module(PLIST REQUIRED libplist-2.0) 94 | find_library( LIBPLIST ${PLIST_LIBRARIES} PATH ${PLIST_LIBDIR} ) 95 | target_link_libraries ( airplay ${LIBPLIST} ) 96 | else () 97 | pkg_search_module(PLIST libplist>=2.0) 98 | if(NOT PLIST_FOUND) 99 | pkg_search_module(PLIST REQUIRED libplist-2.0) 100 | endif() 101 | find_library( LIBPLIST ${PLIST_LIBRARIES} PATH ${PLIST_LIBDIR} ) 102 | target_link_libraries ( airplay PUBLIC ${LIBPLIST} ) 103 | endif() 104 | if ( PLIST_FOUND ) 105 | message( STATUS "found libplist-${PLIST_VERSION}" ) 106 | endif() 107 | target_include_directories( airplay PRIVATE ${PLIST_INCLUDE_DIRS} ) 108 | 109 | #libcrypto 110 | if( APPLE ) 111 | # use static linking 112 | # can either compile Openssl 1.1.1 or 3.0.0 from source (install_dev to /usr/local) or use Macports or Brew 113 | # MacPorts needs zlib with it. Brew has a "keg-only" installation in /usr/local/opt/openssl@3 114 | # Brew on M1 macs puts this in /opt/homebrew/opt/openssl@3 115 | pkg_check_modules( OPENSSL REQUIRED openssl>=1.1.1) 116 | message( "OPENSSL_LIBRARY_DIRS " ${OPENSSL_LIBRARY_DIRS} ) 117 | message( "OPENSSL_INCLUDE_DIRS " ${OPENSSL_INCLUDE_DIRS} ) 118 | find_library( LIBCRYPTO libcrypto.a PATHS ${OPENSSL_LIBRARY_DIRS} REQUIRED ) 119 | message( "(Static linking) LIBCRYPTO " ${LIBCRYPTO} ) 120 | target_link_libraries( airplay ${LIBCRYPTO} ) 121 | if( LIBCRYPTO MATCHES "/opt/local/lib/libcrypto.a" ) #MacPorts openssl 122 | find_library( LIBZ libz.a) # needed by MacPorts openssl 123 | message("(MacPorts) LIBZ= " ${LIBZ} ) 124 | target_link_libraries( airplay ${LIBZ} ) 125 | endif() 126 | target_include_directories( airplay PRIVATE ${OPENSSL_INCLUDE_DIRS} ) 127 | elseif( WIN32 ) 128 | find_package(OpenSSL 1.1.1 REQUIRED) 129 | target_compile_definitions( airplay PUBLIC OPENSSL_API_COMPAT=0x10101000L ) 130 | target_link_libraries( airplay OpenSSL::Crypto ) 131 | else() 132 | find_package(OpenSSL 1.1.1 REQUIRED) 133 | target_compile_definitions( airplay PUBLIC OPENSSL_API_COMPAT=0x10101000L ) 134 | target_link_libraries( airplay PUBLIC OpenSSL::Crypto ) 135 | endif() 136 | 137 | #dns_sd 138 | if ( NOT APPLE ) 139 | pkg_search_module(AVAHI_DNSSD avahi-compat-libdns_sd) 140 | if (AVAHI_DNSSD_FOUND) 141 | target_include_directories( airplay PRIVATE ${AVAHI_DNSSD_INCLUDE_DIRS} ) 142 | find_library( DNSSD ${AVAHI_DNSSD_LIBRARIES} PATH ${AVAHI_DNSSD_LIBDIR}) 143 | target_link_libraries(airplay PUBLIC ${DNSSD} ) 144 | else() # can also build if mDNSResponder or another implementation of dns_sd is present instead of Avahi 145 | if ( WIN32 ) 146 | if (DEFINED ENV{BONJOUR_SDK_HOME}) 147 | set(BONJOUR_SDK "$ENV{BONJOUR_SDK_HOME}" ) 148 | else() 149 | set(BONJOUR_SDK "C:\\Program Files\\Bonjour SDK") 150 | endif() 151 | message( STATUS "BONJOUR_SDK_HOME " ${BONJOUR_SDK} ) 152 | set(DNSSD "${BONJOUR_SDK}/Lib/x64/dnssd.lib") 153 | target_link_libraries(airplay ${DNSSD} ) 154 | message( STATUS "dns_sd: using " ${DNSSD} ) 155 | find_path(DNSSD_INCLUDE_DIR dns_sd.h HINTS ${BONJOUR_SDK}/Include ) 156 | else() 157 | find_library( DNSSD dns_sd ) 158 | if( NOT DNSSD ) 159 | message( FATAL_ERROR "libdns_sd missing; can be provided by avahi_compat-libdns_sd or mDNSResponder." ) 160 | else() 161 | message( STATUS "dns_sd: found" ${DNSSD} ) 162 | endif() 163 | target_link_libraries(airplay PUBLIC ${DNSSD} ) 164 | find_path(DNSSD_INCLUDE_DIR dns_sd.h HINTS ${CMAKE_INSTALL_INCLUDEDIR} ) 165 | endif() 166 | if ( NOT DNSSD_INCLUDE_DIR ) 167 | message( FATAL_ERROR " dns_sd.h not found ") 168 | else() 169 | message( STATUS "found dns_sd.h in " ${DNSSD_INCLUDE_DIR} ) 170 | endif() 171 | target_include_directories( airplay PRIVATE ${DNSSD_INCLUDE_DIR} ) 172 | endif() 173 | endif() 174 | -------------------------------------------------------------------------------- /lib/airplay_video.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2024 fduncanh 3 | * All Rights Reserved. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | */ 16 | 17 | // it should only start and stop the media_data_store that handles all HLS transactions, without 18 | // otherwise participating in them. 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "raop.h" 27 | #include "airplay_video.h" 28 | 29 | struct media_item_s { 30 | char *uri; 31 | char *playlist; 32 | int access; 33 | }; 34 | 35 | struct airplay_video_s { 36 | raop_t *raop; 37 | char apple_session_id[37]; 38 | char playback_uuid[37]; 39 | char *uri_prefix; 40 | char local_uri_prefix[23]; 41 | int next_uri; 42 | int FCUP_RequestID; 43 | float start_position_seconds; 44 | playback_info_t *playback_info; 45 | // The local port of the airplay server on the AirPlay server 46 | unsigned short airplay_port; 47 | char *master_uri; 48 | char *master_playlist; 49 | media_item_t *media_data_store; 50 | int num_uri; 51 | }; 52 | 53 | // initialize airplay_video service. 54 | int airplay_video_service_init(raop_t *raop, unsigned short http_port, 55 | const char *session_id) { 56 | char uri[] = "http://localhost:xxxxx"; 57 | assert(raop); 58 | 59 | airplay_video_t *airplay_video = deregister_airplay_video(raop); 60 | if (airplay_video) { 61 | airplay_video_service_destroy(airplay_video); 62 | } 63 | 64 | airplay_video = (airplay_video_t *) calloc(1, sizeof(airplay_video_t)); 65 | if (!airplay_video) { 66 | return -1; 67 | } 68 | 69 | /* create local_uri_prefix string */ 70 | strncpy(airplay_video->local_uri_prefix, uri, sizeof(airplay_video->local_uri_prefix)); 71 | char *ptr = strstr(airplay_video->local_uri_prefix, "xxxxx"); 72 | snprintf(ptr, 6, "%-5u", http_port); 73 | ptr = strstr(airplay_video->local_uri_prefix, " "); 74 | if (ptr) { 75 | *ptr = '\0'; 76 | } 77 | 78 | if (!register_airplay_video(raop, airplay_video)) { 79 | return -2; 80 | } 81 | 82 | //printf(" %p %p\n", airplay_video, get_airplay_video(raop)); 83 | 84 | airplay_video->raop = raop; 85 | 86 | 87 | airplay_video->FCUP_RequestID = 0; 88 | 89 | 90 | size_t len = strlen(session_id); 91 | assert(len == 36); 92 | strncpy(airplay_video->apple_session_id, session_id, len); 93 | (airplay_video->apple_session_id)[len] = '\0'; 94 | 95 | airplay_video->start_position_seconds = 0.0f; 96 | 97 | airplay_video->master_uri = NULL; 98 | airplay_video->media_data_store = NULL; 99 | airplay_video->master_playlist = NULL; 100 | airplay_video->num_uri = 0; 101 | airplay_video->next_uri = 0; 102 | return 0; 103 | } 104 | 105 | // destroy the airplay_video service 106 | void 107 | airplay_video_service_destroy(airplay_video_t *airplay_video) 108 | { 109 | 110 | if (airplay_video->uri_prefix) { 111 | free(airplay_video->uri_prefix); 112 | } 113 | if (airplay_video->master_uri) { 114 | free (airplay_video->master_uri); 115 | } 116 | if (airplay_video->media_data_store) { 117 | destroy_media_data_store(airplay_video); 118 | } 119 | if (airplay_video->master_playlist) { 120 | free (airplay_video->master_playlist); 121 | } 122 | 123 | 124 | free (airplay_video); 125 | } 126 | 127 | const char *get_apple_session_id(airplay_video_t *airplay_video) { 128 | return airplay_video->apple_session_id; 129 | } 130 | 131 | float get_start_position_seconds(airplay_video_t *airplay_video) { 132 | return airplay_video->start_position_seconds; 133 | } 134 | 135 | void set_start_position_seconds(airplay_video_t *airplay_video, float start_position_seconds) { 136 | airplay_video->start_position_seconds = start_position_seconds; 137 | } 138 | 139 | void set_playback_uuid(airplay_video_t *airplay_video, const char *playback_uuid) { 140 | size_t len = strlen(playback_uuid); 141 | assert(len == 36); 142 | memcpy(airplay_video->playback_uuid, playback_uuid, len); 143 | (airplay_video->playback_uuid)[len] = '\0'; 144 | } 145 | 146 | void set_uri_prefix(airplay_video_t *airplay_video, char *uri_prefix, int uri_prefix_len) { 147 | if (airplay_video->uri_prefix) { 148 | free (airplay_video->uri_prefix); 149 | } 150 | airplay_video->uri_prefix = (char *) calloc(uri_prefix_len + 1, sizeof(char)); 151 | memcpy(airplay_video->uri_prefix, uri_prefix, uri_prefix_len); 152 | } 153 | 154 | char *get_uri_prefix(airplay_video_t *airplay_video) { 155 | return airplay_video->uri_prefix; 156 | } 157 | 158 | char *get_uri_local_prefix(airplay_video_t *airplay_video) { 159 | return airplay_video->local_uri_prefix; 160 | } 161 | 162 | 163 | char *get_master_uri(airplay_video_t *airplay_video) { 164 | return airplay_video->master_uri; 165 | } 166 | 167 | 168 | int get_next_FCUP_RequestID(airplay_video_t *airplay_video) { 169 | return ++(airplay_video->FCUP_RequestID); 170 | } 171 | 172 | void set_next_media_uri_id(airplay_video_t *airplay_video, int num) { 173 | airplay_video->next_uri = num; 174 | } 175 | 176 | int get_next_media_uri_id(airplay_video_t *airplay_video) { 177 | return airplay_video->next_uri; 178 | } 179 | 180 | 181 | /* master playlist */ 182 | 183 | void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist) { 184 | if (airplay_video->master_playlist) { 185 | free (airplay_video->master_playlist); 186 | } 187 | airplay_video->master_playlist = master_playlist; 188 | } 189 | 190 | char *get_master_playlist(airplay_video_t *airplay_video) { 191 | return airplay_video->master_playlist; 192 | } 193 | 194 | /* media_data_store */ 195 | 196 | int get_num_media_uri(airplay_video_t *airplay_video) { 197 | return airplay_video->num_uri; 198 | } 199 | 200 | void destroy_media_data_store(airplay_video_t *airplay_video) { 201 | media_item_t *media_data_store = airplay_video->media_data_store; 202 | if (media_data_store) { 203 | for (int i = 0; i < airplay_video->num_uri ; i ++ ) { 204 | if (media_data_store[i].uri) { 205 | free (media_data_store[i].uri); 206 | } 207 | if (media_data_store[i].playlist) { 208 | free (media_data_store[i].playlist); 209 | } 210 | } 211 | } 212 | free (media_data_store); 213 | airplay_video->num_uri = 0; 214 | } 215 | 216 | void create_media_data_store(airplay_video_t * airplay_video, char ** uri_list, int num_uri) { 217 | destroy_media_data_store(airplay_video); 218 | media_item_t *media_data_store = calloc(num_uri, sizeof(media_item_t)); 219 | for (int i = 0; i < num_uri; i++) { 220 | media_data_store[i].uri = uri_list[i]; 221 | media_data_store[i].playlist = NULL; 222 | media_data_store[i].access = 0; 223 | } 224 | airplay_video->media_data_store = media_data_store; 225 | airplay_video->num_uri = num_uri; 226 | } 227 | 228 | int store_media_data_playlist_by_num(airplay_video_t *airplay_video, char * media_playlist, int num) { 229 | media_item_t *media_data_store = airplay_video->media_data_store; 230 | if ( num < 0 || num >= airplay_video->num_uri) { 231 | return -1; 232 | } else if (media_data_store[num].playlist) { 233 | return -2; 234 | } 235 | media_data_store[num].playlist = media_playlist; 236 | return 0; 237 | } 238 | 239 | char * get_media_playlist_by_num(airplay_video_t *airplay_video, int num) { 240 | media_item_t *media_data_store = airplay_video->media_data_store; 241 | if (media_data_store == NULL) { 242 | return NULL; 243 | } 244 | if (num >= 0 && num num_uri) { 245 | return media_data_store[num].playlist; 246 | } 247 | return NULL; 248 | } 249 | 250 | int get_media_playlist_by_uri(airplay_video_t *airplay_video, const char *uri) { 251 | /* Problem: there can be more than one StreamInf playlist with the same uri: 252 | * they differ by choice of partner Media (audio, subtitles) playlists 253 | * If the same uri is requested again, one of the other ones will be returned 254 | * (the least-previously-requested one will be served up) 255 | */ 256 | // modified to return the position of the media playlist in the master playlist 257 | media_item_t *media_data_store = airplay_video->media_data_store; 258 | if (media_data_store == NULL) { 259 | return -2; 260 | } 261 | int found = 0;; 262 | int num = -1; 263 | int access = -1; 264 | for (int i = 0; i < airplay_video->num_uri; i++) { 265 | if (strstr(media_data_store[i].uri, uri)) { 266 | if (!found) { 267 | found = 1; 268 | num = i; 269 | access = media_data_store[i].access; 270 | } else { 271 | /* change > below to >= to reverse the order of choice */ 272 | if (access > media_data_store[i].access) { 273 | access = media_data_store[i].access; 274 | num = i; 275 | } 276 | } 277 | } 278 | } 279 | if (found) { 280 | //printf("found %s\n", media_data_store[num].uri); 281 | ++media_data_store[num].access; 282 | return num; 283 | } 284 | return -1; 285 | } 286 | 287 | char * get_media_uri_by_num(airplay_video_t *airplay_video, int num) { 288 | media_item_t * media_data_store = airplay_video->media_data_store; 289 | if (media_data_store == NULL) { 290 | return NULL; 291 | } 292 | if (num >= 0 && num < airplay_video->num_uri) { 293 | return media_data_store[num].uri; 294 | } 295 | return NULL; 296 | } 297 | 298 | int get_media_uri_num(airplay_video_t *airplay_video, char * uri) { 299 | media_item_t *media_data_store = airplay_video->media_data_store; 300 | for (int i = 0; i < airplay_video->num_uri ; i++) { 301 | if (strstr(media_data_store[i].uri, uri)) { 302 | return i; 303 | } 304 | } 305 | return -1; 306 | } 307 | 308 | int analyze_media_playlist(char *playlist, float *duration) { 309 | float next; 310 | int count = 0; 311 | char *ptr = strstr(playlist, "#EXTINF:"); 312 | *duration = 0.0f; 313 | while (ptr != NULL) { 314 | char *end; 315 | ptr += strlen("#EXTINF:"); 316 | next = strtof(ptr, &end); 317 | *duration += next; 318 | count++; 319 | ptr = strstr(end, "#EXTINF:"); 320 | } 321 | return count; 322 | } 323 | -------------------------------------------------------------------------------- /lib/airplay_video.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 fduncanh, All Rights Reserved. 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================= 15 | */ 16 | 17 | #ifndef AIRPLAY_VIDEO_H 18 | #define AIRPLAY_VIDEO_H 19 | 20 | 21 | #include 22 | #include 23 | #include "raop.h" 24 | #include "logger.h" 25 | 26 | typedef struct airplay_video_s airplay_video_t; 27 | typedef struct media_item_s media_item_t; 28 | 29 | const char *get_apple_session_id(airplay_video_t *airplay_video); 30 | void set_start_position_seconds(airplay_video_t *airplay_video, float start_position_seconds); 31 | float get_start_position_seconds(airplay_video_t *airplay_video); 32 | void set_playback_uuid(airplay_video_t *airplay_video, const char *playback_uuid); 33 | void set_uri_prefix(airplay_video_t *airplay_video, char *uri_prefix, int uri_prefix_len); 34 | char *get_uri_prefix(airplay_video_t *airplay_video); 35 | char *get_uri_local_prefix(airplay_video_t *airplay_video); 36 | int get_next_FCUP_RequestID(airplay_video_t *airplay_video); 37 | void set_next_media_uri_id(airplay_video_t *airplay_video, int id); 38 | int get_next_media_uri_id(airplay_video_t *airplay_video); 39 | int get_media_playlist_by_uri(airplay_video_t *airplay_video, const char *uri); 40 | void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist); 41 | char *get_master_playlist(airplay_video_t *airplay_video); 42 | int get_num_media_uri(airplay_video_t *airplay_video); 43 | void destroy_media_data_store(airplay_video_t *airplay_video); 44 | void create_media_data_store(airplay_video_t * airplay_video, char ** media_data_store, int num_uri); 45 | int store_media_data_playlist_by_num(airplay_video_t *airplay_video, char * media_playlist, int num); 46 | char *get_media_playlist_by_num(airplay_video_t *airplay_video, int num); 47 | char *get_media_uri_by_num(airplay_video_t *airplay_video, int num); 48 | int get_media_uri_num(airplay_video_t *airplay_video, char * uri); 49 | int analyze_media_playlist(char *playlist, float *duration); 50 | 51 | void airplay_video_service_destroy(airplay_video_t *airplay_video); 52 | 53 | // C wrappers for c++ class MediaDataStore 54 | //create the media_data_store, return a pointer to it. 55 | void* media_data_store_create(void *conn_opaque, uint16_t port); 56 | 57 | //delete the media_data_store 58 | void media_data_store_destroy(void *media_data_store); 59 | 60 | // called by the POST /action handler: 61 | char *process_media_data(void *media_data_store, const char *url, const char *data, int datalen); 62 | 63 | //called by the POST /play handler 64 | bool request_media_data(void *media_data_store, const char *primary_url, const char * session_id); 65 | 66 | //called by airplay_video_media_http_connection::get_handler: &path = req.uri) 67 | char *query_media_data(void *media_data_store, const char *url, int *len); 68 | 69 | //called by the post_stop_handler: 70 | void media_data_store_reset(void *media_data_store); 71 | 72 | const char *adjust_primary_uri(void *media_data_store, const char *url); 73 | 74 | #endif //AIRPLAY_VIDEO_H 75 | -------------------------------------------------------------------------------- /lib/byteutils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 dsafa22, All Rights Reserved. 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================= 15 | * modified by fduncanh 2021-23 16 | */ 17 | 18 | 19 | #define SECOND_IN_NSECS 1000000000UL 20 | 21 | #include 22 | #ifdef _WIN32 23 | # include 24 | #else 25 | # include 26 | #endif 27 | 28 | #include "byteutils.h" 29 | 30 | #ifdef _WIN32 31 | # ifndef ntonll 32 | # define ntohll(x) ((1==ntohl(1)) ? (x) : (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32))) 33 | # endif 34 | #else 35 | # ifndef htonll 36 | # ifdef SYS_ENDIAN_H 37 | # include 38 | # else 39 | # include 40 | # endif 41 | # define htonll(x) htobe64(x) 42 | # define ntohll(x) be64toh(x) 43 | # endif 44 | #endif 45 | 46 | // The functions in this file assume a little endian cpu architecture! 47 | 48 | /** 49 | * Reads a little endian unsigned 16 bit integer from the buffer at position offset 50 | */ 51 | uint16_t byteutils_get_short(unsigned char* b, int offset) { 52 | return *((uint16_t*)(b + offset)); 53 | } 54 | 55 | /** 56 | * Reads a little endian unsigned 32 bit integer from the buffer at position offset 57 | */ 58 | uint32_t byteutils_get_int(unsigned char* b, int offset) { 59 | return *((uint32_t*)(b + offset)); 60 | } 61 | 62 | /** 63 | * Reads a little endian unsigned 64 bit integer from the buffer at position offset 64 | */ 65 | uint64_t byteutils_get_long(unsigned char* b, int offset) { 66 | return *((uint64_t*)(b + offset)); 67 | } 68 | 69 | /** 70 | * Reads a big endian unsigned 16 bit integer from the buffer at position offset 71 | */ 72 | uint16_t byteutils_get_short_be(unsigned char* b, int offset) { 73 | return ntohs(byteutils_get_short(b, offset)); 74 | } 75 | 76 | /** 77 | * Reads a big endian unsigned 32 bit integer from the buffer at position offset 78 | */ 79 | uint32_t byteutils_get_int_be(unsigned char* b, int offset) { 80 | return ntohl(byteutils_get_int(b, offset)); 81 | } 82 | 83 | /** 84 | * Reads a big endian unsigned 64 bit integer from the buffer at position offset 85 | */ 86 | uint64_t byteutils_get_long_be(unsigned char* b, int offset) { 87 | return ntohll(byteutils_get_long(b, offset)); 88 | } 89 | 90 | /** 91 | * Reads a float from the buffer at position offset 92 | */ 93 | float byteutils_get_float(unsigned char* b, int offset) { 94 | return *((float*)(b + offset)); 95 | } 96 | 97 | /** 98 | * Writes a little endian unsigned 32 bit integer to the buffer at position offset 99 | */ 100 | void byteutils_put_int(unsigned char* b, int offset, uint32_t value) { 101 | *((uint32_t*)(b + offset)) = value; 102 | } 103 | 104 | /** 105 | * Reads an ntp timestamp and returns it as nano seconds since the Unix epoch 106 | */ 107 | uint64_t byteutils_get_ntp_timestamp(unsigned char *b, int offset) { 108 | uint64_t seconds = ntohl(((unsigned int) byteutils_get_int(b, offset))) - SECONDS_FROM_1900_TO_1970; 109 | uint64_t fraction = ntohl((unsigned int) byteutils_get_int(b, offset + 4)); 110 | return (seconds * SECOND_IN_NSECS) + ((fraction * SECOND_IN_NSECS) >> 32); 111 | } 112 | 113 | /** 114 | * Writes a time given as nano seconds since the Unix time epoch as an ntp timestamp 115 | * into the buffer at position offset 116 | */ 117 | void byteutils_put_ntp_timestamp(unsigned char *b, int offset, uint64_t ns_since_1970) { 118 | uint64_t seconds = ns_since_1970 / SECOND_IN_NSECS; 119 | uint64_t nanoseconds = ns_since_1970 % SECOND_IN_NSECS; 120 | seconds += SECONDS_FROM_1900_TO_1970; 121 | uint64_t fraction = (nanoseconds << 32) / SECOND_IN_NSECS; 122 | 123 | // Write in big endian! 124 | byteutils_put_int(b, offset, htonl(seconds)); 125 | byteutils_put_int(b, offset + 4, htonl(fraction)); 126 | } 127 | 128 | -------------------------------------------------------------------------------- /lib/byteutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 dsafa22, All Rights Reserved. 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | #ifndef AIRPLAYSERVER_BYTEUTILS_H 16 | #define AIRPLAYSERVER_BYTEUTILS_H 17 | #include 18 | 19 | uint16_t byteutils_get_short(unsigned char* b, int offset); 20 | uint32_t byteutils_get_int(unsigned char* b, int offset); 21 | uint64_t byteutils_get_long(unsigned char* b, int offset); 22 | uint16_t byteutils_get_short_be(unsigned char* b, int offset); 23 | uint32_t byteutils_get_int_be(unsigned char* b, int offset); 24 | uint64_t byteutils_get_long_be(unsigned char* b, int offset); 25 | float byteutils_get_float(unsigned char* b, int offset); 26 | 27 | #define SECONDS_FROM_1900_TO_1970 2208988800ULL 28 | 29 | uint64_t byteutils_get_ntp_timestamp(unsigned char *b, int offset); 30 | void byteutils_put_ntp_timestamp(unsigned char *b, int offset, uint64_t us_since_1970); 31 | 32 | #endif //AIRPLAYSERVER_BYTEUTILS_H 33 | -------------------------------------------------------------------------------- /lib/compat.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (c) 2024 F. Duncanh, All Rights Reserved. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | *================================================================== 16 | */ 17 | 18 | #ifdef _WIN32 19 | #include 20 | #include 21 | #include "compat.h" 22 | 23 | #define MAX_SOCKET_ERROR_MESSAGE_LENGTH 256 24 | 25 | /* Windows (winsock2) socket error message text */ 26 | char *wsa_strerror(int errnum) { 27 | static char message[MAX_SOCKET_ERROR_MESSAGE_LENGTH] = { 0 }; 28 | FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, 29 | 0, errnum, 0, message, sizeof(message), 0); 30 | char *nl = strchr(message, '\n'); 31 | if (nl) { 32 | *nl = 0; /* remove any trailing newline, or truncate to one line */ 33 | } 34 | return message; 35 | } 36 | #endif 37 | -------------------------------------------------------------------------------- /lib/compat.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | #ifndef COMPAT_H 16 | #define COMPAT_H 17 | 18 | #if defined(WIN32) 19 | #include 20 | #include 21 | #ifndef snprintf 22 | #define snprintf _snprintf 23 | #endif 24 | #else 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #endif 36 | 37 | #include "sockets.h" 38 | #include "threads.h" 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /lib/crypto.h: -------------------------------------------------------------------------------- 1 | /** 2 | * RPiPlay - An open-source AirPlay mirroring server for Raspberry Pi 3 | * Copyright (C) 2019 Florian Draschbacher 4 | * Copyright (C) 2020 Jaslo Ziska 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | * 20 | * modified by fduncanh 2023 21 | */ 22 | 23 | /* 24 | * Helper methods for various crypto operations. 25 | * Uses OpenSSL behind the scenes. 26 | */ 27 | 28 | #ifndef CRYPTO_H 29 | #define CRYPTO_H 30 | 31 | #include 32 | #include 33 | 34 | #ifdef __cplusplus 35 | extern "C" { 36 | #endif 37 | 38 | // 128bit AES in CTR mode 39 | 40 | #define AES_128_BLOCK_SIZE 16 41 | 42 | typedef enum aes_direction_e { AES_DECRYPT, AES_ENCRYPT } aes_direction_t; 43 | 44 | typedef struct aes_ctx_s aes_ctx_t; 45 | 46 | aes_ctx_t *aes_ctr_init(const uint8_t *key, const uint8_t *iv); 47 | void aes_ctr_reset(aes_ctx_t *ctx); 48 | void aes_ctr_encrypt(aes_ctx_t *ctx, const uint8_t *in, uint8_t *out, int len); 49 | void aes_ctr_decrypt(aes_ctx_t *ctx, const uint8_t *in, uint8_t *out, int len); 50 | void aes_ctr_start_fresh_block(aes_ctx_t *ctx); 51 | void aes_ctr_destroy(aes_ctx_t *ctx); 52 | 53 | aes_ctx_t *aes_cbc_init(const uint8_t *key, const uint8_t *iv, aes_direction_t direction); 54 | void aes_cbc_reset(aes_ctx_t *ctx); 55 | void aes_cbc_encrypt(aes_ctx_t *ctx, const uint8_t *in, uint8_t *out, int len); 56 | void aes_cbc_decrypt(aes_ctx_t *ctx, const uint8_t *in, uint8_t *out, int len); 57 | void aes_cbc_destroy(aes_ctx_t *ctx); 58 | 59 | // X25519 60 | 61 | #define X25519_KEY_SIZE 32 62 | 63 | typedef struct x25519_key_s x25519_key_t; 64 | 65 | x25519_key_t *x25519_key_generate(void); 66 | x25519_key_t *x25519_key_from_raw(const unsigned char data[X25519_KEY_SIZE]); 67 | void x25519_key_get_raw(unsigned char data[X25519_KEY_SIZE], const x25519_key_t *key); 68 | void x25519_key_destroy(x25519_key_t *key); 69 | int get_random_bytes(unsigned char *buf, int num); 70 | void pk_to_base64(const unsigned char *pk, int pk_len, char *pk_base64, int len); 71 | 72 | void x25519_derive_secret(unsigned char secret[X25519_KEY_SIZE], const x25519_key_t *ours, const x25519_key_t *theirs); 73 | 74 | // GCM AES 128 75 | 76 | int gcm_encrypt(const unsigned char *plaintext, int plaintext_len, unsigned char *ciphertext, 77 | unsigned char *key, unsigned char *iv, unsigned char *tag); 78 | int gcm_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *plaintext, 79 | unsigned char *key, unsigned char *iv, unsigned char *tag); 80 | // ED25519 81 | 82 | #define ED25519_KEY_SIZE 32 83 | 84 | typedef struct ed25519_key_s ed25519_key_t; 85 | 86 | ed25519_key_t *ed25519_key_generate(const char *device_id, const char * keyfile, int * result); 87 | ed25519_key_t *ed25519_key_from_raw(const unsigned char data[ED25519_KEY_SIZE]); 88 | void ed25519_key_get_raw(unsigned char data[ED25519_KEY_SIZE], const ed25519_key_t *key); 89 | /* 90 | * Note that this function does *not copy* the OpenSSL key but only the wrapper. The internal OpenSSL key is still the 91 | * same. Only the reference count is increased so destroying both the original and the copy is allowed. 92 | */ 93 | ed25519_key_t *ed25519_key_copy(const ed25519_key_t *key); 94 | void ed25519_key_destroy(ed25519_key_t *key); 95 | 96 | void ed25519_sign(unsigned char *signature, size_t signature_len, 97 | const unsigned char *data, size_t data_len, 98 | const ed25519_key_t *key); 99 | int ed25519_verify(const unsigned char *signature, size_t signature_len, 100 | const unsigned char *data, size_t data_len, 101 | const ed25519_key_t *key); 102 | 103 | // SHA512 104 | 105 | typedef struct sha_ctx_s sha_ctx_t; 106 | sha_ctx_t *sha_init(); 107 | void sha_update(sha_ctx_t *ctx, const uint8_t *in, int len); 108 | void sha_final(sha_ctx_t *ctx, uint8_t *out, unsigned int *len); 109 | void sha_reset(sha_ctx_t *ctx); 110 | void sha_destroy(sha_ctx_t *ctx); 111 | 112 | #ifdef __cplusplus 113 | } 114 | #endif 115 | #endif 116 | -------------------------------------------------------------------------------- /lib/dnssd.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | #ifndef DNSSD_H 16 | #define DNSSD_H 17 | #include 18 | 19 | #if defined(WIN32) && defined(DLL_EXPORT) 20 | # define DNSSD_API __declspec(dllexport) 21 | #else 22 | # define DNSSD_API 23 | #endif 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | #define DNSSD_ERROR_NOERROR 0 30 | #define DNSSD_ERROR_HWADDRLEN 1 31 | #define DNSSD_ERROR_OUTOFMEM 2 32 | #define DNSSD_ERROR_LIBNOTFOUND 3 33 | #define DNSSD_ERROR_PROCNOTFOUND 4 34 | #define DNSSD_ERROR_BADFEATURES 5 35 | 36 | typedef struct dnssd_s dnssd_t; 37 | 38 | DNSSD_API dnssd_t *dnssd_init(const char *name, int name_len, const char *hw_addr, int hw_addr_len, int *error, int require_pw); 39 | 40 | DNSSD_API int dnssd_register_raop(dnssd_t *dnssd, unsigned short port); 41 | DNSSD_API int dnssd_register_airplay(dnssd_t *dnssd, unsigned short port); 42 | 43 | DNSSD_API void dnssd_unregister_raop(dnssd_t *dnssd); 44 | DNSSD_API void dnssd_unregister_airplay(dnssd_t *dnssd); 45 | 46 | DNSSD_API const char *dnssd_get_airplay_txt(dnssd_t *dnssd, int *length); 47 | DNSSD_API const char *dnssd_get_name(dnssd_t *dnssd, int *length); 48 | DNSSD_API const char *dnssd_get_hw_addr(dnssd_t *dnssd, int *length); 49 | DNSSD_API void dnssd_set_airplay_features(dnssd_t *dnssd, int bit, int val); 50 | DNSSD_API uint64_t dnssd_get_airplay_features(dnssd_t *dnssd); 51 | DNSSD_API void dnssd_set_pk(dnssd_t *dnssd, char * pk_str); 52 | 53 | DNSSD_API void dnssd_destroy(dnssd_t *dnssd); 54 | 55 | #ifdef __cplusplus 56 | } 57 | #endif 58 | #endif 59 | -------------------------------------------------------------------------------- /lib/dnssdint.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================== 15 | * modified by fduncanh 2022 16 | */ 17 | 18 | #ifndef DNSSDINT_H 19 | #define DNSSDINT_H 20 | 21 | #include "global.h" 22 | 23 | /* the previous behavior of announcing UxPlay with a fixed public key PK 24 | * can be restored by uncommenting the following line */ 25 | //#define PK "b07727d6f6cd6e08b58ede525ec3cdeaa252ad9f683feb212ef8a205246554e7" 26 | 27 | #define RAOP_TXTVERS "1" 28 | #define RAOP_CH "2" /* Audio channels: 2 */ 29 | #define RAOP_CN "0,1,2,3" /* Audio codec: PCM, ALAC, AAC, AAC ELD */ 30 | #define RAOP_ET "0,3,5" /* Encryption type: None, FairPlay, FairPlay SAPv2.5 */ 31 | #define RAOP_VV "2" 32 | #define FEATURES_1 "0x5A7FFEE6" /* first 32 bits of features, with bit 27 ("supports legacy pairing") ON */ 33 | //#define FEATURES_1 "0x527FFEE6" /* first 32 bits of features, with bit 27 ("supports legacy pairing") OFF */ 34 | #define FEATURES_2 "0x0" /* second 32 bits of features */ 35 | #define RAOP_RHD "5.6.0.0" 36 | #define RAOP_SF "0x4" 37 | #define RAOP_SV "false" 38 | #define RAOP_DA "true" 39 | #define RAOP_SR "44100" /* Sample rate: 44100 */ 40 | #define RAOP_SS "16" /* Sample size: 16 */ 41 | #define RAOP_VS GLOBAL_VERSION /* defined in global.h */ 42 | #define RAOP_TP "UDP" /* Transport protocol. Possible values: UDP or TCP or TCP,UDP */ 43 | #define RAOP_MD "0,1,2" /* Metadata: text, artwork, progress */ 44 | #define RAOP_VN "65537" 45 | 46 | #define AIRPLAY_SRCVERS GLOBAL_VERSION /*defined in global.h */ 47 | #define AIRPLAY_FLAGS "0x4" 48 | #define AIRPLAY_VV "2" 49 | #define AIRPLAY_PI "2e388006-13ba-4041-9a67-25dd4a43d536" 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /lib/fairplay.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2018 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | #ifndef FAIRPLAY_H 16 | #define FAIRPLAY_H 17 | 18 | #include "logger.h" 19 | 20 | typedef struct fairplay_s fairplay_t; 21 | 22 | fairplay_t *fairplay_init(logger_t *logger); 23 | int fairplay_setup(fairplay_t *fp, const unsigned char req[16], unsigned char res[142]); 24 | int fairplay_handshake(fairplay_t *fp, const unsigned char req[164], unsigned char res[32]); 25 | int fairplay_decrypt(fairplay_t *fp, const unsigned char input[72], unsigned char output[16]); 26 | void fairplay_destroy(fairplay_t *fp); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /lib/fairplay_playfair.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2018 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "fairplay.h" 20 | #include "playfair/playfair.h" 21 | 22 | char reply_message[4][142] = {{0x46,0x50,0x4c,0x59,0x03,0x01,0x02,0x00,0x00,0x00,0x00,0x82,0x02,0x00,0x0f,0x9f,0x3f,0x9e,0x0a,0x25,0x21,0xdb,0xdf,0x31,0x2a,0xb2,0xbf,0xb2,0x9e,0x8d,0x23,0x2b,0x63,0x76,0xa8,0xc8,0x18,0x70,0x1d,0x22,0xae,0x93,0xd8,0x27,0x37,0xfe,0xaf,0x9d,0xb4,0xfd,0xf4,0x1c,0x2d,0xba,0x9d,0x1f,0x49,0xca,0xaa,0xbf,0x65,0x91,0xac,0x1f,0x7b,0xc6,0xf7,0xe0,0x66,0x3d,0x21,0xaf,0xe0,0x15,0x65,0x95,0x3e,0xab,0x81,0xf4,0x18,0xce,0xed,0x09,0x5a,0xdb,0x7c,0x3d,0x0e,0x25,0x49,0x09,0xa7,0x98,0x31,0xd4,0x9c,0x39,0x82,0x97,0x34,0x34,0xfa,0xcb,0x42,0xc6,0x3a,0x1c,0xd9,0x11,0xa6,0xfe,0x94,0x1a,0x8a,0x6d,0x4a,0x74,0x3b,0x46,0xc3,0xa7,0x64,0x9e,0x44,0xc7,0x89,0x55,0xe4,0x9d,0x81,0x55,0x00,0x95,0x49,0xc4,0xe2,0xf7,0xa3,0xf6,0xd5,0xba}, 23 | {0x46,0x50,0x4c,0x59,0x03,0x01,0x02,0x00,0x00,0x00,0x00,0x82,0x02,0x01,0xcf,0x32,0xa2,0x57,0x14,0xb2,0x52,0x4f,0x8a,0xa0,0xad,0x7a,0xf1,0x64,0xe3,0x7b,0xcf,0x44,0x24,0xe2,0x00,0x04,0x7e,0xfc,0x0a,0xd6,0x7a,0xfc,0xd9,0x5d,0xed,0x1c,0x27,0x30,0xbb,0x59,0x1b,0x96,0x2e,0xd6,0x3a,0x9c,0x4d,0xed,0x88,0xba,0x8f,0xc7,0x8d,0xe6,0x4d,0x91,0xcc,0xfd,0x5c,0x7b,0x56,0xda,0x88,0xe3,0x1f,0x5c,0xce,0xaf,0xc7,0x43,0x19,0x95,0xa0,0x16,0x65,0xa5,0x4e,0x19,0x39,0xd2,0x5b,0x94,0xdb,0x64,0xb9,0xe4,0x5d,0x8d,0x06,0x3e,0x1e,0x6a,0xf0,0x7e,0x96,0x56,0x16,0x2b,0x0e,0xfa,0x40,0x42,0x75,0xea,0x5a,0x44,0xd9,0x59,0x1c,0x72,0x56,0xb9,0xfb,0xe6,0x51,0x38,0x98,0xb8,0x02,0x27,0x72,0x19,0x88,0x57,0x16,0x50,0x94,0x2a,0xd9,0x46,0x68,0x8a}, 24 | {0x46,0x50,0x4c,0x59,0x03,0x01,0x02,0x00,0x00,0x00,0x00,0x82,0x02,0x02,0xc1,0x69,0xa3,0x52,0xee,0xed,0x35,0xb1,0x8c,0xdd,0x9c,0x58,0xd6,0x4f,0x16,0xc1,0x51,0x9a,0x89,0xeb,0x53,0x17,0xbd,0x0d,0x43,0x36,0xcd,0x68,0xf6,0x38,0xff,0x9d,0x01,0x6a,0x5b,0x52,0xb7,0xfa,0x92,0x16,0xb2,0xb6,0x54,0x82,0xc7,0x84,0x44,0x11,0x81,0x21,0xa2,0xc7,0xfe,0xd8,0x3d,0xb7,0x11,0x9e,0x91,0x82,0xaa,0xd7,0xd1,0x8c,0x70,0x63,0xe2,0xa4,0x57,0x55,0x59,0x10,0xaf,0x9e,0x0e,0xfc,0x76,0x34,0x7d,0x16,0x40,0x43,0x80,0x7f,0x58,0x1e,0xe4,0xfb,0xe4,0x2c,0xa9,0xde,0xdc,0x1b,0x5e,0xb2,0xa3,0xaa,0x3d,0x2e,0xcd,0x59,0xe7,0xee,0xe7,0x0b,0x36,0x29,0xf2,0x2a,0xfd,0x16,0x1d,0x87,0x73,0x53,0xdd,0xb9,0x9a,0xdc,0x8e,0x07,0x00,0x6e,0x56,0xf8,0x50,0xce}, 25 | {0x46,0x50,0x4c,0x59,0x03,0x01,0x02,0x00,0x00,0x00,0x00,0x82,0x02,0x03,0x90,0x01,0xe1,0x72,0x7e,0x0f,0x57,0xf9,0xf5,0x88,0x0d,0xb1,0x04,0xa6,0x25,0x7a,0x23,0xf5,0xcf,0xff,0x1a,0xbb,0xe1,0xe9,0x30,0x45,0x25,0x1a,0xfb,0x97,0xeb,0x9f,0xc0,0x01,0x1e,0xbe,0x0f,0x3a,0x81,0xdf,0x5b,0x69,0x1d,0x76,0xac,0xb2,0xf7,0xa5,0xc7,0x08,0xe3,0xd3,0x28,0xf5,0x6b,0xb3,0x9d,0xbd,0xe5,0xf2,0x9c,0x8a,0x17,0xf4,0x81,0x48,0x7e,0x3a,0xe8,0x63,0xc6,0x78,0x32,0x54,0x22,0xe6,0xf7,0x8e,0x16,0x6d,0x18,0xaa,0x7f,0xd6,0x36,0x25,0x8b,0xce,0x28,0x72,0x6f,0x66,0x1f,0x73,0x88,0x93,0xce,0x44,0x31,0x1e,0x4b,0xe6,0xc0,0x53,0x51,0x93,0xe5,0xef,0x72,0xe8,0x68,0x62,0x33,0x72,0x9c,0x22,0x7d,0x82,0x0c,0x99,0x94,0x45,0xd8,0x92,0x46,0xc8,0xc3,0x59}}; 26 | 27 | char fp_header[] = {0x46, 0x50, 0x4c, 0x59, 0x03, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x14}; 28 | 29 | struct fairplay_s { 30 | logger_t *logger; 31 | 32 | unsigned char keymsg[164]; 33 | unsigned int keymsglen; 34 | }; 35 | 36 | fairplay_t * 37 | fairplay_init(logger_t *logger) 38 | { 39 | fairplay_t *fp; 40 | 41 | fp = calloc(1, sizeof(fairplay_t)); 42 | if (!fp) { 43 | return NULL; 44 | } 45 | fp->logger = logger; 46 | 47 | return fp; 48 | } 49 | 50 | int 51 | fairplay_setup(fairplay_t *fp, const unsigned char req[16], unsigned char res[142]) 52 | { 53 | int mode; 54 | 55 | assert(fp); 56 | 57 | if (req[4] != 0x03) { 58 | /* Unsupported fairplay version */ 59 | return -1; 60 | } 61 | 62 | mode = req[14]; 63 | memcpy(res, reply_message[mode], 142); 64 | fp->keymsglen = 0; 65 | return 0; 66 | } 67 | 68 | int 69 | fairplay_handshake(fairplay_t *fp, const unsigned char req[164], unsigned char res[32]) 70 | { 71 | assert(fp); 72 | 73 | if (req[4] != 0x03) { 74 | /* Unsupported fairplay version */ 75 | return -1; 76 | } 77 | 78 | memcpy(fp->keymsg, req, 164); 79 | fp->keymsglen = 164; 80 | 81 | memcpy(res, fp_header, 12); 82 | memcpy(res + 12, req + 144, 20); 83 | return 0; 84 | } 85 | 86 | int 87 | fairplay_decrypt(fairplay_t *fp, const unsigned char input[72], unsigned char output[16]) 88 | { 89 | if (fp->keymsglen != 164) { 90 | return -1; 91 | } 92 | 93 | playfair_decrypt(fp->keymsg, (unsigned char *) input, output); 94 | return 0; 95 | } 96 | 97 | void 98 | fairplay_destroy(fairplay_t *fp) 99 | { 100 | free(fp); 101 | } 102 | -------------------------------------------------------------------------------- /lib/fcup_request.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 fduncanh 3 | * All Rights Reserved. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | */ 16 | 17 | /* this file is part of raop.c via http_handlers.h and should not be included in any other file */ 18 | 19 | 20 | //produces the fcup request plist in xml format as a null-terminated string 21 | char *create_fcup_request(const char *url, int request_id, const char *client_session_id, int *datalen) { 22 | char *plist_xml = NULL; 23 | /* values taken from apsdk-public; */ 24 | /* these seem to be arbitrary choices */ 25 | const int sessionID = 1; 26 | const int FCUP_Response_ClientInfo = 1; 27 | const int FCUP_Response_ClientRef = 40030004; 28 | 29 | /* taken from a working AppleTV? */ 30 | const char User_Agent[] = "AppleCoreMedia/1.0.0.11B554a (Apple TV; U; CPU OS 7_0_4 like Mac OS X; en_us"; 31 | 32 | plist_t req_root_node = plist_new_dict(); 33 | 34 | plist_t session_id_node = plist_new_uint((int64_t) sessionID); 35 | plist_dict_set_item(req_root_node, "sessionID", session_id_node); 36 | plist_t type_node = plist_new_string("unhandledURLRequest"); 37 | plist_dict_set_item(req_root_node, "type", type_node); 38 | 39 | plist_t fcup_request_node = plist_new_dict(); 40 | 41 | plist_t client_info_node = plist_new_uint(FCUP_Response_ClientInfo); 42 | plist_dict_set_item(fcup_request_node, "FCUP_Response_ClientInfo", client_info_node); 43 | plist_t client_ref_node = plist_new_uint((int64_t) FCUP_Response_ClientRef); 44 | plist_dict_set_item(fcup_request_node, "FCUP_Response_ClientRef", client_ref_node); 45 | plist_t request_id_node = plist_new_uint((int64_t) request_id); 46 | plist_dict_set_item(fcup_request_node, "FCUP_Response_RequestID", request_id_node); 47 | plist_t url_node = plist_new_string(url); 48 | plist_dict_set_item(fcup_request_node, "FCUP_Response_URL", url_node); 49 | plist_t session_id1_node = plist_new_uint((int64_t) sessionID); 50 | plist_dict_set_item(fcup_request_node, "sessionID", session_id1_node); 51 | 52 | plist_t fcup_response_header_node = plist_new_dict(); 53 | plist_t playback_session_id_node = plist_new_string(client_session_id); 54 | plist_dict_set_item(fcup_response_header_node, "X-Playback-Session-Id", playback_session_id_node); 55 | plist_t user_agent_node = plist_new_string(User_Agent); 56 | plist_dict_set_item(fcup_response_header_node, "User-Agent", user_agent_node); 57 | 58 | plist_dict_set_item(fcup_request_node, "FCUP_Response_Headers", fcup_response_header_node); 59 | plist_dict_set_item(req_root_node, "request", fcup_request_node); 60 | 61 | uint32_t uint_val; 62 | 63 | plist_to_xml(req_root_node, &plist_xml, &uint_val); 64 | *datalen = (int) uint_val; 65 | plist_free(req_root_node); 66 | assert(plist_xml[*datalen] == '\0'); 67 | return plist_xml; //needs to be freed after use 68 | } 69 | 70 | int fcup_request(void *conn_opaque, const char *media_url, const char *client_session_id, int request_id) { 71 | 72 | raop_conn_t *conn = (raop_conn_t *) conn_opaque; 73 | int datalen = 0; 74 | int requestlen; 75 | 76 | int socket_fd = httpd_get_connection_socket_by_type(conn->raop->httpd, CONNECTION_TYPE_PTTH, 1); 77 | 78 | logger_log(conn->raop->logger, LOGGER_DEBUG, "fcup_request send socket = %d", socket_fd); 79 | 80 | /* create xml plist request data */ 81 | char *plist_xml = create_fcup_request(media_url, request_id, client_session_id, &datalen); 82 | 83 | /* use http_response tools for creating the reverse http request */ 84 | http_response_t *request = http_response_create(); 85 | http_response_reverse_request_init(request, "POST", "/event", "HTTP/1.1"); 86 | http_response_add_header(request, "X-Apple-Session-ID", client_session_id); 87 | http_response_add_header(request, "Content-Type", "text/x-apple-plist+xml"); 88 | http_response_finish(request, plist_xml, datalen); 89 | 90 | free(plist_xml); 91 | 92 | const char *http_request = http_response_get_data(request, &requestlen); 93 | int send_len = send(socket_fd, http_request, requestlen, 0); 94 | if (send_len < 0) { 95 | int sock_err = SOCKET_GET_ERROR(); 96 | logger_log(conn->raop->logger, LOGGER_ERR, "fcup_request: send error %d:%s\n", 97 | sock_err, strerror(sock_err)); 98 | http_response_destroy(request); 99 | /* shut down connection? */ 100 | return -1; 101 | } 102 | 103 | if (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG) { 104 | char *request_str = utils_data_to_text(http_request, requestlen); 105 | logger_log(conn->raop->logger, LOGGER_DEBUG, "\n%s", request_str); 106 | free (request_str); 107 | } 108 | http_response_destroy(request); 109 | logger_log(conn->raop->logger, LOGGER_DEBUG,"fcup_request: send sent Request of %d bytes from socket %d\n", 110 | send_len, socket_fd); 111 | return 0; 112 | } 113 | -------------------------------------------------------------------------------- /lib/global.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================== 15 | * modified by fduncanh 2021-2022 16 | */ 17 | 18 | #ifndef GLOBAL_H 19 | #define GLOBAL_H 20 | 21 | #define GLOBAL_MODEL "AppleTV3,2" 22 | #define GLOBAL_VERSION "220.68" 23 | 24 | /* use old protocol for audio AES key if client's User-Agent string is contained in these strings */ 25 | /* replace xxx by any new User-Agent string as needed */ 26 | #define OLD_PROTOCOL_CLIENT_USER_AGENT_LIST "AirMyPC/2.0;xxx" 27 | 28 | #define DECRYPTION_TEST 0 /* set to 1 or 2 to examine audio decryption */ 29 | 30 | #define MAX_HWADDR_LEN 6 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /lib/http_request.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================== 15 | * modified by fduncanh 2021 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "http_request.h" 24 | #include "llhttp/llhttp.h" 25 | 26 | struct http_request_s { 27 | llhttp_t parser; 28 | llhttp_settings_t parser_settings; 29 | 30 | bool is_reverse; // if true, this is a reverse-response from client 31 | const char *method; 32 | char *url; 33 | char protocol[9]; 34 | 35 | char **headers; 36 | int headers_size; 37 | int headers_index; 38 | 39 | char *data; 40 | int datalen; 41 | 42 | int complete; 43 | }; 44 | 45 | static int 46 | on_url(llhttp_t *parser, const char *at, size_t length) 47 | { 48 | http_request_t *request = parser->data; 49 | int urllen = request->url ? strlen(request->url) : 0; 50 | 51 | request->url = realloc(request->url, urllen+length+1); 52 | assert(request->url); 53 | 54 | request->url[urllen] = '\0'; 55 | strncat(request->url, at, length); 56 | 57 | strncpy(request->protocol, at + length + 1, 8); 58 | 59 | return 0; 60 | } 61 | 62 | static int 63 | on_header_field(llhttp_t *parser, const char *at, size_t length) 64 | { 65 | http_request_t *request = parser->data; 66 | 67 | /* Check if our index is a value */ 68 | if (request->headers_index%2 == 1) { 69 | request->headers_index++; 70 | } 71 | 72 | /* Allocate space for new field-value pair */ 73 | if (request->headers_index == request->headers_size) { 74 | request->headers_size += 2; 75 | request->headers = realloc(request->headers, 76 | request->headers_size*sizeof(char*)); 77 | assert(request->headers); 78 | request->headers[request->headers_index] = NULL; 79 | request->headers[request->headers_index+1] = NULL; 80 | } 81 | 82 | /* Allocate space in the current header string */ 83 | if (request->headers[request->headers_index] == NULL) { 84 | request->headers[request->headers_index] = calloc(1, length+1); 85 | } else { 86 | request->headers[request->headers_index] = realloc( 87 | request->headers[request->headers_index], 88 | strlen(request->headers[request->headers_index])+length+1 89 | ); 90 | } 91 | assert(request->headers[request->headers_index]); 92 | 93 | strncat(request->headers[request->headers_index], at, length); 94 | return 0; 95 | } 96 | 97 | static int 98 | on_header_value(llhttp_t *parser, const char *at, size_t length) 99 | { 100 | http_request_t *request = parser->data; 101 | 102 | /* Check if our index is a field */ 103 | if (request->headers_index%2 == 0) { 104 | request->headers_index++; 105 | } 106 | 107 | /* Allocate space in the current header string */ 108 | if (request->headers[request->headers_index] == NULL) { 109 | request->headers[request->headers_index] = calloc(1, length+1); 110 | } else { 111 | request->headers[request->headers_index] = realloc( 112 | request->headers[request->headers_index], 113 | strlen(request->headers[request->headers_index])+length+1 114 | ); 115 | } 116 | assert(request->headers[request->headers_index]); 117 | 118 | strncat(request->headers[request->headers_index], at, length); 119 | return 0; 120 | } 121 | 122 | static int 123 | on_body(llhttp_t *parser, const char *at, size_t length) 124 | { 125 | http_request_t *request = parser->data; 126 | 127 | request->data = realloc(request->data, request->datalen+length); 128 | assert(request->data); 129 | 130 | memcpy(request->data+request->datalen, at, length); 131 | request->datalen += length; 132 | return 0; 133 | } 134 | 135 | static int 136 | on_message_complete(llhttp_t *parser) 137 | { 138 | http_request_t *request = parser->data; 139 | 140 | request->method = llhttp_method_name(request->parser.method); 141 | request->complete = 1; 142 | return 0; 143 | } 144 | 145 | http_request_t * 146 | http_request_init(void) 147 | { 148 | http_request_t *request; 149 | 150 | request = calloc(1, sizeof(http_request_t)); 151 | if (!request) { 152 | return NULL; 153 | } 154 | 155 | llhttp_settings_init(&request->parser_settings); 156 | request->parser_settings.on_url = &on_url; 157 | request->parser_settings.on_header_field = &on_header_field; 158 | request->parser_settings.on_header_value = &on_header_value; 159 | request->parser_settings.on_body = &on_body; 160 | request->parser_settings.on_message_complete = &on_message_complete; 161 | 162 | llhttp_init(&request->parser, HTTP_REQUEST, &request->parser_settings); 163 | request->parser.data = request; 164 | request->is_reverse = false; 165 | return request; 166 | } 167 | 168 | void 169 | http_request_destroy(http_request_t *request) 170 | { 171 | int i; 172 | 173 | if (request) { 174 | free(request->url); 175 | for (i=0; iheaders_size; i++) { 176 | free(request->headers[i]); 177 | } 178 | free(request->headers); 179 | free(request->data); 180 | free(request); 181 | } 182 | } 183 | 184 | int 185 | http_request_add_data(http_request_t *request, const char *data, int datalen) 186 | { 187 | int ret; 188 | 189 | assert(request); 190 | 191 | ret = llhttp_execute(&request->parser, data, datalen); 192 | 193 | /* support for "Upgrade" to reverse http ("PTTH/1.0") protocol */ 194 | llhttp_resume_after_upgrade(&request->parser); 195 | 196 | return ret; 197 | } 198 | 199 | int 200 | http_request_is_complete(http_request_t *request) 201 | { 202 | assert(request); 203 | return request->complete; 204 | } 205 | 206 | int 207 | http_request_has_error(http_request_t *request) 208 | { 209 | assert(request); 210 | if (request->is_reverse) { 211 | return 0; 212 | } 213 | return (llhttp_get_errno(&request->parser) != HPE_OK); 214 | } 215 | 216 | const char * 217 | http_request_get_error_name(http_request_t *request) 218 | { 219 | assert(request); 220 | if (request->is_reverse) { 221 | return NULL; 222 | } 223 | return llhttp_errno_name(llhttp_get_errno(&request->parser)); 224 | } 225 | 226 | const char * 227 | http_request_get_error_description(http_request_t *request) 228 | { 229 | assert(request); 230 | if (request->is_reverse) { 231 | return NULL; 232 | } 233 | return llhttp_get_error_reason(&request->parser); 234 | } 235 | 236 | const char * 237 | http_request_get_method(http_request_t *request) 238 | { 239 | assert(request); 240 | if (request->is_reverse) { 241 | return NULL; 242 | } 243 | return request->method; 244 | } 245 | 246 | const char * 247 | http_request_get_url(http_request_t *request) 248 | { 249 | assert(request); 250 | if (request->is_reverse) { 251 | return NULL; 252 | } 253 | return request->url; 254 | } 255 | 256 | const char * 257 | http_request_get_protocol(http_request_t *request) 258 | { 259 | assert(request); 260 | if (request->is_reverse) { 261 | return NULL; 262 | } 263 | return request->protocol; 264 | } 265 | 266 | const char * 267 | http_request_get_header(http_request_t *request, const char *name) 268 | { 269 | int i; 270 | 271 | assert(request); 272 | if (request->is_reverse) { 273 | return NULL; 274 | } 275 | 276 | for (i=0; iheaders_size; i+=2) { 277 | if (!strcmp(request->headers[i], name)) { 278 | return request->headers[i+1]; 279 | } 280 | } 281 | return NULL; 282 | } 283 | 284 | const char * 285 | http_request_get_data(http_request_t *request, int *datalen) 286 | { 287 | assert(request); 288 | if (datalen) { 289 | *datalen = request->datalen; 290 | } 291 | return request->data; 292 | } 293 | 294 | int 295 | http_request_get_header_string(http_request_t *request, char **header_str) 296 | { 297 | if(!request || request->headers_size == 0) { 298 | *header_str = NULL; 299 | return 0; 300 | } 301 | if (request->is_reverse) { 302 | *header_str = NULL; 303 | return 0; 304 | } 305 | int len = 0; 306 | for (int i = 0; i < request->headers_size; i++) { 307 | len += strlen(request->headers[i]); 308 | if (i%2 == 0) { 309 | len += 2; 310 | } else { 311 | len++; 312 | } 313 | } 314 | char *str = calloc(len+1, sizeof(char)); 315 | assert(str); 316 | *header_str = str; 317 | char *p = str; 318 | int n = len + 1; 319 | for (int i = 0; i < request->headers_size; i++) { 320 | int hlen = strlen(request->headers[i]); 321 | snprintf(p, n, "%s", request->headers[i]); 322 | n -= hlen; 323 | p += hlen; 324 | if (i%2 == 0) { 325 | snprintf(p, n, ": "); 326 | n -= 2; 327 | p += 2; 328 | } else { 329 | snprintf(p, n, "\n"); 330 | n--; 331 | p++; 332 | } 333 | } 334 | assert(p == &(str[len])); 335 | return len; 336 | } 337 | 338 | bool http_request_is_reverse(http_request_t *request) { 339 | return request->is_reverse; 340 | } 341 | 342 | void http_request_set_reverse(http_request_t *request) { 343 | request->is_reverse = true; 344 | } 345 | -------------------------------------------------------------------------------- /lib/http_request.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | #ifndef HTTP_REQUEST_H 16 | #define HTTP_REQUEST_H 17 | 18 | #include 19 | 20 | typedef struct http_request_s http_request_t; 21 | 22 | http_request_t *http_request_init(void); 23 | 24 | int http_request_add_data(http_request_t *request, const char *data, int datalen); 25 | int http_request_is_complete(http_request_t *request); 26 | int http_request_has_error(http_request_t *request); 27 | 28 | const char *http_request_get_error_name(http_request_t *request); 29 | const char *http_request_get_error_description(http_request_t *request); 30 | const char *http_request_get_method(http_request_t *request); 31 | const char *http_request_get_url(http_request_t *request); 32 | const char *http_request_get_protocol(http_request_t *request); 33 | const char *http_request_get_header(http_request_t *request, const char *name); 34 | const char *http_request_get_data(http_request_t *request, int *datalen); 35 | int http_request_get_header_string(http_request_t *request, char **header_str); 36 | bool http_request_is_reverse(http_request_t *request); 37 | void http_request_set_reverse(http_request_t *request); 38 | 39 | void http_request_destroy(http_request_t *request); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /lib/http_response.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "http_response.h" 21 | #include "compat.h" 22 | 23 | struct http_response_s { 24 | int complete; 25 | int disconnect; 26 | 27 | char *data; 28 | int data_size; 29 | int data_length; 30 | }; 31 | 32 | 33 | static void 34 | http_response_add_data(http_response_t *response, const char *data, int datalen) 35 | { 36 | int newdatasize; 37 | 38 | assert(response); 39 | assert(data); 40 | assert(datalen > 0); 41 | 42 | newdatasize = response->data_size; 43 | while (response->data_size+datalen > newdatasize) { 44 | newdatasize *= 2; 45 | } 46 | if (newdatasize != response->data_size) { 47 | response->data = realloc(response->data, newdatasize); 48 | assert(response->data); 49 | } 50 | memcpy(response->data+response->data_length, data, datalen); 51 | response->data_length += datalen; 52 | } 53 | 54 | 55 | http_response_t * 56 | http_response_create() 57 | { 58 | http_response_t *response = (http_response_t *) calloc(1, sizeof(http_response_t)); 59 | if (!response) { 60 | return NULL; 61 | } 62 | /* Allocate response data */ 63 | response->data_size = 1024; 64 | response->data = (char *) malloc(response->data_size); 65 | if (!response->data) { 66 | free(response); 67 | return NULL; 68 | } 69 | return response; 70 | } 71 | 72 | void 73 | http_response_init(http_response_t *response, const char *protocol, int code, const char *message) 74 | { 75 | assert(response); 76 | response->data_length = 0; /* can be used to reinitialize a previously-initialized response */ 77 | char codestr[4]; 78 | 79 | assert(code >= 100 && code < 1000); 80 | 81 | /* Convert code into string */ 82 | memset(codestr, 0, sizeof(codestr)); 83 | snprintf(codestr, sizeof(codestr), "%u", code); 84 | 85 | /* Add first line of response to the data array */ 86 | http_response_add_data(response, protocol, strlen(protocol)); 87 | http_response_add_data(response, " ", 1); 88 | http_response_add_data(response, codestr, strlen(codestr)); 89 | http_response_add_data(response, " ", 1); 90 | http_response_add_data(response, message, strlen(message)); 91 | http_response_add_data(response, "\r\n", 2); 92 | } 93 | 94 | void 95 | http_response_reverse_request_init(http_response_t *request, const char *method, const char *url, const char *protocol) 96 | { 97 | assert(request); 98 | request->data_length = 0; /* reinitialize a previously-initialized response as a reverse-HTTP (PTTH/1.0) request */ 99 | 100 | /* Add first line of response to the data array */ 101 | http_response_add_data(request, method, strlen(method)); 102 | http_response_add_data(request, " ", 1); 103 | http_response_add_data(request, url, strlen(url)); 104 | http_response_add_data(request, " ", 1); 105 | http_response_add_data(request, protocol, strlen(protocol)); 106 | http_response_add_data(request, "\r\n", 2); 107 | } 108 | 109 | void 110 | http_response_destroy(http_response_t *response) 111 | { 112 | if (response) { 113 | free(response->data); 114 | free(response); 115 | } 116 | } 117 | 118 | void 119 | http_response_add_header(http_response_t *response, const char *name, const char *value) 120 | { 121 | assert(response); 122 | assert(name); 123 | assert(value); 124 | 125 | http_response_add_data(response, name, strlen(name)); 126 | http_response_add_data(response, ": ", 2); 127 | http_response_add_data(response, value, strlen(value)); 128 | http_response_add_data(response, "\r\n", 2); 129 | } 130 | 131 | void 132 | http_response_finish(http_response_t *response, const char *data, int datalen) 133 | { 134 | assert(response); 135 | assert(datalen==0 || (data && datalen > 0)); 136 | 137 | if (data && datalen > 0) { 138 | const char *hdrname = "Content-Length"; 139 | char hdrvalue[16]; 140 | 141 | memset(hdrvalue, 0, sizeof(hdrvalue)); 142 | snprintf(hdrvalue, sizeof(hdrvalue)-1, "%d", datalen); 143 | 144 | /* Add Content-Length header first */ 145 | http_response_add_data(response, hdrname, strlen(hdrname)); 146 | http_response_add_data(response, ": ", 2); 147 | http_response_add_data(response, hdrvalue, strlen(hdrvalue)); 148 | http_response_add_data(response, "\r\n\r\n", 4); 149 | 150 | /* Add data to the end of response */ 151 | http_response_add_data(response, data, datalen); 152 | } else { 153 | /* check for "Content-Type" header, with datalen = 0 */ 154 | const char *item = "Content-Type"; 155 | int item_len = strlen(item); 156 | const char *part = response->data; 157 | int end = response->data_length - item_len; 158 | for (int i = 0; i < end; i++) { 159 | if (memcmp (part, item, item_len) == 0) { 160 | const char *hdrname = "Content-Length: 0"; 161 | http_response_add_data(response, hdrname, strlen(hdrname)); 162 | http_response_add_data(response, "\r\n", 2); 163 | break; 164 | } 165 | part++; 166 | } 167 | /* Add extra end of line after headers */ 168 | http_response_add_data(response, "\r\n", 2); 169 | } 170 | response->complete = 1; 171 | } 172 | 173 | void 174 | http_response_set_disconnect(http_response_t *response, int disconnect) 175 | { 176 | assert(response); 177 | 178 | response->disconnect = !!disconnect; 179 | } 180 | 181 | int 182 | http_response_get_disconnect(http_response_t *response) 183 | { 184 | assert(response); 185 | 186 | return response->disconnect; 187 | } 188 | 189 | const char * 190 | http_response_get_data(http_response_t *response, int *datalen) 191 | { 192 | assert(response); 193 | assert(datalen); 194 | assert(response->complete); 195 | 196 | *datalen = response->data_length; 197 | return response->data; 198 | } 199 | -------------------------------------------------------------------------------- /lib/http_response.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *============================================================== 15 | * modified fduncanh 2024 16 | */ 17 | 18 | #ifndef HTTP_RESPONSE_H 19 | #define HTTP_RESPONSE_H 20 | 21 | typedef struct http_response_s http_response_t; 22 | 23 | http_response_t *http_response_create(); 24 | void http_response_init(http_response_t *response, const char *protocol, int code, const char *message); 25 | void http_response_reverse_request_init(http_response_t *request, const char *method, const char *url, 26 | const char *protocol); 27 | 28 | void http_response_add_header(http_response_t *response, const char *name, const char *value); 29 | void http_response_finish(http_response_t *response, const char *data, int datalen); 30 | 31 | void http_response_set_disconnect(http_response_t *response, int disconnect); 32 | int http_response_get_disconnect(http_response_t *response); 33 | 34 | const char *http_response_get_data(http_response_t *response, int *datalen); 35 | 36 | void http_response_destroy(http_response_t *response); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /lib/httpd.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | #ifndef HTTPD_H 16 | #define HTTPD_H 17 | 18 | #include "logger.h" 19 | #include "http_request.h" 20 | #include "http_response.h" 21 | 22 | typedef struct httpd_s httpd_t; 23 | 24 | typedef enum connectype_type_e { 25 | CONNECTION_TYPE_UNKNOWN, 26 | CONNECTION_TYPE_RAOP, 27 | CONNECTION_TYPE_AIRPLAY, 28 | CONNECTION_TYPE_PTTH, 29 | CONNECTION_TYPE_HLS 30 | } connection_type_t; 31 | 32 | struct httpd_callbacks_s { 33 | void* opaque; 34 | void* (*conn_init)(void *opaque, unsigned char *local, int locallen, unsigned char *remote, 35 | int remotelen, unsigned int zone_id); 36 | void (*conn_request)(void *ptr, http_request_t *request, http_response_t **response); 37 | void (*conn_destroy)(void *ptr); 38 | }; 39 | typedef struct httpd_callbacks_s httpd_callbacks_t; 40 | bool httpd_nohold(httpd_t *httpd); 41 | void httpd_remove_known_connections(httpd_t *httpd); 42 | 43 | int httpd_set_connection_type (httpd_t *http, void *user_data, connection_type_t type); 44 | int httpd_count_connection_type (httpd_t *http, connection_type_t type); 45 | int httpd_get_connection_socket (httpd_t *httpd, void *user_data); 46 | int httpd_get_connection_socket_by_type (httpd_t *httpd, connection_type_t type, int instance); 47 | const char *httpd_get_connection_typename (connection_type_t type); 48 | void *httpd_get_connection_by_type (httpd_t *httpd, connection_type_t type, int instance); 49 | httpd_t *httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int nohold); 50 | 51 | int httpd_is_running(httpd_t *httpd); 52 | 53 | int httpd_start(httpd_t *httpd, unsigned short *port); 54 | void httpd_stop(httpd_t *httpd); 55 | 56 | void httpd_destroy(httpd_t *httpd); 57 | 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /lib/llhttp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | aux_source_directory(. llhttp_src) 3 | set(DIR_SRCS ${llhttp_src}) 4 | include_directories(.) 5 | add_library( llhttp 6 | STATIC 7 | ${DIR_SRCS}) 8 | -------------------------------------------------------------------------------- /lib/llhttp/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT License. 2 | 3 | Copyright Fedor Indutny, 2018. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to permit 10 | persons to whom the Software is furnished to do so, subject to the 11 | following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 19 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 20 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 21 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 22 | USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/llhttp/api.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "llhttp.h" 6 | 7 | #define CALLBACK_MAYBE(PARSER, NAME) \ 8 | do { \ 9 | const llhttp_settings_t* settings; \ 10 | settings = (const llhttp_settings_t*) (PARSER)->settings; \ 11 | if (settings == NULL || settings->NAME == NULL) { \ 12 | err = 0; \ 13 | break; \ 14 | } \ 15 | err = settings->NAME((PARSER)); \ 16 | } while (0) 17 | 18 | #define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN) \ 19 | do { \ 20 | const llhttp_settings_t* settings; \ 21 | settings = (const llhttp_settings_t*) (PARSER)->settings; \ 22 | if (settings == NULL || settings->NAME == NULL) { \ 23 | err = 0; \ 24 | break; \ 25 | } \ 26 | err = settings->NAME((PARSER), (START), (LEN)); \ 27 | if (err == -1) { \ 28 | err = HPE_USER; \ 29 | llhttp_set_error_reason((PARSER), "Span callback error in " #NAME); \ 30 | } \ 31 | } while (0) 32 | 33 | void llhttp_init(llhttp_t* parser, llhttp_type_t type, 34 | const llhttp_settings_t* settings) { 35 | llhttp__internal_init(parser); 36 | 37 | parser->type = type; 38 | parser->settings = (void*) settings; 39 | } 40 | 41 | 42 | #if defined(__wasm__) 43 | 44 | extern int wasm_on_message_begin(llhttp_t * p); 45 | extern int wasm_on_url(llhttp_t* p, const char* at, size_t length); 46 | extern int wasm_on_status(llhttp_t* p, const char* at, size_t length); 47 | extern int wasm_on_header_field(llhttp_t* p, const char* at, size_t length); 48 | extern int wasm_on_header_value(llhttp_t* p, const char* at, size_t length); 49 | extern int wasm_on_headers_complete(llhttp_t * p, int status_code, 50 | uint8_t upgrade, int should_keep_alive); 51 | extern int wasm_on_body(llhttp_t* p, const char* at, size_t length); 52 | extern int wasm_on_message_complete(llhttp_t * p); 53 | 54 | static int wasm_on_headers_complete_wrap(llhttp_t* p) { 55 | return wasm_on_headers_complete(p, p->status_code, p->upgrade, 56 | llhttp_should_keep_alive(p)); 57 | } 58 | 59 | const llhttp_settings_t wasm_settings = { 60 | wasm_on_message_begin, 61 | wasm_on_url, 62 | wasm_on_status, 63 | NULL, 64 | NULL, 65 | wasm_on_header_field, 66 | wasm_on_header_value, 67 | NULL, 68 | NULL, 69 | wasm_on_headers_complete_wrap, 70 | wasm_on_body, 71 | wasm_on_message_complete, 72 | NULL, 73 | NULL, 74 | NULL, 75 | NULL, 76 | NULL, 77 | NULL, 78 | NULL, 79 | NULL, 80 | NULL, 81 | NULL, 82 | NULL, 83 | }; 84 | 85 | 86 | llhttp_t* llhttp_alloc(llhttp_type_t type) { 87 | llhttp_t* parser = malloc(sizeof(llhttp_t)); 88 | llhttp_init(parser, type, &wasm_settings); 89 | return parser; 90 | } 91 | 92 | void llhttp_free(llhttp_t* parser) { 93 | free(parser); 94 | } 95 | 96 | #endif // defined(__wasm__) 97 | 98 | /* Some getters required to get stuff from the parser */ 99 | 100 | uint8_t llhttp_get_type(llhttp_t* parser) { 101 | return parser->type; 102 | } 103 | 104 | uint8_t llhttp_get_http_major(llhttp_t* parser) { 105 | return parser->http_major; 106 | } 107 | 108 | uint8_t llhttp_get_http_minor(llhttp_t* parser) { 109 | return parser->http_minor; 110 | } 111 | 112 | uint8_t llhttp_get_method(llhttp_t* parser) { 113 | return parser->method; 114 | } 115 | 116 | int llhttp_get_status_code(llhttp_t* parser) { 117 | return parser->status_code; 118 | } 119 | 120 | uint8_t llhttp_get_upgrade(llhttp_t* parser) { 121 | return parser->upgrade; 122 | } 123 | 124 | 125 | void llhttp_reset(llhttp_t* parser) { 126 | llhttp_type_t type = parser->type; 127 | const llhttp_settings_t* settings = parser->settings; 128 | void* data = parser->data; 129 | uint16_t lenient_flags = parser->lenient_flags; 130 | 131 | llhttp__internal_init(parser); 132 | 133 | parser->type = type; 134 | parser->settings = (void*) settings; 135 | parser->data = data; 136 | parser->lenient_flags = lenient_flags; 137 | } 138 | 139 | 140 | llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) { 141 | return llhttp__internal_execute(parser, data, data + len); 142 | } 143 | 144 | 145 | void llhttp_settings_init(llhttp_settings_t* settings) { 146 | memset(settings, 0, sizeof(*settings)); 147 | } 148 | 149 | 150 | llhttp_errno_t llhttp_finish(llhttp_t* parser) { 151 | int err; 152 | 153 | /* We're in an error state. Don't bother doing anything. */ 154 | if (parser->error != 0) { 155 | return 0; 156 | } 157 | 158 | switch (parser->finish) { 159 | case HTTP_FINISH_SAFE_WITH_CB: 160 | CALLBACK_MAYBE(parser, on_message_complete); 161 | if (err != HPE_OK) return err; 162 | 163 | /* FALLTHROUGH */ 164 | case HTTP_FINISH_SAFE: 165 | return HPE_OK; 166 | case HTTP_FINISH_UNSAFE: 167 | parser->reason = "Invalid EOF state"; 168 | return HPE_INVALID_EOF_STATE; 169 | default: 170 | abort(); 171 | } 172 | } 173 | 174 | 175 | void llhttp_pause(llhttp_t* parser) { 176 | if (parser->error != HPE_OK) { 177 | return; 178 | } 179 | 180 | parser->error = HPE_PAUSED; 181 | parser->reason = "Paused"; 182 | } 183 | 184 | 185 | void llhttp_resume(llhttp_t* parser) { 186 | if (parser->error != HPE_PAUSED) { 187 | return; 188 | } 189 | 190 | parser->error = 0; 191 | } 192 | 193 | 194 | void llhttp_resume_after_upgrade(llhttp_t* parser) { 195 | if (parser->error != HPE_PAUSED_UPGRADE) { 196 | return; 197 | } 198 | 199 | parser->error = 0; 200 | } 201 | 202 | 203 | llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) { 204 | return parser->error; 205 | } 206 | 207 | 208 | const char* llhttp_get_error_reason(const llhttp_t* parser) { 209 | return parser->reason; 210 | } 211 | 212 | 213 | void llhttp_set_error_reason(llhttp_t* parser, const char* reason) { 214 | parser->reason = reason; 215 | } 216 | 217 | 218 | const char* llhttp_get_error_pos(const llhttp_t* parser) { 219 | return parser->error_pos; 220 | } 221 | 222 | 223 | const char* llhttp_errno_name(llhttp_errno_t err) { 224 | #define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME; 225 | switch (err) { 226 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 227 | default: abort(); 228 | } 229 | #undef HTTP_ERRNO_GEN 230 | } 231 | 232 | 233 | const char* llhttp_method_name(llhttp_method_t method) { 234 | #define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING; 235 | switch (method) { 236 | HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN) 237 | default: abort(); 238 | } 239 | #undef HTTP_METHOD_GEN 240 | } 241 | 242 | const char* llhttp_status_name(llhttp_status_t status) { 243 | #define HTTP_STATUS_GEN(NUM, NAME, STRING) case HTTP_STATUS_##NAME: return #STRING; 244 | switch (status) { 245 | HTTP_STATUS_MAP(HTTP_STATUS_GEN) 246 | default: abort(); 247 | } 248 | #undef HTTP_STATUS_GEN 249 | } 250 | 251 | 252 | void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) { 253 | if (enabled) { 254 | parser->lenient_flags |= LENIENT_HEADERS; 255 | } else { 256 | parser->lenient_flags &= ~LENIENT_HEADERS; 257 | } 258 | } 259 | 260 | 261 | void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) { 262 | if (enabled) { 263 | parser->lenient_flags |= LENIENT_CHUNKED_LENGTH; 264 | } else { 265 | parser->lenient_flags &= ~LENIENT_CHUNKED_LENGTH; 266 | } 267 | } 268 | 269 | 270 | void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) { 271 | if (enabled) { 272 | parser->lenient_flags |= LENIENT_KEEP_ALIVE; 273 | } else { 274 | parser->lenient_flags &= ~LENIENT_KEEP_ALIVE; 275 | } 276 | } 277 | 278 | void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled) { 279 | if (enabled) { 280 | parser->lenient_flags |= LENIENT_TRANSFER_ENCODING; 281 | } else { 282 | parser->lenient_flags &= ~LENIENT_TRANSFER_ENCODING; 283 | } 284 | } 285 | 286 | void llhttp_set_lenient_version(llhttp_t* parser, int enabled) { 287 | if (enabled) { 288 | parser->lenient_flags |= LENIENT_VERSION; 289 | } else { 290 | parser->lenient_flags &= ~LENIENT_VERSION; 291 | } 292 | } 293 | 294 | void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled) { 295 | if (enabled) { 296 | parser->lenient_flags |= LENIENT_DATA_AFTER_CLOSE; 297 | } else { 298 | parser->lenient_flags &= ~LENIENT_DATA_AFTER_CLOSE; 299 | } 300 | } 301 | 302 | void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled) { 303 | if (enabled) { 304 | parser->lenient_flags |= LENIENT_OPTIONAL_LF_AFTER_CR; 305 | } else { 306 | parser->lenient_flags &= ~LENIENT_OPTIONAL_LF_AFTER_CR; 307 | } 308 | } 309 | 310 | void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled) { 311 | if (enabled) { 312 | parser->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; 313 | } else { 314 | parser->lenient_flags &= ~LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; 315 | } 316 | } 317 | 318 | void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled) { 319 | if (enabled) { 320 | parser->lenient_flags |= LENIENT_OPTIONAL_CR_BEFORE_LF; 321 | } else { 322 | parser->lenient_flags &= ~LENIENT_OPTIONAL_CR_BEFORE_LF; 323 | } 324 | } 325 | 326 | void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled) { 327 | if (enabled) { 328 | parser->lenient_flags |= LENIENT_SPACES_AFTER_CHUNK_SIZE; 329 | } else { 330 | parser->lenient_flags &= ~LENIENT_SPACES_AFTER_CHUNK_SIZE; 331 | } 332 | } 333 | 334 | /* Callbacks */ 335 | 336 | 337 | int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) { 338 | int err; 339 | CALLBACK_MAYBE(s, on_message_begin); 340 | return err; 341 | } 342 | 343 | 344 | int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) { 345 | int err; 346 | SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p); 347 | return err; 348 | } 349 | 350 | 351 | int llhttp__on_url_complete(llhttp_t* s, const char* p, const char* endp) { 352 | int err; 353 | CALLBACK_MAYBE(s, on_url_complete); 354 | return err; 355 | } 356 | 357 | 358 | int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) { 359 | int err; 360 | SPAN_CALLBACK_MAYBE(s, on_status, p, endp - p); 361 | return err; 362 | } 363 | 364 | 365 | int llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) { 366 | int err; 367 | CALLBACK_MAYBE(s, on_status_complete); 368 | return err; 369 | } 370 | 371 | 372 | int llhttp__on_method(llhttp_t* s, const char* p, const char* endp) { 373 | int err; 374 | SPAN_CALLBACK_MAYBE(s, on_method, p, endp - p); 375 | return err; 376 | } 377 | 378 | 379 | int llhttp__on_method_complete(llhttp_t* s, const char* p, const char* endp) { 380 | int err; 381 | CALLBACK_MAYBE(s, on_method_complete); 382 | return err; 383 | } 384 | 385 | 386 | int llhttp__on_version(llhttp_t* s, const char* p, const char* endp) { 387 | int err; 388 | SPAN_CALLBACK_MAYBE(s, on_version, p, endp - p); 389 | return err; 390 | } 391 | 392 | 393 | int llhttp__on_version_complete(llhttp_t* s, const char* p, const char* endp) { 394 | int err; 395 | CALLBACK_MAYBE(s, on_version_complete); 396 | return err; 397 | } 398 | 399 | 400 | int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) { 401 | int err; 402 | SPAN_CALLBACK_MAYBE(s, on_header_field, p, endp - p); 403 | return err; 404 | } 405 | 406 | 407 | int llhttp__on_header_field_complete(llhttp_t* s, const char* p, const char* endp) { 408 | int err; 409 | CALLBACK_MAYBE(s, on_header_field_complete); 410 | return err; 411 | } 412 | 413 | 414 | int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) { 415 | int err; 416 | SPAN_CALLBACK_MAYBE(s, on_header_value, p, endp - p); 417 | return err; 418 | } 419 | 420 | 421 | int llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) { 422 | int err; 423 | CALLBACK_MAYBE(s, on_header_value_complete); 424 | return err; 425 | } 426 | 427 | 428 | int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) { 429 | int err; 430 | CALLBACK_MAYBE(s, on_headers_complete); 431 | return err; 432 | } 433 | 434 | 435 | int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) { 436 | int err; 437 | CALLBACK_MAYBE(s, on_message_complete); 438 | return err; 439 | } 440 | 441 | 442 | int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) { 443 | int err; 444 | SPAN_CALLBACK_MAYBE(s, on_body, p, endp - p); 445 | return err; 446 | } 447 | 448 | 449 | int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) { 450 | int err; 451 | CALLBACK_MAYBE(s, on_chunk_header); 452 | return err; 453 | } 454 | 455 | 456 | int llhttp__on_chunk_extension_name(llhttp_t* s, const char* p, const char* endp) { 457 | int err; 458 | SPAN_CALLBACK_MAYBE(s, on_chunk_extension_name, p, endp - p); 459 | return err; 460 | } 461 | 462 | 463 | int llhttp__on_chunk_extension_name_complete(llhttp_t* s, const char* p, const char* endp) { 464 | int err; 465 | CALLBACK_MAYBE(s, on_chunk_extension_name_complete); 466 | return err; 467 | } 468 | 469 | 470 | int llhttp__on_chunk_extension_value(llhttp_t* s, const char* p, const char* endp) { 471 | int err; 472 | SPAN_CALLBACK_MAYBE(s, on_chunk_extension_value, p, endp - p); 473 | return err; 474 | } 475 | 476 | 477 | int llhttp__on_chunk_extension_value_complete(llhttp_t* s, const char* p, const char* endp) { 478 | int err; 479 | CALLBACK_MAYBE(s, on_chunk_extension_value_complete); 480 | return err; 481 | } 482 | 483 | 484 | int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) { 485 | int err; 486 | CALLBACK_MAYBE(s, on_chunk_complete); 487 | return err; 488 | } 489 | 490 | 491 | int llhttp__on_reset(llhttp_t* s, const char* p, const char* endp) { 492 | int err; 493 | CALLBACK_MAYBE(s, on_reset); 494 | return err; 495 | } 496 | 497 | 498 | /* Private */ 499 | 500 | 501 | void llhttp__debug(llhttp_t* s, const char* p, const char* endp, 502 | const char* msg) { 503 | if (p == endp) { 504 | fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type, 505 | s->flags, msg); 506 | } else { 507 | fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s, 508 | s->type, s->flags, *p, msg); 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /lib/llhttp/http.c: -------------------------------------------------------------------------------- 1 | #include 2 | #ifndef LLHTTP__TEST 3 | # include "llhttp.h" 4 | #else 5 | # define llhttp_t llparse_t 6 | #endif /* */ 7 | 8 | int llhttp_message_needs_eof(const llhttp_t* parser); 9 | int llhttp_should_keep_alive(const llhttp_t* parser); 10 | 11 | int llhttp__before_headers_complete(llhttp_t* parser, const char* p, 12 | const char* endp) { 13 | /* Set this here so that on_headers_complete() callbacks can see it */ 14 | if ((parser->flags & F_UPGRADE) && 15 | (parser->flags & F_CONNECTION_UPGRADE)) { 16 | /* For responses, "Upgrade: foo" and "Connection: upgrade" are 17 | * mandatory only when it is a 101 Switching Protocols response, 18 | * otherwise it is purely informational, to announce support. 19 | */ 20 | parser->upgrade = 21 | (parser->type == HTTP_REQUEST || parser->status_code == 101); 22 | } else { 23 | parser->upgrade = (parser->method == HTTP_CONNECT); 24 | } 25 | return 0; 26 | } 27 | 28 | 29 | /* Return values: 30 | * 0 - No body, `restart`, message_complete 31 | * 1 - CONNECT request, `restart`, message_complete, and pause 32 | * 2 - chunk_size_start 33 | * 3 - body_identity 34 | * 4 - body_identity_eof 35 | * 5 - invalid transfer-encoding for request 36 | */ 37 | int llhttp__after_headers_complete(llhttp_t* parser, const char* p, 38 | const char* endp) { 39 | int hasBody; 40 | 41 | hasBody = parser->flags & F_CHUNKED || parser->content_length > 0; 42 | if ( 43 | (parser->upgrade && (parser->method == HTTP_CONNECT || 44 | (parser->flags & F_SKIPBODY) || !hasBody)) || 45 | /* See RFC 2616 section 4.4 - 1xx e.g. Continue */ 46 | (parser->type == HTTP_RESPONSE && parser->status_code == 101) 47 | ) { 48 | /* Exit, the rest of the message is in a different protocol. */ 49 | return 1; 50 | } 51 | 52 | if (parser->type == HTTP_RESPONSE && parser->status_code == 100) { 53 | /* No body, restart as the message is complete */ 54 | return 0; 55 | } 56 | 57 | /* See RFC 2616 section 4.4 */ 58 | if ( 59 | parser->flags & F_SKIPBODY || /* response to a HEAD request */ 60 | ( 61 | parser->type == HTTP_RESPONSE && ( 62 | parser->status_code == 102 || /* Processing */ 63 | parser->status_code == 103 || /* Early Hints */ 64 | parser->status_code == 204 || /* No Content */ 65 | parser->status_code == 304 /* Not Modified */ 66 | ) 67 | ) 68 | ) { 69 | return 0; 70 | } else if (parser->flags & F_CHUNKED) { 71 | /* chunked encoding - ignore Content-Length header, prepare for a chunk */ 72 | return 2; 73 | } else if (parser->flags & F_TRANSFER_ENCODING) { 74 | if (parser->type == HTTP_REQUEST && 75 | (parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0 && 76 | (parser->lenient_flags & LENIENT_TRANSFER_ENCODING) == 0) { 77 | /* RFC 7230 3.3.3 */ 78 | 79 | /* If a Transfer-Encoding header field 80 | * is present in a request and the chunked transfer coding is not 81 | * the final encoding, the message body length cannot be determined 82 | * reliably; the server MUST respond with the 400 (Bad Request) 83 | * status code and then close the connection. 84 | */ 85 | return 5; 86 | } else { 87 | /* RFC 7230 3.3.3 */ 88 | 89 | /* If a Transfer-Encoding header field is present in a response and 90 | * the chunked transfer coding is not the final encoding, the 91 | * message body length is determined by reading the connection until 92 | * it is closed by the server. 93 | */ 94 | return 4; 95 | } 96 | } else { 97 | if (!(parser->flags & F_CONTENT_LENGTH)) { 98 | if (!llhttp_message_needs_eof(parser)) { 99 | /* Assume content-length 0 - read the next */ 100 | return 0; 101 | } else { 102 | /* Read body until EOF */ 103 | return 4; 104 | } 105 | } else if (parser->content_length == 0) { 106 | /* Content-Length header given but zero: Content-Length: 0\r\n */ 107 | return 0; 108 | } else { 109 | /* Content-Length header given and non-zero */ 110 | return 3; 111 | } 112 | } 113 | } 114 | 115 | 116 | int llhttp__after_message_complete(llhttp_t* parser, const char* p, 117 | const char* endp) { 118 | int should_keep_alive; 119 | 120 | should_keep_alive = llhttp_should_keep_alive(parser); 121 | parser->finish = HTTP_FINISH_SAFE; 122 | parser->flags = 0; 123 | 124 | /* NOTE: this is ignored in loose parsing mode */ 125 | return should_keep_alive; 126 | } 127 | 128 | 129 | int llhttp_message_needs_eof(const llhttp_t* parser) { 130 | if (parser->type == HTTP_REQUEST) { 131 | return 0; 132 | } 133 | 134 | /* See RFC 2616 section 4.4 */ 135 | if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ 136 | parser->status_code == 204 || /* No Content */ 137 | parser->status_code == 304 || /* Not Modified */ 138 | (parser->flags & F_SKIPBODY)) { /* response to a HEAD request */ 139 | return 0; 140 | } 141 | 142 | /* RFC 7230 3.3.3, see `llhttp__after_headers_complete` */ 143 | if ((parser->flags & F_TRANSFER_ENCODING) && 144 | (parser->flags & F_CHUNKED) == 0) { 145 | return 1; 146 | } 147 | 148 | if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) { 149 | return 0; 150 | } 151 | 152 | return 1; 153 | } 154 | 155 | 156 | int llhttp_should_keep_alive(const llhttp_t* parser) { 157 | if (parser->http_major > 0 && parser->http_minor > 0) { 158 | /* HTTP/1.1 */ 159 | if (parser->flags & F_CONNECTION_CLOSE) { 160 | return 0; 161 | } 162 | } else { 163 | /* HTTP/1.0 or earlier */ 164 | if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { 165 | return 0; 166 | } 167 | } 168 | 169 | return !llhttp_message_needs_eof(parser); 170 | } 171 | -------------------------------------------------------------------------------- /lib/logger.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *=============================================================== 15 | * modified by fduncanh 2023 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "logger.h" 24 | #include "compat.h" 25 | 26 | struct logger_s { 27 | mutex_handle_t lvl_mutex; 28 | mutex_handle_t cb_mutex; 29 | 30 | int level; 31 | void *cls; 32 | logger_callback_t callback; 33 | }; 34 | 35 | logger_t * 36 | logger_init() 37 | { 38 | logger_t *logger = calloc(1, sizeof(logger_t)); 39 | assert(logger); 40 | 41 | MUTEX_CREATE(logger->lvl_mutex); 42 | MUTEX_CREATE(logger->cb_mutex); 43 | 44 | logger->level = LOGGER_WARNING; 45 | logger->callback = NULL; 46 | return logger; 47 | } 48 | 49 | void 50 | logger_destroy(logger_t *logger) 51 | { 52 | MUTEX_DESTROY(logger->lvl_mutex); 53 | MUTEX_DESTROY(logger->cb_mutex); 54 | free(logger); 55 | } 56 | 57 | void 58 | logger_set_level(logger_t *logger, int level) 59 | { 60 | assert(logger); 61 | 62 | MUTEX_LOCK(logger->lvl_mutex); 63 | logger->level = level; 64 | MUTEX_UNLOCK(logger->lvl_mutex); 65 | } 66 | 67 | int 68 | logger_get_level(logger_t *logger) 69 | { 70 | int level; 71 | assert(logger); 72 | 73 | MUTEX_LOCK(logger->lvl_mutex); 74 | level = logger->level; 75 | MUTEX_UNLOCK(logger->lvl_mutex); 76 | 77 | return level; 78 | } 79 | 80 | void 81 | logger_set_callback(logger_t *logger, logger_callback_t callback, void *cls) 82 | { 83 | assert(logger); 84 | 85 | MUTEX_LOCK(logger->cb_mutex); 86 | logger->cls = cls; 87 | logger->callback = callback; 88 | MUTEX_UNLOCK(logger->cb_mutex); 89 | } 90 | 91 | static char * 92 | logger_utf8_to_local(const char *str) 93 | { 94 | char *ret = NULL; 95 | 96 | /* FIXME: This is only implemented on Windows for now */ 97 | #if defined(_WIN32) || defined(_WIN64) 98 | int wclen, mblen; 99 | WCHAR *wcstr; 100 | BOOL failed; 101 | 102 | wclen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); 103 | wcstr = malloc(sizeof(WCHAR) * wclen); 104 | MultiByteToWideChar(CP_UTF8, 0, str, -1, wcstr, wclen); 105 | 106 | mblen = WideCharToMultiByte(CP_ACP, 0, wcstr, wclen, NULL, 0, NULL, &failed); 107 | if (failed) { 108 | /* Invalid characters in input, conversion failed */ 109 | free(wcstr); 110 | return NULL; 111 | } 112 | 113 | ret = malloc(sizeof(CHAR) * mblen); 114 | WideCharToMultiByte(CP_ACP, 0, wcstr, wclen, ret, mblen, NULL, NULL); 115 | free(wcstr); 116 | #endif 117 | 118 | return ret; 119 | } 120 | 121 | void 122 | logger_log(logger_t *logger, int level, const char *fmt, ...) 123 | { 124 | char buffer[4096]; 125 | va_list ap; 126 | 127 | MUTEX_LOCK(logger->lvl_mutex); 128 | if (level > logger->level) { 129 | MUTEX_UNLOCK(logger->lvl_mutex); 130 | return; 131 | } 132 | MUTEX_UNLOCK(logger->lvl_mutex); 133 | 134 | buffer[sizeof(buffer)-1] = '\0'; 135 | va_start(ap, fmt); 136 | vsnprintf(buffer, sizeof(buffer)-1, fmt, ap); 137 | va_end(ap); 138 | 139 | MUTEX_LOCK(logger->cb_mutex); 140 | if (logger->callback) { 141 | logger->callback(logger->cls, level, buffer); 142 | MUTEX_UNLOCK(logger->cb_mutex); 143 | } else { 144 | char *local; 145 | MUTEX_UNLOCK(logger->cb_mutex); 146 | local = logger_utf8_to_local(buffer); 147 | if (local) { 148 | fprintf(stderr, "%s\n", local); 149 | free(local); 150 | } else { 151 | fprintf(stderr, "%s\n", buffer); 152 | } 153 | } 154 | } 155 | 156 | -------------------------------------------------------------------------------- /lib/logger.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================= 15 | * modified by fduncanh 2023 16 | */ 17 | 18 | #ifndef LOGGER_H 19 | #define LOGGER_H 20 | 21 | #ifdef __cplusplus 22 | extern "C" { 23 | #endif 24 | 25 | /* Define syslog style log levels */ 26 | #define LOGGER_EMERG 0 /* system is unusable */ 27 | #define LOGGER_ALERT 1 /* action must be taken immediately */ 28 | #define LOGGER_CRIT 2 /* critical conditions */ 29 | #define LOGGER_ERR 3 /* error conditions */ 30 | #define LOGGER_WARNING 4 /* warning conditions */ 31 | #define LOGGER_NOTICE 5 /* normal but significant condition */ 32 | #define LOGGER_INFO 6 /* informational */ 33 | #define LOGGER_DEBUG 7 /* debug-level messages */ 34 | 35 | typedef void (*logger_callback_t)(void *cls, int level, const char *msg); 36 | 37 | typedef struct logger_s logger_t; 38 | 39 | logger_t *logger_init(); 40 | void logger_destroy(logger_t *logger); 41 | 42 | void logger_set_level(logger_t *logger, int level); 43 | int logger_get_level(logger_t *logger); 44 | void logger_set_callback(logger_t *logger, logger_callback_t callback, void *cls); 45 | 46 | void logger_log(logger_t *logger, int level, const char *fmt, ...); 47 | 48 | #ifdef __cplusplus 49 | } 50 | #endif 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /lib/mirror_buffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 dsafa22, All Rights Reserved. 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================ 15 | * modified by fduncanh 2022 16 | */ 17 | 18 | #include "mirror_buffer.h" 19 | #include "raop_rtp.h" 20 | #include "raop_rtp.h" 21 | #include 22 | #include "crypto.h" 23 | #include "compat.h" 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | struct mirror_buffer_s { 32 | logger_t *logger; 33 | aes_ctx_t *aes_ctx; 34 | int nextDecryptCount; 35 | uint8_t og[16]; 36 | /* audio aes key is used in a hash for the video aes key and iv */ 37 | unsigned char aeskey_audio[RAOP_AESKEY_LEN]; 38 | }; 39 | 40 | void 41 | mirror_buffer_init_aes(mirror_buffer_t *mirror_buffer, const uint64_t *streamConnectionID) 42 | { 43 | unsigned char aeskey_video[64]; 44 | unsigned char aesiv_video[64]; 45 | 46 | assert(mirror_buffer); 47 | assert(streamConnectionID); 48 | 49 | /* AES key and IV */ 50 | // Need secondary processing to use 51 | 52 | snprintf((char*) aeskey_video, sizeof(aeskey_video), "AirPlayStreamKey%" PRIu64, *streamConnectionID); 53 | snprintf((char*) aesiv_video, sizeof(aesiv_video), "AirPlayStreamIV%" PRIu64, *streamConnectionID); 54 | 55 | sha_ctx_t *ctx = sha_init(); 56 | sha_update(ctx, aeskey_video, strlen((char*) aeskey_video)); 57 | sha_update(ctx, mirror_buffer->aeskey_audio, RAOP_AESKEY_LEN); 58 | sha_final(ctx, aeskey_video, NULL); 59 | 60 | sha_reset(ctx); 61 | sha_update(ctx, aesiv_video, strlen((char*) aesiv_video)); 62 | sha_update(ctx, mirror_buffer->aeskey_audio, RAOP_AESKEY_LEN); 63 | sha_final(ctx, aesiv_video, NULL); 64 | sha_destroy(ctx); 65 | 66 | // Need to be initialized externally 67 | mirror_buffer->aes_ctx = aes_ctr_init(aeskey_video, aesiv_video); 68 | } 69 | 70 | mirror_buffer_t * 71 | mirror_buffer_init(logger_t *logger, const unsigned char *aeskey) 72 | { 73 | mirror_buffer_t *mirror_buffer; 74 | assert(aeskey); 75 | mirror_buffer = calloc(1, sizeof(mirror_buffer_t)); 76 | if (!mirror_buffer) { 77 | return NULL; 78 | } 79 | memcpy(mirror_buffer->aeskey_audio, aeskey, RAOP_AESKEY_LEN); 80 | mirror_buffer->logger = logger; 81 | mirror_buffer->nextDecryptCount = 0; 82 | return mirror_buffer; 83 | } 84 | 85 | void mirror_buffer_decrypt(mirror_buffer_t *mirror_buffer, unsigned char* input, unsigned char* output, int inputLen) { 86 | // Start decrypting 87 | if (mirror_buffer->nextDecryptCount > 0) {//mirror_buffer->nextDecryptCount = 10 88 | for (int i = 0; i < mirror_buffer->nextDecryptCount; i++) { 89 | output[i] = (input[i] ^ mirror_buffer->og[(16 - mirror_buffer->nextDecryptCount) + i]); 90 | } 91 | } 92 | // Handling encrypted bytes 93 | int encryptlen = ((inputLen - mirror_buffer->nextDecryptCount) / 16) * 16; 94 | // Aes decryption 95 | aes_ctr_start_fresh_block(mirror_buffer->aes_ctx); 96 | aes_ctr_decrypt(mirror_buffer->aes_ctx, input + mirror_buffer->nextDecryptCount, 97 | input + mirror_buffer->nextDecryptCount, encryptlen); 98 | // Copy to output 99 | memcpy(output + mirror_buffer->nextDecryptCount, input + mirror_buffer->nextDecryptCount, encryptlen); 100 | // int outputlength = mirror_buffer->nextDecryptCount + encryptlen; 101 | // Processing remaining length 102 | int restlen = (inputLen - mirror_buffer->nextDecryptCount) % 16; 103 | int reststart = inputLen - restlen; 104 | mirror_buffer->nextDecryptCount = 0; 105 | if (restlen > 0) { 106 | memset(mirror_buffer->og, 0, 16); 107 | memcpy(mirror_buffer->og, input + reststart, restlen); 108 | aes_ctr_decrypt(mirror_buffer->aes_ctx, mirror_buffer->og, mirror_buffer->og, 16); 109 | for (int j = 0; j < restlen; j++) { 110 | output[reststart + j] = mirror_buffer->og[j]; 111 | } 112 | //outputlength += restlen; 113 | mirror_buffer->nextDecryptCount = 16 - restlen;// Difference 16-6=10 bytes 114 | } 115 | } 116 | 117 | void 118 | mirror_buffer_destroy(mirror_buffer_t *mirror_buffer) 119 | { 120 | if (mirror_buffer) { 121 | aes_ctr_destroy(mirror_buffer->aes_ctx); 122 | free(mirror_buffer); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/mirror_buffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 dsafa22, All Rights Reserved. 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================= 15 | * modified by fduncanh 2022 16 | */ 17 | 18 | #ifndef MIRROR_BUFFER_H 19 | #define MIRROR_BUFFER_H 20 | 21 | #include 22 | #include "logger.h" 23 | 24 | typedef struct mirror_buffer_s mirror_buffer_t; 25 | 26 | 27 | mirror_buffer_t *mirror_buffer_init( logger_t *logger, const unsigned char *aeskey); 28 | void mirror_buffer_init_aes(mirror_buffer_t *mirror_buffer, const uint64_t *streamConnectionID); 29 | void mirror_buffer_decrypt(mirror_buffer_t *raop_mirror, unsigned char* input, unsigned char* output, int datalen); 30 | void mirror_buffer_destroy(mirror_buffer_t *mirror_buffer); 31 | #endif //MIRROR_BUFFER_H 32 | -------------------------------------------------------------------------------- /lib/netutils.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================== 15 | * modified by fduncanh 2022 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "compat.h" 23 | 24 | int 25 | netutils_init() 26 | { 27 | #ifdef WIN32 28 | WORD wVersionRequested; 29 | WSADATA wsaData; 30 | int ret; 31 | 32 | wVersionRequested = MAKEWORD(2, 2); 33 | ret = WSAStartup(wVersionRequested, &wsaData); 34 | if (ret) { 35 | return -1; 36 | } 37 | 38 | if (LOBYTE(wsaData.wVersion) != 2 || 39 | HIBYTE(wsaData.wVersion) != 2) { 40 | /* Version mismatch, requested version not found */ 41 | return -1; 42 | } 43 | #endif 44 | return 0; 45 | } 46 | 47 | void 48 | netutils_cleanup() 49 | { 50 | #ifdef WIN32 51 | WSACleanup(); 52 | #endif 53 | } 54 | 55 | unsigned char * 56 | netutils_get_address(void *sockaddr, int *length, unsigned int *zone_id) 57 | { 58 | unsigned char ipv4_prefix[] = { 0,0,0,0,0,0,0,0,0,0,255,255 }; 59 | struct sockaddr *address = sockaddr; 60 | 61 | assert(address); 62 | assert(length); 63 | assert(zone_id); 64 | if (address->sa_family == AF_INET) { 65 | struct sockaddr_in *sin; 66 | *zone_id = 0; 67 | sin = (struct sockaddr_in *)address; 68 | *length = sizeof(sin->sin_addr.s_addr); 69 | return (unsigned char *)&sin->sin_addr.s_addr; 70 | } else if (address->sa_family == AF_INET6) { 71 | struct sockaddr_in6 *sin6; 72 | 73 | sin6 = (struct sockaddr_in6 *)address; 74 | if (!memcmp(sin6->sin6_addr.s6_addr, ipv4_prefix, 12)) { 75 | /* Actually an embedded IPv4 address */ 76 | *zone_id = 0; 77 | *length = sizeof(sin6->sin6_addr.s6_addr)-12; 78 | return (sin6->sin6_addr.s6_addr+12); 79 | } 80 | *zone_id = (unsigned int) sin6->sin6_scope_id; 81 | *length = sizeof(sin6->sin6_addr.s6_addr); 82 | return sin6->sin6_addr.s6_addr; 83 | } 84 | 85 | *length = 0; 86 | return NULL; 87 | } 88 | 89 | int 90 | netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp) 91 | { 92 | int family = use_ipv6 ? AF_INET6 : AF_INET; 93 | int type = use_udp ? SOCK_DGRAM : SOCK_STREAM; 94 | int proto = use_udp ? IPPROTO_UDP : IPPROTO_TCP; 95 | 96 | struct sockaddr_storage saddr; 97 | socklen_t socklen; 98 | int server_fd; 99 | int ret; 100 | #ifndef _WIN32 101 | int reuseaddr = 1; 102 | #else 103 | const char reuseaddr = 1; 104 | #endif 105 | 106 | assert(port); 107 | 108 | server_fd = socket(family, type, proto); 109 | if (server_fd == -1) { 110 | goto cleanup; 111 | } 112 | 113 | ret = setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof (reuseaddr)); 114 | if (ret == -1) { 115 | goto cleanup; 116 | } 117 | 118 | memset(&saddr, 0, sizeof(saddr)); 119 | if (use_ipv6) { 120 | struct sockaddr_in6 *sin6ptr = (struct sockaddr_in6 *)&saddr; 121 | 122 | /* Initialize sockaddr for bind */ 123 | sin6ptr->sin6_family = family; 124 | sin6ptr->sin6_addr = in6addr_any; 125 | sin6ptr->sin6_port = htons(*port); 126 | 127 | #ifndef _WIN32 128 | int v6only = 1; 129 | /* Make sure we only listen to IPv6 addresses */ 130 | setsockopt(server_fd, IPPROTO_IPV6, IPV6_V6ONLY, 131 | (char *) &v6only, sizeof(v6only)); 132 | #endif 133 | 134 | socklen = sizeof(*sin6ptr); 135 | ret = bind(server_fd, (struct sockaddr *)sin6ptr, socklen); 136 | if (ret == -1) { 137 | goto cleanup; 138 | } 139 | 140 | ret = getsockname(server_fd, (struct sockaddr *)sin6ptr, &socklen); 141 | if (ret == -1) { 142 | goto cleanup; 143 | } 144 | *port = ntohs(sin6ptr->sin6_port); 145 | } else { 146 | struct sockaddr_in *sinptr = (struct sockaddr_in *)&saddr; 147 | 148 | /* Initialize sockaddr for bind */ 149 | sinptr->sin_family = family; 150 | sinptr->sin_addr.s_addr = INADDR_ANY; 151 | sinptr->sin_port = htons(*port); 152 | 153 | socklen = sizeof(*sinptr); 154 | ret = bind(server_fd, (struct sockaddr *)sinptr, socklen); 155 | if (ret == -1) { 156 | goto cleanup; 157 | } 158 | 159 | ret = getsockname(server_fd, (struct sockaddr *)sinptr, &socklen); 160 | if (ret == -1) { 161 | goto cleanup; 162 | } 163 | *port = ntohs(sinptr->sin_port); 164 | } 165 | return server_fd; 166 | 167 | cleanup: 168 | ret = SOCKET_GET_ERROR(); 169 | if (server_fd != -1) { 170 | closesocket(server_fd); 171 | } 172 | SOCKET_SET_ERROR(ret); 173 | return -1; 174 | } 175 | 176 | // Src is the ip address 177 | int 178 | netutils_parse_address(int family, const char *src, void *dst, int dstlen) 179 | { 180 | struct addrinfo *result; 181 | struct addrinfo *ptr; 182 | struct addrinfo hints; 183 | int length; 184 | int ret; 185 | 186 | if (family != AF_INET && family != AF_INET6) { 187 | return -1; 188 | } 189 | if (!src || !dst) { 190 | return -1; 191 | } 192 | 193 | memset(&hints, 0, sizeof(hints)); 194 | hints.ai_family = family; 195 | hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; 196 | 197 | ret = getaddrinfo(src, NULL, &hints, &result); 198 | if (ret != 0) { 199 | return -1; 200 | } 201 | 202 | length = -1; 203 | for (ptr=result; ptr!=NULL; ptr=ptr->ai_next) { 204 | if (family == ptr->ai_family && (unsigned int)dstlen >= ptr->ai_addrlen) { 205 | memcpy(dst, ptr->ai_addr, ptr->ai_addrlen); 206 | length = ptr->ai_addrlen; 207 | break; 208 | } 209 | } 210 | freeaddrinfo(result); 211 | return length; 212 | } 213 | -------------------------------------------------------------------------------- /lib/netutils.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | #ifndef NETUTILS_H 16 | #define NETUTILS_H 17 | 18 | int netutils_init(); 19 | void netutils_cleanup(); 20 | 21 | int netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp); 22 | unsigned char *netutils_get_address(void *sockaddr, int *length, unsigned int *zone_id); 23 | int netutils_parse_address(int family, const char *src, void *dst, int dstlen); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /lib/pairing.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2018 Juho Vähä-Herttua 3 | * Copyright (C) 2020 Jaslo Ziska 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | */ 15 | 16 | #include "crypto.h" 17 | 18 | #ifndef PAIRING_H 19 | #define PAIRING_H 20 | 21 | #define PAIRING_SIG_SIZE (2 * X25519_KEY_SIZE) 22 | 23 | 24 | #define SRP_USERNAME_SIZE 24 /* accomodates up to an 8-octet MAC address */ 25 | #define SRP_SESSION_KEY_SIZE 40 26 | #define SRP_VERIFIER_SIZE 256 27 | #define SRP_SALT_SIZE 16 28 | #define SRP_PK_SIZE 256 29 | #define SRP_SHA SRP_SHA1 30 | #define SRP_NG SRP_NG_2048 31 | #define SRP_M2_SIZE 64 32 | #define SRP_PRIVATE_KEY_SIZE 32 33 | #define GCM_AUTHTAG_SIZE 16 34 | #define SHA512_KEY_LENGTH 64 35 | 36 | typedef struct pairing_s pairing_t; 37 | typedef struct pairing_session_s pairing_session_t; 38 | 39 | pairing_t *pairing_init_generate(const char *device_id, const char *keyfile, int *result); 40 | void pairing_get_public_key(pairing_t *pairing, unsigned char public_key[ED25519_KEY_SIZE]); 41 | 42 | pairing_session_t *pairing_session_init(pairing_t *pairing); 43 | void pairing_session_set_setup_status(pairing_session_t *session); 44 | int pairing_session_check_handshake_status(pairing_session_t *session); 45 | int pairing_session_handshake(pairing_session_t *session, const unsigned char ecdh_key[X25519_KEY_SIZE], 46 | const unsigned char ed_key[ED25519_KEY_SIZE]); 47 | int pairing_session_get_public_key(pairing_session_t *session, unsigned char ecdh_key[X25519_KEY_SIZE]); 48 | int random_pin(); 49 | int pairing_session_get_signature(pairing_session_t *session, unsigned char signature[PAIRING_SIG_SIZE]); 50 | int pairing_session_finish(pairing_session_t *session, const unsigned char signature[PAIRING_SIG_SIZE]); 51 | void pairing_session_destroy(pairing_session_t *session); 52 | 53 | void pairing_destroy(pairing_t *pairing); 54 | 55 | int pairing_get_ecdh_secret_key(pairing_session_t *session, unsigned char ecdh_secret[X25519_KEY_SIZE]); 56 | 57 | int srp_new_user(pairing_session_t *session, pairing_t *pairing, const char *device_id, const char *pin, 58 | const char **salt, int *len_salt, const char **pk, int *len_pk); 59 | int srp_validate_proof(pairing_session_t *session, pairing_t *pairing, const unsigned char *A, 60 | int len_A, unsigned char *proof, int client_proof_len, int proof_len); 61 | int srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing, unsigned char *epk, 62 | unsigned char *auth_tag); 63 | void access_client_session_data(pairing_session_t *session, char **username, char **client_pk, bool *setup); 64 | void ed25519_pk_to_base64(const unsigned char *pk, char **pk64); 65 | #endif 66 | -------------------------------------------------------------------------------- /lib/playfair/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | aux_source_directory(. playfair_src) 3 | set(DIR_SRCS ${playfair_src}) 4 | include_directories(.) 5 | add_library( playfair 6 | STATIC 7 | ${DIR_SRCS}) 8 | -------------------------------------------------------------------------------- /lib/playfair/modified_md5.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define printf(...) (void)0; 8 | 9 | int shift[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 10 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 11 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 12 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; 13 | 14 | uint32_t F(uint32_t B, uint32_t C, uint32_t D) 15 | { 16 | return (B & C) | (~B & D); 17 | } 18 | 19 | uint32_t G(uint32_t B, uint32_t C, uint32_t D) 20 | { 21 | return (B & D) | (C & ~D); 22 | } 23 | 24 | uint32_t H(uint32_t B, uint32_t C, uint32_t D) 25 | { 26 | return B ^ C ^ D; 27 | } 28 | 29 | uint32_t I(uint32_t B, uint32_t C, uint32_t D) 30 | { 31 | return C ^ (B | ~D); 32 | } 33 | 34 | 35 | uint32_t rol(uint32_t input, int count) 36 | { 37 | return ((input << count) & 0xffffffff) | (input & 0xffffffff) >> (32-count); 38 | } 39 | 40 | void swap(uint32_t* a, uint32_t* b) 41 | { 42 | printf("%08x <-> %08x\n", *a, *b); 43 | uint32_t c = *a; 44 | *a = *b; 45 | *b = c; 46 | } 47 | 48 | void modified_md5(unsigned char* originalblockIn, unsigned char* keyIn, unsigned char* keyOut) 49 | { 50 | unsigned char blockIn[64]; 51 | uint32_t* block_words = (uint32_t*)blockIn; 52 | uint32_t* key_words = (uint32_t*)keyIn; 53 | uint32_t* out_words = (uint32_t*)keyOut; 54 | uint32_t A, B, C, D, Z, tmp; 55 | int i; 56 | 57 | memcpy(blockIn, originalblockIn, 64); 58 | 59 | // Each cycle does something like this: 60 | A = key_words[0]; 61 | B = key_words[1]; 62 | C = key_words[2]; 63 | D = key_words[3]; 64 | for (i = 0; i < 64; i++) 65 | { 66 | uint32_t input; 67 | int j; 68 | if (i < 16) 69 | j = i; 70 | else if (i < 32) 71 | j = (5*i + 1) % 16; 72 | else if (i < 48) 73 | j = (3*i + 5) % 16; 74 | else if (i < 64) 75 | j = 7*i % 16; 76 | 77 | input = blockIn[4*j] << 24 | blockIn[4*j+1] << 16 | blockIn[4*j+2] << 8 | blockIn[4*j+3]; 78 | printf("Key = %08x\n", A); 79 | Z = A + input + (int)(long long)((1LL << 32) * fabs(sin(i + 1))); 80 | if (i < 16) 81 | Z = rol(Z + F(B,C,D), shift[i]); 82 | else if (i < 32) 83 | Z = rol(Z + G(B,C,D), shift[i]); 84 | else if (i < 48) 85 | Z = rol(Z + H(B,C,D), shift[i]); 86 | else if (i < 64) 87 | Z = rol(Z + I(B,C,D), shift[i]); 88 | if (i == 63) 89 | printf("Ror is %08x\n", Z); 90 | printf("Output of round %d: %08X + %08X = %08X (shift %d, constant %08X)\n", i, Z, B, Z+B, shift[i], (int)(long long)((1LL << 32) * fabs(sin(i + 1)))); 91 | Z = Z + B; 92 | tmp = D; 93 | D = C; 94 | C = B; 95 | B = Z; 96 | A = tmp; 97 | if (i == 31) 98 | { 99 | // swapsies 100 | swap(&block_words[A & 15], &block_words[B & 15]); 101 | swap(&block_words[C & 15], &block_words[D & 15]); 102 | swap(&block_words[(A & (15<<4))>>4], &block_words[(B & (15<<4))>>4]); 103 | swap(&block_words[(A & (15<<8))>>8], &block_words[(B & (15<<8))>>8]); 104 | swap(&block_words[(A & (15<<12))>>12], &block_words[(B & (15<<12))>>12]); 105 | } 106 | } 107 | printf("%08X %08X %08X %08X\n", A, B, C, D); 108 | // Now we can actually compute the output 109 | printf("Out:\n"); 110 | printf("%08x + %08x = %08x\n", key_words[0], A, key_words[0] + A); 111 | printf("%08x + %08x = %08x\n", key_words[1], B, key_words[1] + B); 112 | printf("%08x + %08x = %08x\n", key_words[2], C, key_words[2] + C); 113 | printf("%08x + %08x = %08x\n", key_words[3], D, key_words[3] + D); 114 | out_words[0] = key_words[0] + A; 115 | out_words[1] = key_words[1] + B; 116 | out_words[2] = key_words[2] + C; 117 | out_words[3] = key_words[3] + D; 118 | 119 | } 120 | -------------------------------------------------------------------------------- /lib/playfair/playfair.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "playfair.h" 4 | 5 | void generate_key_schedule(unsigned char* key_material, uint32_t key_schedule[11][4]); 6 | void generate_session_key(unsigned char* oldSap, unsigned char* messageIn, unsigned char* sessionKey); 7 | void cycle(unsigned char* block, uint32_t key_schedule[11][4]); 8 | void z_xor(unsigned char* in, unsigned char* out, int blocks); 9 | void x_xor(unsigned char* in, unsigned char* out, int blocks); 10 | 11 | extern unsigned char default_sap[]; 12 | 13 | void playfair_decrypt(unsigned char* message3, unsigned char* cipherText, unsigned char* keyOut) 14 | { 15 | unsigned char* chunk1 = &cipherText[16]; 16 | unsigned char* chunk2 = &cipherText[56]; 17 | int i; 18 | unsigned char blockIn[16]; 19 | unsigned char sapKey[16]; 20 | uint32_t key_schedule[11][4]; 21 | generate_session_key(default_sap, message3, sapKey); 22 | generate_key_schedule(sapKey, key_schedule); 23 | z_xor(chunk2, blockIn, 1); 24 | cycle(blockIn, key_schedule); 25 | for (i = 0; i < 16; i++) { 26 | keyOut[i] = blockIn[i] ^ chunk1[i]; 27 | } 28 | x_xor(keyOut, keyOut, 1); 29 | z_xor(keyOut, keyOut, 1); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /lib/playfair/playfair.h: -------------------------------------------------------------------------------- 1 | #ifndef PLAYFAIR_H 2 | #define PLAYFAIR_H 3 | 4 | void playfair_decrypt(unsigned char* message3, unsigned char* cipherText, unsigned char* keyOut); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /lib/playfair/sap_hash.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define printf(...) (void)0; 6 | 7 | void garble(unsigned char*, unsigned char*, unsigned char*, unsigned char*, unsigned char*); 8 | 9 | unsigned char rol8(unsigned char input, int count) 10 | { 11 | return ((input << count) & 0xff) | (input & 0xff) >> (8-count); 12 | } 13 | 14 | uint32_t rol8x(unsigned char input, int count) 15 | { 16 | return ((input << count)) | (input) >> (8-count); 17 | } 18 | 19 | 20 | void sap_hash(unsigned char* blockIn, unsigned char* keyOut) 21 | { 22 | uint32_t* block_words = (uint32_t*)blockIn; 23 | uint32_t* out_words = (uint32_t*)keyOut; 24 | unsigned char buffer0[20] = {0x96, 0x5F, 0xC6, 0x53, 0xF8, 0x46, 0xCC, 0x18, 0xDF, 0xBE, 0xB2, 0xF8, 0x38, 0xD7, 0xEC, 0x22, 0x03, 0xD1, 0x20, 0x8F}; 25 | unsigned char buffer1[210]; 26 | unsigned char buffer2[35] = {0x43, 0x54, 0x62, 0x7A, 0x18, 0xC3, 0xD6, 0xB3, 0x9A, 0x56, 0xF6, 0x1C, 0x14, 0x3F, 0x0C, 0x1D, 0x3B, 0x36, 0x83, 0xB1, 0x39, 0x51, 0x4A, 0xAA, 0x09, 0x3E, 0xFE, 0x44, 0xAF, 0xDE, 0xC3, 0x20, 0x9D, 0x42, 0x3A}; 27 | unsigned char buffer3[132]; 28 | unsigned char buffer4[21] = {0xED, 0x25, 0xD1, 0xBB, 0xBC, 0x27, 0x9F, 0x02, 0xA2, 0xA9, 0x11, 0x00, 0x0C, 0xB3, 0x52, 0xC0, 0xBD, 0xE3, 0x1B, 0x49, 0xC7}; 29 | int i0_index[11] = {18, 22, 23, 0, 5, 19, 32, 31, 10, 21, 30}; 30 | uint8_t w,x,y,z; 31 | int i, j; 32 | 33 | // Load the input into the buffer 34 | for (i = 0; i < 210; i++) 35 | { 36 | // We need to swap the byte order around so it is the right endianness 37 | uint32_t in_word = block_words[((i % 64)>>2)]; 38 | uint32_t in_byte = (in_word >> ((3-(i % 4)) << 3)) & 0xff; 39 | buffer1[i] = in_byte; 40 | } 41 | // Next a scrambling 42 | for (i = 0; i < 840; i++) 43 | { 44 | // We have to do unsigned, 32-bit modulo, or we get the wrong indices 45 | x = buffer1[((i-155) & 0xffffffff) % 210]; 46 | y = buffer1[((i-57) & 0xffffffff) % 210]; 47 | z = buffer1[((i-13) & 0xffffffff) % 210]; 48 | w = buffer1[(i & 0xffffffff) % 210]; 49 | buffer1[i % 210] = (rol8(y, 5) + (rol8(z, 3) ^ w) - rol8(x,7)) & 0xff; 50 | } 51 | printf("Garbling...\n"); 52 | // I have no idea what this is doing (yet), but it gives the right output 53 | garble(buffer0, buffer1, buffer2, buffer3, buffer4); 54 | 55 | // Fill the output with 0xE1 56 | for (i = 0; i < 16; i++) 57 | keyOut[i] = 0xE1; 58 | 59 | // Now we use all the buffers we have calculated to grind out the output. First buffer3 60 | for (i = 0; i < 11; i++) 61 | { 62 | // Note that this is addition (mod 255) and not XOR 63 | // Also note that we only use certain indices 64 | // And that index 3 is hard-coded to be 0x3d (Maybe we can hack this up by changing buffer3[0] to be 0xdc? 65 | if (i == 3) 66 | keyOut[i] = 0x3d; 67 | else 68 | keyOut[i] = ((keyOut[i] + buffer3[i0_index[i] * 4]) & 0xff); 69 | } 70 | 71 | // Then buffer0 72 | for (i = 0; i < 20; i++) 73 | keyOut[i % 16] ^= buffer0[i]; 74 | 75 | // Then buffer2 76 | for (i = 0; i < 35; i++) 77 | keyOut[i % 16] ^= buffer2[i]; 78 | 79 | // Do buffer1 80 | for (i = 0; i < 210; i++) 81 | keyOut[(i % 16)] ^= buffer1[i]; 82 | 83 | 84 | // Now we do a kind of reverse-scramble 85 | for (j = 0; j < 16; j++) 86 | { 87 | for (i = 0; i < 16; i++) 88 | { 89 | x = keyOut[((i-7) & 0xffffffff) % 16]; 90 | y = keyOut[i % 16]; 91 | z = keyOut[((i-37) & 0xffffffff) % 16]; 92 | w = keyOut[((i-177) & 0xffffffff) % 16]; 93 | keyOut[i] = rol8(x, 1) ^ y ^ rol8(z, 6) ^ rol8(w, 5); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/raop.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012-2015 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *=================================================================== 15 | * modified by fduncanh 2021-23 16 | */ 17 | 18 | #ifndef RAOP_H 19 | #define RAOP_H 20 | 21 | #include "dnssd.h" 22 | #include "stream.h" 23 | #include "raop_ntp.h" 24 | #include "airplay_video.h" 25 | 26 | #if defined (WIN32) && defined(DLL_EXPORT) 27 | # define RAOP_API __declspec(dllexport) 28 | #else 29 | # define RAOP_API 30 | #endif 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | typedef struct raop_s raop_t; 37 | 38 | typedef void (*raop_log_callback_t)(void *cls, int level, const char *msg); 39 | 40 | 41 | typedef struct playback_info_s { 42 | //char * uuid; 43 | uint32_t stallcount; 44 | double duration; 45 | double position; 46 | float rate; 47 | bool ready_to_play; 48 | bool playback_buffer_empty; 49 | bool playback_buffer_full; 50 | bool playback_likely_to_keep_up; 51 | int num_loaded_time_ranges; 52 | int num_seekable_time_ranges; 53 | void *loadedTimeRanges; 54 | void *seekableTimeRanges; 55 | } playback_info_t; 56 | 57 | typedef enum video_codec_e { 58 | VIDEO_CODEC_UNKNOWN, 59 | VIDEO_CODEC_H264, 60 | VIDEO_CODEC_H265 61 | } video_codec_t; 62 | 63 | struct raop_callbacks_s { 64 | void* cls; 65 | 66 | void (*audio_process)(void *cls, raop_ntp_t *ntp, audio_decode_struct *data); 67 | void (*video_process)(void *cls, raop_ntp_t *ntp, video_decode_struct *data); 68 | void (*video_pause)(void *cls); 69 | void (*video_resume)(void *cls); 70 | 71 | /* Optional but recommended callback functions */ 72 | void (*conn_init)(void *cls); 73 | void (*conn_destroy)(void *cls); 74 | void (*conn_reset) (void *cls, int timeouts, bool reset_video); 75 | void (*conn_teardown)(void *cls, bool *teardown_96, bool *teardown_110 ); 76 | void (*audio_flush)(void *cls); 77 | void (*video_flush)(void *cls); 78 | void (*audio_set_volume)(void *cls, float volume); 79 | void (*audio_set_metadata)(void *cls, const void *buffer, int buflen); 80 | void (*audio_set_coverart)(void *cls, const void *buffer, int buflen); 81 | void (*audio_remote_control_id)(void *cls, const char *dacp_id, const char *active_remote_header); 82 | void (*audio_set_progress)(void *cls, unsigned int start, unsigned int curr, unsigned int end); 83 | void (*audio_get_format)(void *cls, unsigned char *ct, unsigned short *spf, bool *usingScreen, bool *isMedia, uint64_t *audioFormat); 84 | void (*video_report_size)(void *cls, float *width_source, float *height_source, float *width, float *height); 85 | void (*report_client_request) (void *cls, char *deviceid, char *model, char *name, bool *admit); 86 | void (*display_pin) (void *cls, char * pin); 87 | void (*register_client) (void *cls, const char *device_id, const char *pk_str, const char *name); 88 | bool (*check_register) (void *cls, const char *pk_str); 89 | void (*export_dacp) (void *cls, const char *active_remote, const char *dacp_id); 90 | void (*video_reset) (void *cls); 91 | void (*video_set_codec)(void *cls, video_codec_t codec); 92 | /* for HLS video player controls */ 93 | void (*on_video_play) (void *cls, const char *location, const float start_position); 94 | void (*on_video_scrub) (void *cls, const float position); 95 | void (*on_video_rate) (void *cls, const float rate); 96 | void (*on_video_stop) (void *cls); 97 | void (*on_video_acquire_playback_info) (void *cls, playback_info_t *playback_video); 98 | 99 | }; 100 | typedef struct raop_callbacks_s raop_callbacks_t; 101 | raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *remote, 102 | int remote_addr_len, unsigned short timing_rport, 103 | timing_protocol_t *time_protocol); 104 | 105 | int airplay_video_service_init(raop_t *raop, unsigned short port, const char *session_id); 106 | 107 | bool register_airplay_video(raop_t *raop, airplay_video_t *airplay_video); 108 | airplay_video_t *get_airplay_video(raop_t *raop); 109 | airplay_video_t *deregister_airplay_video(raop_t *raop); 110 | 111 | RAOP_API raop_t *raop_init(raop_callbacks_t *callbacks); 112 | RAOP_API int raop_init2(raop_t *raop, int nohold, const char *device_id, const char *keyfile); 113 | RAOP_API void raop_set_log_level(raop_t *raop, int level); 114 | RAOP_API void raop_set_log_callback(raop_t *raop, raop_log_callback_t callback, void *cls); 115 | RAOP_API int raop_set_plist(raop_t *raop, const char *plist_item, const int value); 116 | RAOP_API void raop_set_port(raop_t *raop, unsigned short port); 117 | RAOP_API void raop_set_udp_ports(raop_t *raop, unsigned short port[3]); 118 | RAOP_API void raop_set_tcp_ports(raop_t *raop, unsigned short port[2]); 119 | RAOP_API unsigned short raop_get_port(raop_t *raop); 120 | RAOP_API void *raop_get_callback_cls(raop_t *raop); 121 | RAOP_API int raop_start(raop_t *raop, unsigned short *port); 122 | RAOP_API int raop_is_running(raop_t *raop); 123 | RAOP_API void raop_stop(raop_t *raop); 124 | RAOP_API void raop_set_dnssd(raop_t *raop, dnssd_t *dnssd); 125 | RAOP_API void raop_destroy(raop_t *raop); 126 | RAOP_API void raop_remove_known_connections(raop_t * raop); 127 | RAOP_API void raop_destroy_airplay_video(raop_t *raop); 128 | 129 | #ifdef __cplusplus 130 | } 131 | #endif 132 | #endif 133 | -------------------------------------------------------------------------------- /lib/raop_buffer.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================== 15 | * modified by fduncanh 2021-2023 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "raop_buffer.h" 27 | #include "raop_rtp.h" 28 | 29 | #include "crypto.h" 30 | #include "compat.h" 31 | #include "stream.h" 32 | #include "global.h" 33 | #include "utils.h" 34 | #include "byteutils.h" 35 | 36 | #define RAOP_BUFFER_LENGTH 32 37 | 38 | typedef struct { 39 | /* Data available */ 40 | int filled; 41 | 42 | /* RTP header */ 43 | unsigned short seqnum; 44 | uint64_t rtp_timestamp; 45 | uint64_t ntp_timestamp; 46 | 47 | /* Payload data */ 48 | unsigned int payload_size; 49 | void *payload_data; 50 | } raop_buffer_entry_t; 51 | 52 | struct raop_buffer_s { 53 | logger_t *logger; 54 | /* AES CTX used for decryption */ 55 | aes_ctx_t *aes_ctx; 56 | 57 | /* First and last seqnum */ 58 | int is_empty; 59 | unsigned short first_seqnum; 60 | unsigned short last_seqnum; 61 | 62 | /* RTP buffer entries */ 63 | raop_buffer_entry_t entries[RAOP_BUFFER_LENGTH]; 64 | }; 65 | 66 | raop_buffer_t * 67 | raop_buffer_init(logger_t *logger, 68 | const unsigned char *aeskey, 69 | const unsigned char *aesiv) 70 | { 71 | raop_buffer_t *raop_buffer; 72 | assert(aeskey); 73 | assert(aesiv); 74 | raop_buffer = calloc(1, sizeof(raop_buffer_t)); 75 | if (!raop_buffer) { 76 | return NULL; 77 | } 78 | raop_buffer->logger = logger; 79 | // Need to be initialized internally 80 | raop_buffer->aes_ctx = aes_cbc_init(aeskey, aesiv, AES_DECRYPT); 81 | 82 | for (int i = 0; i < RAOP_BUFFER_LENGTH; i++) { 83 | raop_buffer_entry_t *entry = &raop_buffer->entries[i]; 84 | entry->payload_data = NULL; 85 | entry->payload_size = 0; 86 | } 87 | 88 | raop_buffer->is_empty = 1; 89 | 90 | return raop_buffer; 91 | } 92 | 93 | void 94 | raop_buffer_destroy(raop_buffer_t *raop_buffer) 95 | { 96 | for (int i = 0; i < RAOP_BUFFER_LENGTH; i++) { 97 | raop_buffer_entry_t *entry = &raop_buffer->entries[i]; 98 | if (entry->payload_data != NULL) { 99 | free(entry->payload_data); 100 | } 101 | } 102 | 103 | if (raop_buffer) { 104 | aes_cbc_destroy(raop_buffer->aes_ctx); 105 | free(raop_buffer); 106 | } 107 | 108 | } 109 | 110 | static short 111 | seqnum_cmp(unsigned short s1, unsigned short s2) 112 | { 113 | return (s1 - s2); 114 | } 115 | 116 | int 117 | raop_buffer_decrypt(raop_buffer_t *raop_buffer, unsigned char *data, unsigned char* output, unsigned int payload_size, unsigned int *outputlen) 118 | { 119 | assert(raop_buffer); 120 | int encryptedlen; 121 | if (DECRYPTION_TEST) { 122 | char *str = utils_data_to_string(data,12,12); 123 | logger_log(raop_buffer->logger, LOGGER_INFO, "encrypted 12 byte header %s", str); 124 | free(str); 125 | if (payload_size) { 126 | str = utils_data_to_string(&data[12],16,16); 127 | logger_log(raop_buffer->logger, LOGGER_INFO, "len %d before decryption:\n%s", payload_size, str); 128 | free(str); 129 | } 130 | } 131 | encryptedlen = payload_size / 16*16; 132 | memset(output, 0, payload_size); 133 | 134 | aes_cbc_decrypt(raop_buffer->aes_ctx, &data[12], output, encryptedlen); 135 | aes_cbc_reset(raop_buffer->aes_ctx); 136 | 137 | memcpy(output + encryptedlen, &data[12 + encryptedlen], payload_size - encryptedlen); 138 | *outputlen = payload_size; 139 | if (payload_size && DECRYPTION_TEST){ 140 | switch (output[0]) { 141 | case 0x8c: 142 | case 0x8d: 143 | case 0x8e: 144 | case 0x80: 145 | case 0x81: 146 | case 0x82: 147 | case 0x20: 148 | break; 149 | default: 150 | logger_log(raop_buffer->logger, LOGGER_INFO, "***ERROR AUDIO FRAME IS NOT AAC_ELD OR ALAC"); 151 | break; 152 | } 153 | if (DECRYPTION_TEST == 2) { 154 | logger_log(raop_buffer->logger, LOGGER_INFO, "decrypted audio frame, len = %d", *outputlen); 155 | char *str = utils_data_to_string(output,payload_size,16); 156 | logger_log(raop_buffer->logger, LOGGER_INFO,"%s",str); 157 | free(str); 158 | } else { 159 | char *str = utils_data_to_string(output,16,16); 160 | logger_log(raop_buffer->logger, LOGGER_INFO, "%d after \n%s", payload_size, str); 161 | free(str); 162 | } 163 | } 164 | return 1; 165 | } 166 | 167 | int 168 | raop_buffer_enqueue(raop_buffer_t *raop_buffer, unsigned char *data, unsigned short datalen, uint64_t *ntp_timestamp, uint64_t *rtp_timestamp, int use_seqnum) { 169 | unsigned char empty_packet_marker[] = { 0x00, 0x68, 0x34, 0x00 }; 170 | assert(raop_buffer); 171 | 172 | /* Check packet data length is valid */ 173 | if (datalen < 12 || datalen > RAOP_PACKET_LEN) { 174 | return -1; 175 | } 176 | /* before time is synchronized, some empty data packets are sent */ 177 | if (datalen == 16 && !memcmp(&data[12], empty_packet_marker, 4)) { 178 | return 0; 179 | } 180 | int payload_size = datalen - 12; 181 | 182 | /* Get correct seqnum for the packet */ 183 | unsigned short seqnum; 184 | if (use_seqnum) { 185 | seqnum = byteutils_get_short_be(data, 2); 186 | } else { 187 | seqnum = raop_buffer->first_seqnum; 188 | } 189 | 190 | /* If this packet is too late, just skip it */ 191 | if (!raop_buffer->is_empty && seqnum_cmp(seqnum, raop_buffer->first_seqnum) < 0) { 192 | return 0; 193 | } 194 | 195 | /* Check that there is always space in the buffer, otherwise flush */ 196 | if (seqnum_cmp(seqnum, raop_buffer->first_seqnum + RAOP_BUFFER_LENGTH) >= 0) { 197 | raop_buffer_flush(raop_buffer, seqnum); 198 | } 199 | 200 | /* Get entry corresponding our seqnum */ 201 | raop_buffer_entry_t *entry = &raop_buffer->entries[seqnum % RAOP_BUFFER_LENGTH]; 202 | if (entry->filled && seqnum_cmp(entry->seqnum, seqnum) == 0) { 203 | /* Packet resend, we can safely ignore */ 204 | return 0; 205 | } 206 | 207 | /* Update the raop_buffer entry header */ 208 | entry->seqnum = seqnum; 209 | entry->rtp_timestamp = *rtp_timestamp; 210 | entry->ntp_timestamp = *ntp_timestamp; 211 | entry->filled = 1; 212 | 213 | entry->payload_data = malloc(payload_size); 214 | int decrypt_ret = raop_buffer_decrypt(raop_buffer, data, entry->payload_data, payload_size, &entry->payload_size); 215 | assert(decrypt_ret >= 0); 216 | assert(entry->payload_size <= payload_size); 217 | 218 | /* Update the raop_buffer seqnums */ 219 | if (raop_buffer->is_empty) { 220 | raop_buffer->first_seqnum = seqnum; 221 | raop_buffer->last_seqnum = seqnum; 222 | raop_buffer->is_empty = 0; 223 | } 224 | if (seqnum_cmp(seqnum, raop_buffer->last_seqnum) > 0) { 225 | raop_buffer->last_seqnum = seqnum; 226 | } 227 | return 1; 228 | } 229 | 230 | void * 231 | raop_buffer_dequeue(raop_buffer_t *raop_buffer, unsigned int *length, uint64_t *ntp_timestamp, uint64_t *rtp_timestamp, unsigned short *seqnum, int no_resend) { 232 | assert(raop_buffer); 233 | 234 | /* Calculate number of entries in the current buffer */ 235 | short entry_count = seqnum_cmp(raop_buffer->last_seqnum, raop_buffer->first_seqnum)+1; 236 | 237 | /* Cannot dequeue from empty buffer */ 238 | if (raop_buffer->is_empty || entry_count <= 0) { 239 | return NULL; 240 | } 241 | 242 | /* Get the first buffer entry for inspection */ 243 | raop_buffer_entry_t *entry = &raop_buffer->entries[raop_buffer->first_seqnum % RAOP_BUFFER_LENGTH]; 244 | if (no_resend) { 245 | /* If we do no resends, always return the first entry */ 246 | } else if (!entry->filled) { 247 | /* Check how much we have space left in the buffer */ 248 | if (entry_count < RAOP_BUFFER_LENGTH) { 249 | /* Return nothing and hope resend gets on time */ 250 | return NULL; 251 | } 252 | /* Risk of buffer overrun, return empty buffer */ 253 | } 254 | 255 | /* Update buffer and validate entry */ 256 | raop_buffer->first_seqnum += 1; 257 | if (!entry->filled) { 258 | return NULL; 259 | } 260 | entry->filled = 0; 261 | 262 | /* Return entry payload buffer */ 263 | *rtp_timestamp = entry->rtp_timestamp; 264 | *ntp_timestamp = entry->ntp_timestamp; 265 | *seqnum = entry->seqnum; 266 | *length = entry->payload_size; 267 | entry->payload_size = 0; 268 | void* data = entry->payload_data; 269 | entry->payload_data = NULL; 270 | return data; 271 | } 272 | 273 | void raop_buffer_handle_resends(raop_buffer_t *raop_buffer, raop_resend_cb_t resend_cb, void *opaque) { 274 | assert(raop_buffer); 275 | assert(resend_cb); 276 | 277 | if (seqnum_cmp(raop_buffer->first_seqnum, raop_buffer->last_seqnum) < 0) { 278 | unsigned short seqnum, count = 0; 279 | logger_log(raop_buffer->logger, LOGGER_DEBUG, "raop_buffer_handle_resends first_seqnum=%u last seqnum=%u", 280 | raop_buffer->first_seqnum, raop_buffer->last_seqnum); 281 | for (seqnum = raop_buffer->first_seqnum; seqnum_cmp(seqnum, raop_buffer->last_seqnum) < 0; seqnum++) { 282 | raop_buffer_entry_t *entry = &raop_buffer->entries[seqnum % RAOP_BUFFER_LENGTH]; 283 | if (entry->filled) { 284 | break; 285 | } 286 | count++; 287 | } 288 | if (count){ 289 | resend_cb(opaque, raop_buffer->first_seqnum, count); 290 | } 291 | } 292 | } 293 | 294 | void raop_buffer_flush(raop_buffer_t *raop_buffer, int next_seq) { 295 | assert(raop_buffer); 296 | 297 | for (int i = 0; i < RAOP_BUFFER_LENGTH; i++) { 298 | if (raop_buffer->entries[i].payload_data) { 299 | free(raop_buffer->entries[i].payload_data); 300 | raop_buffer->entries[i].payload_data = NULL; 301 | raop_buffer->entries[i].payload_size = 0; 302 | } 303 | raop_buffer->entries[i].filled = 0; 304 | } 305 | if (next_seq < 0 || next_seq > 0xffff) { 306 | raop_buffer->is_empty = 1; 307 | } else { 308 | raop_buffer->first_seqnum = next_seq; 309 | raop_buffer->last_seqnum = next_seq - 1; 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /lib/raop_buffer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================== 15 | * modified by fduncanh 2021-23 16 | */ 17 | 18 | #ifndef RAOP_BUFFER_H 19 | #define RAOP_BUFFER_H 20 | 21 | #include "logger.h" 22 | #include "raop_rtp.h" 23 | 24 | typedef struct raop_buffer_s raop_buffer_t; 25 | 26 | typedef int (*raop_resend_cb_t)(void *opaque, unsigned short seqno, unsigned short count); 27 | 28 | raop_buffer_t *raop_buffer_init(logger_t *logger, 29 | const unsigned char *aeskey, 30 | const unsigned char *aesiv); 31 | int raop_buffer_enqueue(raop_buffer_t *raop_buffer, unsigned char *data, unsigned short datalen, uint64_t *ntp_timestamp, uint64_t *rtp_timestamp, int use_seqnum); 32 | void *raop_buffer_dequeue(raop_buffer_t *raop_buffer, unsigned int *length, uint64_t *ntp_timestamp, uint64_t *rtp_timestamp, unsigned short *seqnum, int no_resend); 33 | void raop_buffer_handle_resends(raop_buffer_t *raop_buffer, raop_resend_cb_t resend_cb, void *opaque); 34 | void raop_buffer_flush(raop_buffer_t *raop_buffer, int next_seq); 35 | 36 | int raop_buffer_decrypt(raop_buffer_t *raop_buffer, unsigned char *data, unsigned char* output, 37 | unsigned int datalen, unsigned int *outputlen); 38 | void raop_buffer_destroy(raop_buffer_t *raop_buffer); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /lib/raop_ntp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 dsafa22, modified by Florian Draschbacher, 3 | * All Rights Reserved. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | *================================================================= 16 | * modified by fduncanh 2021-2023 17 | */ 18 | 19 | #ifndef RAOP_NTP_H 20 | #define RAOP_NTP_H 21 | 22 | #include 23 | #include 24 | #include "logger.h" 25 | 26 | typedef struct raop_ntp_s raop_ntp_t; 27 | 28 | typedef enum timing_protocol_e { NTP, TP_NONE, TP_OTHER, TP_UNSPECIFIED } timing_protocol_t; 29 | 30 | void raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport, int max_ntp_timeouts); 31 | 32 | void raop_ntp_stop(raop_ntp_t *raop_ntp); 33 | 34 | unsigned short raop_ntp_get_port(raop_ntp_t *raop_ntp); 35 | 36 | void raop_ntp_destroy(raop_ntp_t *raop_rtp); 37 | 38 | uint64_t raop_ntp_timestamp_to_nano_seconds(uint64_t ntp_timestamp, bool account_for_epoch_diff); 39 | uint64_t raop_remote_timestamp_to_nano_seconds(raop_ntp_t *raop_ntp, uint64_t timestamp); 40 | 41 | uint64_t raop_ntp_get_local_time(raop_ntp_t *raop_ntp); 42 | uint64_t raop_ntp_get_remote_time(raop_ntp_t *raop_ntp); 43 | uint64_t raop_ntp_convert_remote_time(raop_ntp_t *raop_ntp, uint64_t remote_time); 44 | uint64_t raop_ntp_convert_local_time(raop_ntp_t *raop_ntp, uint64_t local_time); 45 | 46 | #endif //RAOP_NTP_H 47 | -------------------------------------------------------------------------------- /lib/raop_rtp.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================== 15 | * modified by fduncanh 2021-2023 16 | */ 17 | 18 | #ifndef RAOP_RTP_H 19 | #define RAOP_RTP_H 20 | 21 | /* For raop_callbacks_t */ 22 | #include "raop.h" 23 | #include "logger.h" 24 | #include "raop_ntp.h" 25 | 26 | #define RAOP_AESIV_LEN 16 27 | #define RAOP_AESKEY_LEN 16 28 | #define RAOP_PACKET_LEN 32768 29 | 30 | typedef struct raop_rtp_s raop_rtp_t; 31 | 32 | raop_rtp_t *raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp, const char *remote, 33 | int remotelen, const unsigned char *aeskey, const unsigned char *aesiv); 34 | 35 | void raop_rtp_start_audio(raop_rtp_t *raop_rtp, unsigned short *control_rport, unsigned short *control_lport, 36 | unsigned short *data_lport, unsigned char *ct, unsigned int *sr); 37 | 38 | void raop_rtp_set_volume(raop_rtp_t *raop_rtp, float volume); 39 | void raop_rtp_set_metadata(raop_rtp_t *raop_rtp, const char *data, int datalen); 40 | void raop_rtp_set_coverart(raop_rtp_t *raop_rtp, const char *data, int datalen); 41 | void raop_rtp_remote_control_id(raop_rtp_t *raop_rtp, const char *dacp_id, const char *active_remote_header); 42 | void raop_rtp_set_progress(raop_rtp_t *raop_rtp, unsigned int start, unsigned int curr, unsigned int end); 43 | void raop_rtp_flush(raop_rtp_t *raop_rtp, int next_seq); 44 | void raop_rtp_stop(raop_rtp_t *raop_rtp); 45 | int raop_rtp_is_running(raop_rtp_t *raop_rtp); 46 | void raop_rtp_destroy(raop_rtp_t *raop_rtp); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /lib/raop_rtp_mirror.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 dsafa22, All Rights Reserved. 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================= 15 | * modified by fduncanh 2021-2023 16 | */ 17 | 18 | #ifndef RAOP_RTP_MIRROR_H 19 | #define RAOP_RTP_MIRROR_H 20 | 21 | #include 22 | #include "raop.h" 23 | #include "logger.h" 24 | 25 | typedef struct raop_rtp_mirror_s raop_rtp_mirror_t; 26 | typedef struct h264codec_s h264codec_t; 27 | 28 | raop_rtp_mirror_t *raop_rtp_mirror_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp, 29 | const char *remote, int remotelen, const unsigned char *aeskey); 30 | void raop_rtp_mirror_init_aes(raop_rtp_mirror_t *raop_rtp_mirror, uint64_t *streamConnectionID); 31 | void raop_rtp_mirror_start(raop_rtp_mirror_t *raop_rtp_mirror, unsigned short *mirror_data_lport, uint8_t show_client_FPS_data); 32 | void raop_rtp_mirror_stop(raop_rtp_mirror_t *raop_rtp_mirror); 33 | void raop_rtp_mirror_destroy(raop_rtp_mirror_t *raop_rtp_mirror); 34 | #endif //RAOP_RTP_MIRROR_H 35 | -------------------------------------------------------------------------------- /lib/sockets.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | #ifndef SOCKETS_H 16 | #define SOCKETS_H 17 | 18 | #if defined(WIN32) 19 | 20 | char *wsa_strerror(int errnum); 21 | 22 | typedef int socklen_t; 23 | 24 | #ifndef SHUT_RD 25 | # define SHUT_RD SD_RECEIVE 26 | #endif 27 | #ifndef SHUT_WR 28 | # define SHUT_WR SD_SEND 29 | #endif 30 | #ifndef SHUT_RDWR 31 | # define SHUT_RDWR SD_BOTH 32 | #endif 33 | 34 | #define SOCKET_GET_ERROR() WSAGetLastError() 35 | #define SOCKET_SET_ERROR(value) WSASetLastError(value) 36 | #define SOCKET_ERRORNAME(name) WSA##name 37 | #define SOCKET_ERROR_STRING(errnum) wsa_strerror(errnum) 38 | 39 | #define WSAEAGAIN WSAEWOULDBLOCK 40 | #define WSAENOMEM WSA_NOT_ENOUGH_MEMORY 41 | 42 | #else 43 | 44 | #define closesocket close 45 | #define ioctlsocket ioctl 46 | 47 | #define SOCKET_GET_ERROR() (errno) 48 | #define SOCKET_SET_ERROR(value) (errno = (value)) 49 | #define SOCKET_ERRORNAME(name) name 50 | #define SOCKET_ERROR_STRING(errnum) strerror(errnum) 51 | #endif 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /lib/srp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Secure Remote Password 6a implementation 3 | * Copyright (c) 2010 Tom Cocagne. All rights reserved. 4 | * https://github.com/cocagne/csrp 5 | * 6 | * The MIT License (MIT) 7 | * 8 | * Copyright (c) 2014 Tom Cocagne 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | * this software and associated documentation files (the "Software"), to deal in 12 | * the Software without restriction, including without limitation the rights to 13 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 14 | * of the Software, and to permit persons to whom the Software is furnished to do 15 | * so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | *=========================================================================== 29 | * updated (2023) by fduncanh to replace deprecated openssl SHA* hash functions 30 | * modified (2023) by fduncanh for use with Apple's pair-setup-pin protocol 31 | */ 32 | 33 | /* 34 | * 35 | * Purpose: This is a direct implementation of the Secure Remote Password 36 | * Protocol version 6a as described by 37 | * http://srp.stanford.edu/design.html 38 | * 39 | * Author: tom.cocagne@gmail.com (Tom Cocagne) 40 | * 41 | * Dependencies: OpenSSL (and Advapi32.lib on Windows) 42 | * 43 | * Usage: Refer to test_srp.c for a demonstration 44 | * 45 | * Notes: 46 | * This library allows multiple combinations of hashing algorithms and 47 | * prime number constants. For authentication to succeed, the hash and 48 | * prime number constants must match between 49 | * srp_create_salted_verification_key(), srp_user_new(), 50 | * and srp_verifier_new(). A recommended approach is to determine the 51 | * desired level of security for an application and globally define the 52 | * hash and prime number constants to the predetermined values. 53 | * 54 | * As one might suspect, more bits means more security. As one might also 55 | * suspect, more bits also means more processing time. The test_srp.c 56 | * program can be easily modified to profile various combinations of 57 | * hash & prime number pairings. 58 | */ 59 | 60 | #ifndef SRP_H 61 | #define SRP_H 62 | #define APPLE_VARIANT 63 | 64 | struct SRPVerifier; 65 | #if 0 /*begin removed section 1*/ 66 | struct SRPUser; 67 | #endif /*end removed section 1*/ 68 | typedef enum 69 | { 70 | #if 0 /* begin removed section 2*/ 71 | SRP_NG_1024, 72 | SRP_NG_1536, 73 | #endif /* end removed section 2*/ 74 | SRP_NG_2048, 75 | #if 0 /* begin removed section 3*/ 76 | SRP_NG_3072, 77 | SRP_NG_4096, 78 | SRP_NG_6144, 79 | SRP_NG_8192, 80 | #endif /* end removed section 3*/ 81 | SRP_NG_CUSTOM 82 | } SRP_NGType; 83 | 84 | typedef enum 85 | { 86 | SRP_SHA1, 87 | SRP_SHA224, 88 | SRP_SHA256, 89 | SRP_SHA384, 90 | SRP_SHA512 91 | } SRP_HashAlgorithm; 92 | 93 | 94 | /* This library will automatically seed the OpenSSL random number generator 95 | * using cryptographically sound random data on Windows & Linux. If this is 96 | * undesirable behavior or the host OS does not provide a /dev/urandom file, 97 | * this function may be called to seed the random number generator with 98 | * alternate data. 99 | * 100 | * The random data should include at least as many bits of entropy as the 101 | * largest hash function used by the application. So, for example, if a 102 | * 512-bit hash function is used, the random data requies at least 512 103 | * bits of entropy. 104 | * 105 | * Passing a null pointer to this function will cause this library to skip 106 | * seeding the random number generator. This is only legitimate if it is 107 | * absolutely known that the OpenSSL random number generator has already 108 | * been sufficiently seeded within the running application. 109 | * 110 | * Notes: 111 | * * This function is optional on Windows & Linux and mandatory on all 112 | * other platforms. 113 | */ 114 | void srp_random_seed( const unsigned char * random_data, int data_length ); 115 | 116 | 117 | /* Out: bytes_s, len_s, bytes_v, len_v 118 | * 119 | * The caller is responsible for freeing the memory allocated for bytes_s and bytes_v 120 | * 121 | * The n_hex and g_hex parameters should be 0 unless SRP_NG_CUSTOM is used for ng_type. 122 | * If provided, they must contain ASCII text of the hexidecimal notation. 123 | * 124 | */ 125 | void srp_create_salted_verification_key( SRP_HashAlgorithm alg, 126 | SRP_NGType ng_type, const char * username, 127 | const unsigned char * password, int len_password, 128 | const unsigned char ** bytes_s, int * len_s, 129 | const unsigned char ** bytes_v, int * len_v, 130 | const char * n_hex, const char * g_hex ); 131 | 132 | 133 | #ifdef APPLE_VARIANT 134 | /* Out: bytes_B, len_B 135 | * On failure, bytes_B will be set to NULL and len_B will be set to 0 136 | * 137 | * The n_hex and g_hex parameters should be 0 unless SRP_NG_CUSTOM is used for ng_type 138 | * 139 | * bytes_b should be a pointer to a cryptographically secure random array of length 140 | * len_b bytes (for example, produced with OpenSSL's RAND_bytes(bytes_b, len_b)). 141 | */ 142 | void srp_create_server_ephemeral_key( SRP_HashAlgorithm alg, SRP_NGType ng_type, 143 | const unsigned char * bytes_v, int len_v, 144 | const unsigned char * bytes_b, int len_b, 145 | const unsigned char ** bytes_B, int * len_B, 146 | const char * n_hex, const char * g_hex, 147 | int rfc5054_compat ); 148 | #endif 149 | 150 | /* Out: bytes_B, len_B. 151 | * 152 | * On failure, bytes_B will be set to NULL and len_B will be set to 0 153 | * 154 | * The n_hex and g_hex parameters should be 0 unless SRP_NG_CUSTOM is used for ng_type 155 | * 156 | * If rfc5054_compat is non-zero the resulting verifier will be RFC 5054 compaliant. This 157 | * breaks compatibility with previous versions of the csrp library but is recommended 158 | * for new code. 159 | */ 160 | struct SRPVerifier * srp_verifier_new( SRP_HashAlgorithm alg, SRP_NGType ng_type, const char * username, 161 | const unsigned char * bytes_s, int len_s, 162 | const unsigned char * bytes_v, int len_v, 163 | const unsigned char * bytes_A, int len_A, 164 | #ifdef APPLE_VARIANT 165 | const unsigned char * bytes_b, int len_b, 166 | #endif 167 | const unsigned char ** bytes_B, int * len_B, 168 | const char * n_hex, const char * g_hex, 169 | int rfc5054_compat ); 170 | 171 | 172 | void srp_verifier_delete( struct SRPVerifier * ver ); 173 | 174 | 175 | int srp_verifier_is_authenticated( struct SRPVerifier * ver ); 176 | 177 | 178 | const char * srp_verifier_get_username( struct SRPVerifier * ver ); 179 | 180 | /* key_length may be null */ 181 | const unsigned char * srp_verifier_get_session_key( struct SRPVerifier * ver, int * key_length ); 182 | 183 | 184 | int srp_verifier_get_session_key_length( struct SRPVerifier * ver ); 185 | 186 | 187 | /* user_M must be exactly srp_verifier_get_session_key_length() bytes in size */ 188 | /* (in APPLE_VARIANT case, session_key_length is DOUBLE the length of user_M) */ 189 | void srp_verifier_verify_session( struct SRPVerifier * ver, 190 | const unsigned char * user_M, 191 | const unsigned char ** bytes_HAMK ); 192 | 193 | /*******************************************************************************/ 194 | 195 | /* The n_hex and g_hex parameters should be 0 unless SRP_NG_CUSTOM is used for ng_type 196 | * 197 | * If rfc5054_compat is non-zero the resulting verifier will be RFC 5054 compaliant. This 198 | * breaks compatibility with previous versions of the csrp library but is recommended 199 | * for new code. 200 | */ 201 | #if 0 /*begin removed section 4 */ 202 | struct SRPUser * srp_user_new( SRP_HashAlgorithm alg, SRP_NGType ng_type, const char * username, 203 | const unsigned char * bytes_password, int len_password, 204 | const char * n_hex, const char * g_hex, 205 | int rfc5054_compat ); 206 | 207 | void srp_user_delete( struct SRPUser * usr ); 208 | 209 | int srp_user_is_authenticated( struct SRPUser * usr); 210 | 211 | 212 | const char * srp_user_get_username( struct SRPUser * usr ); 213 | 214 | /* key_length may be null */ 215 | const unsigned char * srp_user_get_session_key( struct SRPUser * usr, int * key_length ); 216 | 217 | int srp_user_get_session_key_length( struct SRPUser * usr ); 218 | 219 | /* Output: username, bytes_A, len_A */ 220 | void srp_user_start_authentication( struct SRPUser * usr, const char ** username, 221 | const unsigned char ** bytes_A, int * len_A ); 222 | 223 | /* Output: bytes_M, len_M (len_M may be null and will always be 224 | * srp_user_get_session_key_length() bytes in size) */ 225 | /* (in APPLE_VARIANT case, session_key_length is DOUBLE the length of bytes_M) */ 226 | void srp_user_process_challenge( struct SRPUser * usr, 227 | const unsigned char * bytes_s, int len_s, 228 | const unsigned char * bytes_B, int len_B, 229 | const unsigned char ** bytes_M, int * len_M ); 230 | 231 | /* bytes_HAMK must be exactly srp_user_get_session_key_length() bytes in size */ 232 | /* (in APPLE_VARIANT case, session_key_length is DOUBLE the length of bytes_HAMK) */ 233 | void srp_user_verify_session( struct SRPUser * usr, const unsigned char * bytes_HAMK ); 234 | 235 | #endif /*end removed section 4*/ 236 | #endif /* Include Guard */ 237 | -------------------------------------------------------------------------------- /lib/stream.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 dsafa22, All Rights Reserved. 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================= 15 | * modified by fduncanh 2022-2023 16 | */ 17 | 18 | #ifndef AIRPLAYSERVER_STREAM_H 19 | #define AIRPLAYSERVER_STREAM_H 20 | 21 | #include 22 | #include 23 | 24 | typedef struct { 25 | bool is_h265; 26 | int nal_count; 27 | unsigned char *data; 28 | int data_len; 29 | uint64_t ntp_time_local; 30 | uint64_t ntp_time_remote; 31 | } video_decode_struct; 32 | 33 | typedef struct { 34 | unsigned char *data; 35 | unsigned char ct; 36 | int data_len; 37 | int sync_status; 38 | uint64_t ntp_time_local; 39 | uint64_t ntp_time_remote; 40 | uint64_t rtp_time; 41 | unsigned short seqnum; 42 | } audio_decode_struct; 43 | 44 | #endif //AIRPLAYSERVER_STREAM_H 45 | -------------------------------------------------------------------------------- /lib/threads.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================= 15 | * modified by fduncanh 2022 16 | */ 17 | 18 | #ifndef THREADS_H 19 | #define THREADS_H 20 | 21 | /* Always use pthread library */ 22 | 23 | #include 24 | #include 25 | 26 | #define sleepms(x) usleep((x)*1000) 27 | 28 | typedef pthread_t thread_handle_t; 29 | 30 | #define THREAD_RETVAL void * 31 | #define THREAD_CREATE(handle, func, arg) \ 32 | if (pthread_create(&(handle), NULL, func, arg)) handle = 0 33 | #define THREAD_JOIN(handle) pthread_join(handle, NULL) 34 | 35 | typedef pthread_mutex_t mutex_handle_t; 36 | 37 | typedef pthread_cond_t cond_handle_t; 38 | 39 | #define MUTEX_CREATE(handle) pthread_mutex_init(&(handle), NULL) 40 | #define MUTEX_LOCK(handle) pthread_mutex_lock(&(handle)) 41 | #define MUTEX_UNLOCK(handle) pthread_mutex_unlock(&(handle)) 42 | #define MUTEX_DESTROY(handle) pthread_mutex_destroy(&(handle)) 43 | 44 | #define COND_CREATE(handle) pthread_cond_init(&(handle), NULL) 45 | #define COND_SIGNAL(handle) pthread_cond_signal(&(handle)) 46 | #define COND_DESTROY(handle) pthread_cond_destroy(&(handle)) 47 | 48 | #endif /* THREADS_H */ 49 | -------------------------------------------------------------------------------- /lib/utils.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================= 15 | * modified by fduncanh 2021-2022 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #define SECOND_IN_NSECS 1000000000UL 25 | 26 | char * 27 | utils_strsep(char **stringp, const char *delim) 28 | { 29 | char *original; 30 | char *strptr; 31 | 32 | if (*stringp == NULL) { 33 | return NULL; 34 | } 35 | 36 | original = *stringp; 37 | strptr = strstr(*stringp, delim); 38 | if (strptr == NULL) { 39 | *stringp = NULL; 40 | return original; 41 | } 42 | *strptr = '\0'; 43 | *stringp = strptr+strlen(delim); 44 | return original; 45 | } 46 | 47 | int 48 | utils_read_file(char **dst, const char *filename) 49 | { 50 | FILE *stream; 51 | int filesize; 52 | char *buffer; 53 | int read_bytes; 54 | 55 | /* Open stream for reading */ 56 | stream = fopen(filename, "rb"); 57 | if (!stream) { 58 | return -1; 59 | } 60 | 61 | /* Find out file size */ 62 | fseek(stream, 0, SEEK_END); 63 | filesize = ftell(stream); 64 | fseek(stream, 0, SEEK_SET); 65 | 66 | /* Allocate one extra byte for zero */ 67 | buffer = malloc(filesize+1); 68 | if (!buffer) { 69 | fclose(stream); 70 | return -2; 71 | } 72 | 73 | /* Read data in a loop to buffer */ 74 | read_bytes = 0; 75 | do { 76 | int ret = fread(buffer+read_bytes, 1, 77 | filesize-read_bytes, stream); 78 | if (ret == 0) { 79 | break; 80 | } 81 | read_bytes += ret; 82 | } while (read_bytes < filesize); 83 | 84 | /* Add final null byte and close stream */ 85 | buffer[read_bytes] = '\0'; 86 | fclose(stream); 87 | 88 | /* If read didn't finish, return error */ 89 | if (read_bytes != filesize) { 90 | free(buffer); 91 | return -3; 92 | } 93 | 94 | /* Return buffer */ 95 | *dst = buffer; 96 | return filesize; 97 | } 98 | 99 | int 100 | utils_hwaddr_raop(char *str, int strlen, const char *hwaddr, int hwaddrlen) 101 | { 102 | int i,j; 103 | 104 | /* Check that our string is long enough */ 105 | if (strlen == 0 || strlen < 2*hwaddrlen+1) 106 | return -1; 107 | 108 | /* Convert hardware address to hex string */ 109 | for (i=0,j=0; i>4) & 0x0f; 111 | int lo = hwaddr[i] & 0x0f; 112 | 113 | if (hi < 10) str[j++] = '0' + hi; 114 | else str[j++] = 'A' + hi-10; 115 | if (lo < 10) str[j++] = '0' + lo; 116 | else str[j++] = 'A' + lo-10; 117 | } 118 | 119 | /* Add string terminator */ 120 | str[j++] = '\0'; 121 | return j; 122 | } 123 | 124 | int 125 | utils_hwaddr_airplay(char *str, int strlen, const char *hwaddr, int hwaddrlen) 126 | { 127 | int i,j; 128 | 129 | /* Check that our string is long enough */ 130 | if (strlen == 0 || strlen < 2*hwaddrlen+hwaddrlen) 131 | return -1; 132 | 133 | /* Convert hardware address to hex string */ 134 | for (i=0,j=0; i>4) & 0x0f; 136 | int lo = hwaddr[i] & 0x0f; 137 | 138 | if (hi < 10) str[j++] = '0' + hi; 139 | else str[j++] = 'a' + hi-10; 140 | if (lo < 10) str[j++] = '0' + lo; 141 | else str[j++] = 'a' + lo-10; 142 | 143 | str[j++] = ':'; 144 | } 145 | 146 | /* Add string terminator */ 147 | if (j != 0) j--; 148 | str[j++] = '\0'; 149 | return j; 150 | } 151 | 152 | char *utils_parse_hex(const char *str, int str_len, int *data_len) { 153 | assert(str_len % 2 == 0); 154 | 155 | char *data = malloc(str_len / 2); 156 | 157 | for (int i = 0; i < (str_len / 2); i++) { 158 | char c_1 = str[i * 2]; 159 | if (c_1 >= 97 && c_1 <= 102) { 160 | c_1 -= (97 - 10); 161 | } else if (c_1 >= 65 && c_1 <= 70) { 162 | c_1 -= (65 - 10); 163 | } else if (c_1 >= 48 && c_1 <= 57) { 164 | c_1 -= 48; 165 | } else { 166 | free(data); 167 | return NULL; 168 | } 169 | 170 | char c_2 = str[(i * 2) + 1]; 171 | if (c_2 >= 97 && c_2 <= 102) { 172 | c_2 -= (97 - 10); 173 | } else if (c_2 >= 65 && c_2 <= 70) { 174 | c_2 -= (65 - 10); 175 | } else if (c_2 >= 48 && c_2 <= 57) { 176 | c_2 -= 48; 177 | } else { 178 | free(data); 179 | return NULL; 180 | } 181 | 182 | data[i] = (c_1 << 4) | c_2; 183 | } 184 | 185 | *data_len = (str_len / 2); 186 | return data; 187 | } 188 | 189 | char *utils_pk_to_string(const unsigned char *pk, int pk_len) { 190 | char *pk_str = (char *) malloc(2*pk_len + 1); 191 | char* pos = pk_str; 192 | for (int i = 0; i < pk_len; i++) { 193 | snprintf(pos, 3, "%2.2x", *(pk + i)); 194 | pos +=2; 195 | } 196 | return pk_str; 197 | } 198 | 199 | char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per_line) { 200 | assert(datalen >= 0); 201 | assert(chars_per_line > 0); 202 | int len = 3*datalen + 1; 203 | if (datalen > chars_per_line) { 204 | len += (datalen-1)/chars_per_line; 205 | } 206 | char *str = (char *) calloc(len + 1, sizeof(char)); 207 | assert(str); 208 | char *p = str; 209 | int n = len + 1; 210 | for (int i = 0; i < datalen; i++) { 211 | if (i > 0 && i % chars_per_line == 0) { 212 | snprintf(p, n, "\n"); 213 | n--; 214 | p++; 215 | } 216 | snprintf(p, n, "%2.2x ", (unsigned int) data[i]); 217 | n -= 3; 218 | p += 3; 219 | } 220 | snprintf(p, n, "\n"); 221 | n--; 222 | p++; 223 | assert(p == &(str[len])); 224 | assert(len == strlen(str)); 225 | return str; 226 | } 227 | 228 | char *utils_data_to_text(const char *data, int datalen) { 229 | char *ptr = (char *) calloc(datalen + 1, sizeof(char)); 230 | assert(ptr); 231 | strncpy(ptr, data, datalen); 232 | char *p = ptr; 233 | while (p) { 234 | p = strchr(p, '\r'); /* replace occurences of '\r' by ' ' */ 235 | if (p) *p = ' '; 236 | } 237 | return ptr; 238 | } 239 | 240 | void ntp_timestamp_to_time(uint64_t ntp_timestamp, char *timestamp, size_t maxsize) { 241 | time_t rawtime = (time_t) (ntp_timestamp / SECOND_IN_NSECS); 242 | struct tm ts = *localtime(&rawtime); 243 | assert(maxsize > 29); 244 | #ifdef _WIN32 /*modification for compiling for Windows */ 245 | strftime(timestamp, 20, "%Y-%m-%d %H:%M:%S", &ts); 246 | #else 247 | strftime(timestamp, 20, "%F %T", &ts); 248 | #endif 249 | snprintf(timestamp + 19, 11,".%9.9lu", (unsigned long) ntp_timestamp % SECOND_IN_NSECS); 250 | } 251 | 252 | void ntp_timestamp_to_seconds(uint64_t ntp_timestamp, char *timestamp, size_t maxsize) { 253 | time_t rawtime = (time_t) (ntp_timestamp / SECOND_IN_NSECS); 254 | struct tm ts = *localtime(&rawtime); 255 | assert(maxsize > 12); 256 | strftime(timestamp, 3, "%S", &ts); 257 | snprintf(timestamp + 2, 11,".%9.9lu", (unsigned long) ntp_timestamp % SECOND_IN_NSECS); 258 | } 259 | 260 | int utils_ipaddress_to_string(int addresslen, const unsigned char *address, unsigned int zone_id, char *string, int sizeof_string) { 261 | int ret = 0; 262 | unsigned char ipv6_link_local_prefix[] = { 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; 263 | assert(sizeof_string > 0); 264 | assert(string); 265 | if (addresslen != 4 && addresslen != 16) { //invalid address length (only ipv4 and ipv6 allowed) 266 | string[0] = '\0'; 267 | } 268 | if (addresslen == 4) { /* IPV4 */ 269 | ret = snprintf(string, sizeof_string, "%d.%d.%d.%d", address[0], address[1], address[2], address[3]); 270 | } else if (zone_id) { /* IPV6 link-local */ 271 | if (memcmp(address, ipv6_link_local_prefix, 8)) { 272 | string[0] = '\0'; //only link-local ipv6 addresses can have a zone_id 273 | } else { 274 | ret = snprintf(string, sizeof_string, "fe80::%02x%02x:%02x%02x:%02x%02x:%02x%02x%%%u", 275 | address[8], address[9], address[10], address[11], 276 | address[12], address[13], address[14], address[15], zone_id); 277 | } 278 | } else { /* IPV6 standard*/ 279 | ret = snprintf(string, sizeof_string, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", 280 | address[0], address[1], address[2], address[3], address[4], address[5], address[6], address[7], 281 | address[8], address[9], address[10], address[11], address[12], address[13], address[14], address[15]); 282 | } 283 | return ret; 284 | } 285 | 286 | const char *gmt_time_string() { 287 | static char date_buf[64]; 288 | memset(date_buf, 0, 64); 289 | 290 | time_t now = time(0); 291 | if (strftime(date_buf, 63, "%c GMT", gmtime(&now))) 292 | return date_buf; 293 | else 294 | return ""; 295 | } 296 | -------------------------------------------------------------------------------- /lib/utils.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | *================================================================== 15 | * modified by fduncanh 2021-2022 16 | */ 17 | 18 | #ifndef UTILS_H 19 | #define UTILS_H 20 | 21 | #include 22 | 23 | char *utils_strsep(char **stringp, const char *delim); 24 | int utils_read_file(char **dst, const char *pemstr); 25 | int utils_hwaddr_raop(char *str, int strlen, const char *hwaddr, int hwaddrlen); 26 | int utils_hwaddr_airplay(char *str, int strlen, const char *hwaddr, int hwaddrlen); 27 | char *utils_parse_hex(const char *str, int str_len, int *data_len); 28 | char *utils_pk_to_string(const unsigned char *pk, int pk_len); 29 | char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per_line); 30 | char *utils_data_to_text(const char *data, int datalen); 31 | void ntp_timestamp_to_time(uint64_t ntp_timestamp, char *timestamp, size_t maxsize); 32 | void ntp_timestamp_to_seconds(uint64_t ntp_timestamp, char *timestamp, size_t maxsize); 33 | const char *gmt_time_string(); 34 | int utils_ipaddress_to_string(int addresslen, const unsigned char *address, 35 | unsigned int zone_id, char *string, int len); 36 | #endif 37 | -------------------------------------------------------------------------------- /renderers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | if (APPLE ) 4 | set( ENV{PKG_CONFIG_PATH} "/Library/FrameWorks/GStreamer.framework/Libraries/pkgconfig" ) # GStreamer.framework, preferred 5 | set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig" ) # Brew or self-installed gstreamer 6 | set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/opt/homebrew/lib/pkgconfig" ) # Brew, M1/M2 macs 7 | set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:$ENV{HOMEBREW_PREFIX}/lib/pkgconfig" ) # Brew, using prefix 8 | set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/opt/local/lib/pkgconfig/" ) # MacPorts 9 | message( "PKG_CONFIG_PATH (Apple, renderers) = " $ENV{PKG_CONFIG_PATH} ) 10 | find_program( PKG_CONFIG_EXECUTABLE pkg-config PATHS /Library/FrameWorks/GStreamer.framework/Commands ) 11 | set(PKG_CONFIG_EXECUTABLE ${PKG_CONFIG_EXECUTABLE} --define-prefix ) 12 | else() 13 | if ( DEFINED ENV{GSTREAMER_ROOT_DIR} ) 14 | if ( EXISTS "$ENV{GSTREAMER_ROOT_DIR}/pkgconfig" ) 15 | message ( STATUS "*** Using GSTREAMER_ROOT_DIR = " $ENV{GSTREAMER_ROOT_DIR} ) 16 | set( ENV{PKG_CONFIG_PATH} "$ENV{GSTREAMER_ROOT_DIR}/pkgconfig:$ENV{PKG_CONFIG_PATH}" ) 17 | endif() 18 | endif() 19 | set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig" ) # standard location for self-installed gstreamer 20 | endif() 21 | 22 | find_package( PkgConfig REQUIRED ) 23 | if ( X11_FOUND ) 24 | message (STATUS "Will use X_DISPLAY_FIX" ) 25 | add_definitions( -DX_DISPLAY_FIX ) 26 | pkg_check_modules (GST120 gstreamer-1.0>=1.20) 27 | if ( GST120_FOUND ) 28 | message( "-- ZOOMFIX will NOT be applied as Gstreamer version is >= 1.20" ) 29 | else() 30 | message( "-- Failure to find Gstreamer >= 1.20 is NOT an error!" ) 31 | message( "-- ZOOMFIX will be applied as Gstreamer version is < 1.20" ) 32 | add_definitions( -DZOOM_WINDOW_NAME_FIX ) 33 | endif() 34 | endif() 35 | 36 | pkg_check_modules(GST REQUIRED gstreamer-1.0>=1.4 37 | gstreamer-sdp-1.0>=1.4 38 | gstreamer-video-1.0>=1.4 39 | gstreamer-app-1.0>=1.4 40 | ) 41 | 42 | add_library( renderers 43 | STATIC 44 | audio_renderer.c 45 | video_renderer.c ) 46 | 47 | target_link_libraries ( renderers PUBLIC airplay ) 48 | 49 | # hacks to fix cmake confusion due to links in path with macOS FrameWorks 50 | 51 | if( GST_INCLUDE_DIRS MATCHES "/Library/FrameWorks/GStreamer.framework/include" ) 52 | set( GST_INCLUDE_DIRS "/Library/FrameWorks/GStreamer.framework/Headers") 53 | message( STATUS "GST_INCLUDE_DIRS" ${GST_INCLUDE_DIRS} ) 54 | # fix to use -DGST_MACOS for "Official" GStreamer >= 1.22 packages 55 | pkg_check_modules ( GST122 gstreamer-1.0>=1.22 ) 56 | if ( GST122_FOUND ) 57 | set( GST_MACOS "1" CACHE STRING "define GST_MACOS in uxplay.cpp" ) 58 | endif() 59 | endif() 60 | 61 | # set GST_MACOS for all Apple when GStreamer >= 1.24 62 | if ( APPLE AND NOT GST_MACOS ) 63 | pkg_check_modules ( GST124 gstreamer-1.0>=1.24 ) 64 | if ( GST124_FOUND ) 65 | set( GST_MACOS "1" CACHE STRING "define GST_MACOS in uxplay.cpp" ) 66 | endif() 67 | endif() 68 | 69 | target_include_directories ( renderers PUBLIC ${GST_INCLUDE_DIRS} ) 70 | 71 | if( GST_LIBRARY_DIRS MATCHES "/Library/FrameWorks/GStreamer.framework/lib" ) 72 | set( GST_LIBRARY_DIRS "/Library/FrameWorks/GStreamer.framework/Libraries") 73 | message( STATUS "GST_LIBRARY_DIRS" ${GST_LIBRARY_DIRS} ) 74 | target_link_libraries( renderers PUBLIC ${GST_LIBRARIES} ) 75 | if( CMAKE_VERSION VERSION_LESS "3.13" ) 76 | message( FATAL_ERROR "This macOS build needs cmake >= 3.13" ) 77 | endif() 78 | target_link_directories ( renderers PUBLIC ${GST_LIBRARY_DIRS} ) 79 | elseif( CMAKE_VERSION VERSION_LESS "3.12" ) 80 | target_link_libraries ( renderers PUBLIC ${GST_LIBRARIES} ) 81 | else() 82 | target_link_libraries( renderers PUBLIC ${GST_LINK_LIBRARIES} ) 83 | endif() 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /renderers/audio_renderer.c: -------------------------------------------------------------------------------- 1 | /** 2 | * RPiPlay - An open-source AirPlay mirroring server for Raspberry Pi 3 | * Copyright (C) 2019 Florian Draschbacher 4 | * Modified for: 5 | * UxPlay - An open-source AirPlay mirroring server 6 | * Copyright (C) 2021-23 F. Duncanh 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software Foundation, 20 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include "audio_renderer.h" 27 | #define SECOND_IN_NSECS 1000000000UL 28 | 29 | #define NFORMATS 2 /* set to 4 to enable AAC_LD and PCM: allowed, but never seen in real-world use */ 30 | 31 | static GstClockTime gst_audio_pipeline_base_time = GST_CLOCK_TIME_NONE; 32 | static logger_t *logger = NULL; 33 | const char * format[NFORMATS]; 34 | 35 | static const gchar *avdec_aac = "avdec_aac"; 36 | static const gchar *avdec_alac = "avdec_alac"; 37 | static gboolean aac = FALSE; 38 | static gboolean alac = FALSE; 39 | static gboolean render_audio = FALSE; 40 | static gboolean async = FALSE; 41 | static gboolean vsync = FALSE; 42 | static gboolean sync = FALSE; 43 | 44 | typedef struct audio_renderer_s { 45 | GstElement *appsrc; 46 | GstElement *pipeline; 47 | GstElement *volume; 48 | unsigned char ct; 49 | } audio_renderer_t ; 50 | static audio_renderer_t *renderer_type[NFORMATS]; 51 | static audio_renderer_t *renderer = NULL; 52 | 53 | /* GStreamer Caps strings for Airplay-defined audio compression types (ct) */ 54 | 55 | /* ct = 1; linear PCM (uncompressed): 44100/16/2, S16LE */ 56 | static const char lpcm_caps[]="audio/x-raw,rate=(int)44100,channels=(int)2,format=S16LE,layout=interleaved"; 57 | 58 | /* ct = 2; codec_data is ALAC magic cookie: 44100/16/2 spf = 352 */ 59 | static const char alac_caps[] = "audio/x-alac,mpegversion=(int)4,channnels=(int)2,rate=(int)44100,stream-format=raw,codec_data=(buffer)" 60 | "00000024""616c6163""00000000""00000160""0010280a""0e0200ff""00000000""00000000""0000ac44"; 61 | 62 | /* ct = 4; codec_data from MPEG v4 ISO 14996-3 Section 1.6.2.1: AAC-LC 44100/2 spf = 1024 */ 63 | static const char aac_lc_caps[] ="audio/mpeg,mpegversion=(int)4,channnels=(int)2,rate=(int)44100,stream-format=raw,codec_data=(buffer)1210"; 64 | 65 | /* ct = 8; codec_data from MPEG v4 ISO 14996-3 Section 1.6.2.1: AAC_ELD 44100/2 spf = 480 */ 66 | static const char aac_eld_caps[] ="audio/mpeg,mpegversion=(int)4,channnels=(int)2,rate=(int)44100,stream-format=raw,codec_data=(buffer)f8e85000"; 67 | 68 | static gboolean check_plugins (void) 69 | { 70 | gboolean ret; 71 | GstRegistry *registry; 72 | const gchar *needed[] = { "app", "libav", "playback", "autodetect", "videoparsersbad", NULL}; 73 | const gchar *gst[] = {"plugins-base", "libav", "plugins-base", "plugins-good", "plugins-bad", NULL}; 74 | registry = gst_registry_get (); 75 | ret = TRUE; 76 | for (int i = 0; i < g_strv_length ((gchar **) needed); i++) { 77 | GstPlugin *plugin; 78 | plugin = gst_registry_find_plugin (registry, needed[i]); 79 | if (!plugin) { 80 | g_print ("Required gstreamer plugin '%s' not found\n" 81 | "Missing plugin is contained in '[GStreamer 1.x]-%s'\n",needed[i], gst[i]); 82 | ret = FALSE; 83 | continue; 84 | } 85 | gst_object_unref (plugin); 86 | plugin = NULL; 87 | } 88 | if (ret == FALSE) { 89 | g_print ("\nif the plugin is installed, but not found, your gstreamer registry may have been corrupted.\n" 90 | "to rebuild it when gstreamer next starts, clear your gstreamer cache with:\n" 91 | "\"rm -rf ~/.cache/gstreamer-1.0\"\n\n"); 92 | } 93 | return ret; 94 | } 95 | 96 | static gboolean check_plugin_feature (const gchar *needed_feature) 97 | { 98 | gboolean ret; 99 | GstPluginFeature *plugin_feature; 100 | GstRegistry *registry = gst_registry_get (); 101 | ret = TRUE; 102 | 103 | plugin_feature = gst_registry_find_feature (registry, needed_feature, GST_TYPE_ELEMENT_FACTORY); 104 | if (!plugin_feature) { 105 | g_print ("Required gstreamer libav plugin feature '%s' not found:\n\n" 106 | "This may be missing because the FFmpeg package used by GStreamer-1.x-libav is incomplete.\n" 107 | "(Some distributions provide an incomplete FFmpeg due to License or Patent issues:\n" 108 | "in such cases a complete version for that distribution is usually made available elsewhere)\n", 109 | needed_feature); 110 | ret = FALSE; 111 | } else { 112 | gst_object_unref (plugin_feature); 113 | plugin_feature = NULL; 114 | } 115 | if (ret == FALSE) { 116 | g_print ("\nif the plugin feature is installed, but not found, your gstreamer registry may have been corrupted.\n" 117 | "to rebuild it when gstreamer next starts, clear your gstreamer cache with:\n" 118 | "\"rm -rf ~/.cache/gstreamer-1.0\"\n\n"); 119 | } 120 | return ret; 121 | } 122 | 123 | bool gstreamer_init(){ 124 | gst_init(NULL,NULL); 125 | return (bool) check_plugins (); 126 | } 127 | 128 | void audio_renderer_init(logger_t *render_logger, const char* audiosink, const bool* audio_sync, const bool* video_sync) { 129 | GError *error = NULL; 130 | GstCaps *caps = NULL; 131 | GstClock *clock = gst_system_clock_obtain(); 132 | g_object_set(clock, "clock-type", GST_CLOCK_TYPE_REALTIME, NULL); 133 | 134 | logger = render_logger; 135 | 136 | aac = check_plugin_feature (avdec_aac); 137 | alac = check_plugin_feature (avdec_alac); 138 | 139 | for (int i = 0; i < NFORMATS ; i++) { 140 | renderer_type[i] = (audio_renderer_t *) calloc(1,sizeof(audio_renderer_t)); 141 | g_assert(renderer_type[i]); 142 | GString *launch = g_string_new("appsrc name=audio_source ! "); 143 | g_string_append(launch, "queue ! "); 144 | switch (i) { 145 | case 0: /* AAC-ELD */ 146 | case 2: /* AAC-LC */ 147 | if (aac) g_string_append(launch, "avdec_aac ! "); 148 | break; 149 | case 1: /* ALAC */ 150 | if (alac) g_string_append(launch, "avdec_alac ! "); 151 | break; 152 | case 3: /*PCM*/ 153 | break; 154 | default: 155 | break; 156 | } 157 | g_string_append (launch, "audioconvert ! "); 158 | g_string_append (launch, "audioresample ! "); /* wasapisink must resample from 44.1 kHz to 48 kHz */ 159 | g_string_append (launch, "volume name=volume ! level ! "); 160 | g_string_append (launch, audiosink); 161 | switch(i) { 162 | case 1: /*ALAC*/ 163 | if (*audio_sync) { 164 | g_string_append (launch, " sync=true"); 165 | async = TRUE; 166 | } else { 167 | g_string_append (launch, " sync=false"); 168 | async = FALSE; 169 | } 170 | break; 171 | default: 172 | if (*video_sync) { 173 | g_string_append (launch, " sync=true"); 174 | vsync = TRUE; 175 | } else { 176 | g_string_append (launch, " sync=false"); 177 | vsync = FALSE; 178 | } 179 | break; 180 | } 181 | renderer_type[i]->pipeline = gst_parse_launch(launch->str, &error); 182 | if (error) { 183 | g_error ("gst_parse_launch error (audio %d):\n %s\n", i+1, error->message); 184 | g_clear_error (&error); 185 | } 186 | 187 | g_assert (renderer_type[i]->pipeline); 188 | gst_pipeline_use_clock(GST_PIPELINE_CAST(renderer_type[i]->pipeline), clock); 189 | 190 | renderer_type[i]->appsrc = gst_bin_get_by_name (GST_BIN (renderer_type[i]->pipeline), "audio_source"); 191 | renderer_type[i]->volume = gst_bin_get_by_name (GST_BIN (renderer_type[i]->pipeline), "volume"); 192 | switch (i) { 193 | case 0: 194 | caps = gst_caps_from_string(aac_eld_caps); 195 | renderer_type[i]->ct = 8; 196 | format[i] = "AAC-ELD 44100/2"; 197 | break; 198 | case 1: 199 | caps = gst_caps_from_string(alac_caps); 200 | renderer_type[i]->ct = 2; 201 | format[i] = "ALAC 44100/16/2"; 202 | break; 203 | case 2: 204 | caps = gst_caps_from_string(aac_lc_caps); 205 | renderer_type[i]->ct = 4; 206 | format[i] = "AAC-LC 44100/2"; 207 | break; 208 | case 3: 209 | caps = gst_caps_from_string(lpcm_caps); 210 | renderer_type[i]->ct = 1; 211 | format[i] = "PCM 44100/16/2 S16LE"; 212 | break; 213 | default: 214 | break; 215 | } 216 | logger_log(logger, LOGGER_DEBUG, "Audio format %d: %s",i+1,format[i]); 217 | logger_log(logger, LOGGER_DEBUG, "GStreamer audio pipeline %d: \"%s\"", i+1, launch->str); 218 | g_string_free(launch, TRUE); 219 | g_object_set(renderer_type[i]->appsrc, "caps", caps, "stream-type", 0, "is-live", TRUE, "format", GST_FORMAT_TIME, NULL); 220 | gst_caps_unref(caps); 221 | g_object_unref(clock); 222 | } 223 | } 224 | 225 | void audio_renderer_stop() { 226 | if (renderer) { 227 | gst_app_src_end_of_stream(GST_APP_SRC(renderer->appsrc)); 228 | gst_element_set_state (renderer->pipeline, GST_STATE_NULL); 229 | renderer = NULL; 230 | } 231 | } 232 | 233 | static void get_renderer_type(unsigned char *ct, int *id) { 234 | render_audio = FALSE; 235 | *id = -1; 236 | for (int i = 0; i < NFORMATS; i++) { 237 | if (renderer_type[i]->ct == *ct) { 238 | *id = i; 239 | break; 240 | } 241 | } 242 | switch (*id) { 243 | case 2: 244 | case 0: 245 | if (aac) { 246 | render_audio = TRUE; 247 | } else { 248 | logger_log(logger, LOGGER_INFO, "*** GStreamer libav plugin feature avdec_aac is missing, cannot decode AAC audio"); 249 | } 250 | sync = vsync; 251 | break; 252 | case 1: 253 | if (alac) { 254 | render_audio = TRUE; 255 | } else { 256 | logger_log(logger, LOGGER_INFO, "*** GStreamer libav plugin feature avdec_alac is missing, cannot decode ALAC audio"); 257 | } 258 | sync = async; 259 | break; 260 | case 3: 261 | render_audio = TRUE; 262 | sync = FALSE; 263 | break; 264 | default: 265 | break; 266 | } 267 | } 268 | 269 | void audio_renderer_start(unsigned char *ct) { 270 | int id = -1; 271 | get_renderer_type(ct, &id); 272 | if (id >= 0 && renderer) { 273 | if(*ct != renderer->ct) { 274 | gst_app_src_end_of_stream(GST_APP_SRC(renderer->appsrc)); 275 | gst_element_set_state (renderer->pipeline, GST_STATE_NULL); 276 | logger_log(logger, LOGGER_INFO, "changed audio connection, format %s", format[id]); 277 | renderer = renderer_type[id]; 278 | gst_element_set_state (renderer->pipeline, GST_STATE_PLAYING); 279 | gst_audio_pipeline_base_time = gst_element_get_base_time(renderer->appsrc); 280 | } 281 | } else if (id >= 0) { 282 | logger_log(logger, LOGGER_INFO, "start audio connection, format %s", format[id]); 283 | renderer = renderer_type[id]; 284 | gst_element_set_state (renderer->pipeline, GST_STATE_PLAYING); 285 | gst_audio_pipeline_base_time = gst_element_get_base_time(renderer->appsrc); 286 | } else { 287 | logger_log(logger, LOGGER_ERR, "unknown audio compression type ct = %d", *ct); 288 | } 289 | } 290 | 291 | void audio_renderer_render_buffer(unsigned char* data, int *data_len, unsigned short *seqnum, uint64_t *ntp_time) { 292 | GstBuffer *buffer; 293 | bool valid; 294 | 295 | if (!render_audio) return; /* do nothing unless render_audio == TRUE */ 296 | 297 | GstClockTime pts = (GstClockTime) *ntp_time ; /* now in nsecs */ 298 | //GstClockTimeDiff latency = GST_CLOCK_DIFF(gst_element_get_current_clock_time (renderer->appsrc), pts); 299 | if (sync) { 300 | if (pts >= gst_audio_pipeline_base_time) { 301 | pts -= gst_audio_pipeline_base_time; 302 | } else { 303 | logger_log(logger, LOGGER_ERR, "*** invalid ntp_time < gst_audio_pipeline_base_time\n%8.6f ntp_time\n%8.6f base_time", 304 | ((double) *ntp_time) / SECOND_IN_NSECS, ((double) gst_audio_pipeline_base_time) / SECOND_IN_NSECS); 305 | return; 306 | } 307 | } 308 | if (data_len == 0 || renderer == NULL) return; 309 | 310 | /* all audio received seems to be either ct = 8 (AAC_ELD 44100/2 spf 460 ) AirPlay Mirror protocol * 311 | * or ct = 2 (ALAC 44100/16/2 spf 352) AirPlay protocol. * 312 | * first byte data[0] of ALAC frame is 0x20, * 313 | * first byte of AAC_ELD is 0x8c, 0x8d or 0x8e: 0x100011(00,01,10) in modern devices * 314 | * but is 0x80, 0x81 or 0x82: 0x100000(00,01,10) in ios9, ios10 devices * 315 | * first byte of AAC_LC should be 0xff (ADTS) (but has never been seen). */ 316 | 317 | buffer = gst_buffer_new_allocate(NULL, *data_len, NULL); 318 | g_assert(buffer != NULL); 319 | //g_print("audio latency %8.6f\n", (double) latency / SECOND_IN_NSECS); 320 | if (sync) { 321 | GST_BUFFER_PTS(buffer) = pts; 322 | } 323 | gst_buffer_fill(buffer, 0, data, *data_len); 324 | switch (renderer->ct){ 325 | case 8: /*AAC-ELD*/ 326 | switch (data[0]){ 327 | case 0x8c: 328 | case 0x8d: 329 | case 0x8e: 330 | case 0x80: 331 | case 0x81: 332 | case 0x82: 333 | valid = true; 334 | break; 335 | default: 336 | valid = false; 337 | break; 338 | } 339 | break; 340 | case 2: /*ALAC*/ 341 | valid = (data[0] == 0x20); 342 | break; 343 | case 4: /*AAC_LC */ 344 | valid = (data[0] == 0xff ); 345 | break; 346 | default: 347 | valid = true; 348 | break; 349 | } 350 | if (valid) { 351 | gst_app_src_push_buffer(GST_APP_SRC(renderer->appsrc), buffer); 352 | } else { 353 | logger_log(logger, LOGGER_ERR, "*** ERROR invalid audio frame (compression_type %d) skipped ", renderer->ct); 354 | logger_log(logger, LOGGER_ERR, "*** first byte of invalid frame was 0x%2.2x ", (unsigned int) data[0]); 355 | } 356 | } 357 | 358 | void audio_renderer_set_volume(double volume) { 359 | volume = (volume > 10.0) ? 10.0 : volume; 360 | volume = (volume < 0.0) ? 0.0 : volume; 361 | g_object_set(renderer->volume, "volume", volume, NULL); 362 | } 363 | 364 | void audio_renderer_flush() { 365 | } 366 | 367 | void audio_renderer_destroy() { 368 | audio_renderer_stop(); 369 | for (int i = 0; i < NFORMATS ; i++ ) { 370 | gst_object_unref (renderer_type[i]->volume); 371 | renderer_type[i]->volume = NULL; 372 | gst_object_unref (renderer_type[i]->appsrc); 373 | renderer_type[i]->appsrc = NULL; 374 | gst_object_unref (renderer_type[i]->pipeline); 375 | renderer_type[i]->pipeline = NULL; 376 | free(renderer_type[i]); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /renderers/audio_renderer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * RPiPlay - An open-source AirPlay mirroring server for Raspberry Pi 3 | * Copyright (C) 2019 Florian Draschbacher 4 | * Modified for: 5 | * UxPlay - An open-source AirPlay mirroring server 6 | * Copyright (C) 2021-23 F. Duncanh 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software Foundation, 20 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 21 | */ 22 | 23 | #ifndef AUDIO_RENDERER_H 24 | #define AUDIO_RENDERER_H 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | #include 31 | #include 32 | #include 33 | #include "../lib/logger.h" 34 | 35 | bool gstreamer_init(); 36 | void audio_renderer_init(logger_t *logger, const char* audiosink, const bool *audio_sync, const bool *video_sync); 37 | void audio_renderer_start(unsigned char* compression_type); 38 | void audio_renderer_stop(); 39 | void audio_renderer_render_buffer(unsigned char* data, int *data_len, unsigned short *seqnum, uint64_t *ntp_time); 40 | void audio_renderer_set_volume(double volume); 41 | void audio_renderer_flush(); 42 | void audio_renderer_destroy(); 43 | 44 | #ifdef __cplusplus 45 | } 46 | #endif 47 | 48 | #endif //AUDIO_RENDERER_H 49 | -------------------------------------------------------------------------------- /renderers/video_renderer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * RPiPlay - An open-source AirPlay mirroring server for Raspberry Pi 3 | * Copyright (C) 2019 Florian Draschbacher 4 | * Modified for: 5 | * UxPlay - An open-source AirPlay mirroring server 6 | * Copyright (C) 2021-23 F. Duncanh 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software Foundation, 20 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 21 | */ 22 | 23 | /* 24 | * H264 renderer using gstreamer 25 | */ 26 | 27 | #ifndef VIDEO_RENDERER_H 28 | #define VIDEO_RENDERER_H 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | #include 35 | #include 36 | #include 37 | #include "../lib/logger.h" 38 | 39 | typedef enum videoflip_e { 40 | NONE, 41 | LEFT, 42 | RIGHT, 43 | INVERT, 44 | VFLIP, 45 | HFLIP, 46 | } videoflip_t; 47 | 48 | typedef struct video_renderer_s video_renderer_t; 49 | 50 | void video_renderer_init (logger_t *logger, const char *server_name, videoflip_t videoflip[2], const char *parser, 51 | const char *decoder, const char *converter, const char *videosink, const char *videosink_options, 52 | bool initial_fullscreen, bool video_sync, bool h265_support, const char *uri); 53 | void video_renderer_start (); 54 | void video_renderer_stop (); 55 | void video_renderer_pause (); 56 | void video_renderer_seek(float position); 57 | void video_renderer_resume (); 58 | bool video_renderer_is_paused(); 59 | void video_renderer_render_buffer (unsigned char* data, int *data_len, int *nal_count, uint64_t *ntp_time); 60 | void video_renderer_flush (); 61 | unsigned int video_renderer_listen(void *loop, int id); 62 | void video_renderer_destroy (); 63 | void video_renderer_size(float *width_source, float *height_source, float *width, float *height); 64 | bool waiting_for_x11_window(); 65 | bool video_get_playback_info(double *duration, double *position, float *rate); 66 | void video_renderer_choose_codec(bool is_h265); 67 | unsigned int video_renderer_listen(void *loop, int id); 68 | unsigned int video_reset_callback(void *loop); 69 | #ifdef __cplusplus 70 | } 71 | #endif 72 | 73 | #endif //VIDEO_RENDERER_H 74 | 75 | -------------------------------------------------------------------------------- /renderers/x_display_fix.h: -------------------------------------------------------------------------------- 1 | /** 2 | * RPiPlay - An open-source AirPlay mirroring server for Raspberry Pi 3 | * Copyright (C) 2019 Florian Draschbacher 4 | * Modified for: 5 | * UxPlay - An open-source AirPlay mirroring server 6 | * Copyright (C) 2021-23 F. Duncanh 7 | * 8 | * 9 | * This program is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program; if not, write to the Free Software Foundation, 21 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | 24 | /* based on code from David Ventura https://github.com/DavidVentura/UxPlay */ 25 | 26 | /* This file should be only included from video_renderer.c as it defines static 27 | * functions and depends on video_renderer internals */ 28 | 29 | #ifndef X_DISPLAY_FIX_H 30 | #define X_DISPLAY_FIX_H 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | struct X11_Window_s { 42 | Display * display; 43 | Window window; 44 | } typedef X11_Window_t; 45 | 46 | static void get_X11_Display(X11_Window_t * X11) { 47 | X11->display = XOpenDisplay(NULL); 48 | X11->window = (Window) NULL; 49 | } 50 | 51 | static Window enum_windows(const char * str, Display * display, Window window, int depth) { 52 | int i; 53 | XTextProperty text; 54 | XGetWMName(display, window, &text); 55 | char* name = NULL; 56 | XFetchName(display, window, &name); 57 | if (name != 0 && strcmp(str, name) == 0) { 58 | return window; 59 | } 60 | Window _root, parent; 61 | Window* children = NULL; 62 | unsigned int n; 63 | XQueryTree(display, window, &_root, &parent, &children, &n); 64 | if (children != NULL) { 65 | for (i = 0; i < n; i++) { 66 | Window w = enum_windows(str, display, children[i], depth + 1); 67 | if (w) return w; 68 | } 69 | XFree(children); 70 | } 71 | return (Window) NULL; 72 | } 73 | 74 | int X11_error_catcher( Display *disp, XErrorEvent *xe ) { 75 | // do nothing 76 | return 0; 77 | } 78 | 79 | static void get_x_window(X11_Window_t * X11, const char * name) { 80 | Window root = XDefaultRootWindow(X11->display); 81 | XSetErrorHandler(X11_error_catcher); 82 | X11->window = enum_windows(name, X11->display, root, 0); 83 | XSetErrorHandler(NULL); 84 | #ifdef ZOOM_WINDOW_NAME_FIX 85 | if (X11->window) { 86 | Atom _NET_WM_NAME = XInternAtom(X11->display, "_NET_WM_NAME", 0); 87 | Atom UTF8_STRING = XInternAtom(X11->display, "UTF8_STRING", 0); 88 | XChangeProperty(X11->display, X11->window, _NET_WM_NAME, UTF8_STRING, 89 | 8, 0, (const unsigned char *) name, strlen(name)); 90 | XSync(X11->display, False); 91 | } 92 | #endif 93 | } 94 | 95 | static void set_fullscreen(X11_Window_t * X11, bool * fullscreen) { 96 | XClientMessageEvent msg = { 97 | .type = ClientMessage, 98 | .display = X11->display, 99 | .window = X11->window, 100 | .message_type = XInternAtom(X11->display, "_NET_WM_STATE", True), 101 | .format = 32, 102 | .data = { .l = { 103 | *fullscreen, 104 | XInternAtom(X11->display, "_NET_WM_STATE_FULLSCREEN", True), 105 | None, 106 | 0, 107 | 1 108 | }} 109 | }; 110 | XSendEvent(X11->display, XRootWindow(X11->display, XDefaultScreen(X11->display)), 111 | False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*) &msg); 112 | XSync(X11->display, False); 113 | } 114 | 115 | #ifdef __cplusplus 116 | } 117 | #endif 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /uxplay.1: -------------------------------------------------------------------------------- 1 | .TH UXPLAY "1" "December 2024" "1.71" "User Commands" 2 | .SH NAME 3 | uxplay \- start AirPlay server 4 | .SH SYNOPSIS 5 | .B uxplay 6 | [\fI\,-n name\/\fR] [\fI\,-s wxh\/\fR] [\fI\,-p \/\fR[\fI\,n\/\fR]] [more \fI OPTIONS \/\fR ...] 7 | .SH DESCRIPTION 8 | UxPlay 1.71: An open\-source AirPlay mirroring (+ audio streaming) server: 9 | .SH OPTIONS 10 | .TP 11 | .B 12 | \fB\-n\fR name Specify the network name of the AirPlay server 13 | .TP 14 | \fB\-nh\fR Do \fBNOT\fR append "@\fIhostname\fR" at end of AirPlay server name 15 | .TP 16 | \fB\-h265\fR Support h265 (4K) video (with h265 versions of h264 plugins) 17 | .TP 18 | \fB\-hls\fR Support HTTP Live Streaming (currently YouTube video only) 19 | .TP 20 | \fB\-pin\fI[xxxx]\fRUse a 4-digit pin code to control client access (default: no) 21 | .IP 22 | without option, pin is random: optionally use fixed pin xxxx. 23 | .TP 24 | \fB\-reg\fI [fn]\fR Keep a register in $HOME/.uxplay.register to verify returning 25 | .IP 26 | client pin-registration; (option: use file "fn" for this) 27 | .TP 28 | \fB\-vsync\fI[x]\fR Mirror mode: sync audio to video using timestamps (default) 29 | .IP 30 | \fIx\fR is optional audio delay: millisecs, decimal, can be neg. 31 | .TP 32 | \fB\-vsync\fR no Switch off audio/(server)video timestamp synchronization. 33 | .TP 34 | \fB\-async\fR[\fIx\fR] Audio-Only mode: sync audio to client video (default: no). 35 | .TP 36 | \fB\-async\fR no Switch off audio/(client)video timestamp synchronization. 37 | .TP 38 | \fB\-db\fI l[:h]\fR Set minumum volume attenuation to l dB (decibels, negative); 39 | .IP 40 | optional: set maximum to h dB (+ or -); default -30.0:0.0 41 | .PP 42 | .TP 43 | \fB\-taper\fR Use a "tapered" AirPlay volume-control profile. 44 | .TP 45 | \fB\-s\fR wxh[@r]Request to client for video display resolution [refresh_rate] 46 | .IP 47 | default 1920x1080[@60] (or 3840x2160[@60] with -h265 option). 48 | .PP 49 | .TP 50 | \fB\-o\fR Set display "overscanned" mode on (not usually needed) 51 | .TP 52 | \fB-fs\fR Full-screen (only works with X11, Wayland, VAAPI, D3D11) 53 | .TP 54 | \fB\-p\fR Use legacy ports UDP 6000:6001:7011 TCP 7000:7001:7100 55 | .TP 56 | \fB\-p\fR n Use TCP and UDP ports n,n+1,n+2. range 1024\-65535 57 | .IP 58 | use "\-p n1,n2,n3" to set each port, "n1,n2" for n3 = n2+1 59 | .IP 60 | "\-p tcp n" or "\-p udp n" sets TCP or UDP ports separately. 61 | .PP 62 | .TP 63 | \fB\-avdec\fR Force software h264 video decoding with libav decoder. 64 | .TP 65 | \fB\-vp\fI prs \fR Choose GStreamer h264 parser; default "h264parse" 66 | .TP 67 | \fB\-vd\fI dec \fR Choose GStreamer h264 decoder; default "decodebin" 68 | .IP 69 | choices: (software) avdec_h264; (hardware) v4l2h264dec, 70 | .IP 71 | nvdec, nvh264dec, vaapih264dec, vtdec, ... 72 | .TP 73 | \fB\-vc\fI cnv \fR Choose GStreamer videoconverter; default "videoconvert" 74 | .IP 75 | another choice when using v4l2h264dec: v4l2convert. 76 | .TP 77 | \fB\-vs\fI sink\fR Choose the GStreamer videosink; default "autovideosink" 78 | .IP 79 | choices: ximagesink,xvimagesink,vaapisink,glimagesink, 80 | .IP 81 | gtksink,waylandsink,osxvideosink,kmssink,d3d11videosink,... 82 | .PP 83 | .TP 84 | \fB\-vs\fR 0 Streamed audio only, with no video display window. 85 | .TP 86 | \fB\-v4l2\fR Use Video4Linux2 for GPU hardware h264 video decoding. 87 | .TP 88 | \fB\-bt709\fR Sometimes needed for Raspberry Pi models using Video4Linux2. 89 | .TP 90 | \fB\-srgb\fR Display "Full range" [0-255] color, not "Limited Range"[16-235] 91 | .IP 92 | This is a workaround for a GStreamer problem, until it is fixed. 93 | .PP 94 | \fB\-srgb\fR no Disable srgb option (use when enabled by default: Linux, *BSD) 95 | .TP 96 | \fB\-as\fI sink\fR Choose the GStreamer audiosink; default "autoaudiosink" 97 | .IP 98 | choices:pulsesink,alsasink,pipewiresink,osssink,oss4sink, 99 | .IP 100 | jackaudiosink,osxaudiosink,wasapisink,directsoundsink,.. 101 | .PP 102 | .TP 103 | \fB\-as\fR 0 (or \fB\-a\fR) Turn audio off, streamed video only. 104 | .TP 105 | \fB\-al\fR x Audio latency in seconds (default 0.25) reported to client. 106 | .TP 107 | \fB\-ca\fI fn \fR In Airplay Audio (ALAC) mode, write cover-art to file fn. 108 | .TP 109 | \fB\-reset\fR n Reset after 3n seconds client silence (default 5, 0=never). 110 | .TP 111 | \fB\-nofreeze\fR Do NOT leave frozen screen in place after reset. 112 | .TP 113 | \fB\-nc\fR Do NOT close video window when client stops mirroring 114 | .TP 115 | \fB\-nohold\fR Drop current connection when new client connects. 116 | .TP 117 | \fB\-restrict\fR Restrict clients to those specified by "-allow deviceID". 118 | .IP 119 | Uxplay displays deviceID when a client attempts to connect. 120 | .IP 121 | Use "-restrict no" for no client restrictions (default). 122 | .PP 123 | \fB\-allow\fR id Permit deviceID = id to connect if restrictions are imposed. 124 | .TP 125 | \fB\-block\fR id Always block connections from deviceID = id. 126 | .TP 127 | \fB\-FPSdata\fR Show video-streaming performance reports sent by client. 128 | .TP 129 | \fB\-fps\fR n Set maximum allowed streaming framerate, default 30 130 | .TP 131 | \fB\-f\fR {H|V|I}Horizontal|Vertical flip, or both=Inversion=rotate 180 deg 132 | .TP 133 | \fB\-r\fR {R|L} Rotate 90 degrees Right (cw) or Left (ccw) 134 | .TP 135 | \fB\-m\fI [mac]\fR Set MAC address (also Device ID); use for concurrent UxPlays 136 | .IP 137 | if mac xx:xx:xx:xx:xx:xx is not given, a random MAC is used. 138 | .PP 139 | .TP 140 | \fB\-key\fI [fn]\fR Store private key in $HOME/.uxplay.pem (or in file "fn") 141 | .PP 142 | .TP 143 | \fB\-dacp\fI [fn]\fRExport client DACP information to file $HOME/.uxplay.dacp 144 | .IP 145 | (option to use file "fn" instead); used for client remote. 146 | .PP 147 | .TP 148 | \fB\-vdmp\fR [n] Dump h264 video output to "fn.h264"; fn="videodump", change 149 | .IP 150 | with "-vdmp [n] filename". If [n] is given, file fn.x.h264 151 | .IP 152 | x=1,2,.. opens whenever a new SPS/PPS NAL arrives, and <=n 153 | .IP 154 | NAL units are dumped. 155 | .PP 156 | .TP 157 | \fB\-admp\fR [n] Dump audio output to "fn.x.fmt", fmt ={aac, alac, aud}, x 158 | .IP 159 | =1,2,..; fn="audiodump"; change with "-admp [n] filename". 160 | .IP 161 | x increases when audio format changes. If n is given, <= n 162 | .IP 163 | audio packets are dumped. "aud"= unknown format. 164 | .PP 165 | .TP 166 | \fB\-d\fR Enable debug logging 167 | .TP 168 | \fB\-v\fR Displays version information 169 | .TP 170 | \fB\-h\fR Displays help information 171 | .SH 172 | FILES 173 | Options in one of $UXPLAYRC, or ~/.uxplayrc, or ~/.config/uxplayrc 174 | .TP 175 | are applied first (command-line options may modify them). Format: 176 | .TP 177 | one option per line,\fI no\fR initial "-"; lines beginning with "#" ignored. 178 | .SH 179 | AUTHORS 180 | .TP 181 | Various, see website or distribution. 182 | .SH 183 | COPYRIGHT 184 | .TP 185 | Various, see website or distribution. License: GPL v3+: 186 | .TP 187 | GNU GPL version 3 or later. (some parts LGPL v.2.1+ or MIT). 188 | .SH 189 | SEE ALSO 190 | .TP 191 | Website: 192 | -------------------------------------------------------------------------------- /uxplay.spec: -------------------------------------------------------------------------------- 1 | Name: uxplay 2 | Version: 1.71.1 3 | Release: 1%{?dist} 4 | 5 | %global gittag v%{version} 6 | 7 | Summary: AirPlay-Mirror and AirPlay-Audio server 8 | License: GPLv3+ 9 | URL: https://github.com/FDH2/UxPlay 10 | Source0: https://github.com/FDH2/UxPlay/archive/%{gittag}/%{name}-%{version}.tar.gz 11 | 12 | Packager: UxPlay maintainer 13 | 14 | BuildRequires: cmake >= 3.5 15 | BuildRequires: make 16 | BuildRequires: gcc 17 | BuildRequires: gcc-c++ 18 | Requires: avahi 19 | 20 | #RedHat and clones 21 | %if %{defined fedora} || %{defined rhel} 22 | BuildRequires: pkgconf 23 | BuildRequires: openssl-devel >= 3.0 24 | BuildRequires: libplist-devel >= 2.0 25 | BuildRequires: avahi-compat-libdns_sd-devel 26 | BuildRequires: gstreamer1-devel 27 | BuildRequires: gstreamer1-plugins-base-devel 28 | Requires: openssl-libs >= 3.0 29 | Requires: libplist >= 2.0 30 | Requires: gstreamer1-plugins-base 31 | Requires: gstreamer1-plugins-good 32 | Requires: gstreamer1-plugins-bad-free 33 | Requires: gstreamer1-libav 34 | %define cmake_builddir redhat-linux-build 35 | %endif 36 | 37 | #SUSE 38 | %if "%{_host_vendor}" == "suse" 39 | BuildRequires: pkg-config 40 | BuildRequires: libopenssl-3-devel 41 | BuildRequires: libplist-2_0-devel 42 | BuildRequires: avahi-compat-mDNSResponder-devel 43 | BuildRequires: gstreamer-devel 44 | BuildRequires: gstreamer-plugins-base-devel 45 | Requires: libopenssl3 46 | Requires: libplist-2_0-3 47 | Requires: gstreamer-plugins-base 48 | Requires: gstreamer-plugins-good 49 | Requires: gstreamer-plugins-bad 50 | Requires: gstreamer-plugins-libav 51 | %endif 52 | 53 | #Mageia, OpenMandriva, PCLinuxOS (Mandriva/Mandrake descendents) 54 | %if "%{_host_vendor}" == "mageia" || %{defined omvver} || "%{_host_vendor}" == "mandriva" 55 | %if "%{_host_vendor}" == "mandriva" 56 | # host_vendor = "mandriva" identifies PCLinuxOS. 57 | # As of 07/2023, PCLinuxOS does not seem to supply openssl >= 3.0. 58 | # Note that UxPlay does not have a "GPL exception" allowing it to be 59 | # distributed in binary form when linked to openssl < 3.0, unless that 60 | # OpenSSL < 3.0 qualifies as a "system library". 61 | BuildRequires: pkgconfig 62 | BuildRequires: %{mklibname openssl-devel} >= 1.1.1 63 | Requires: %{mklibname openssl1.1.0} 64 | %else 65 | BuildRequires: pkgconf 66 | BuildRequires: %{mklibname openssl-devel} >= 3.0 67 | %if %{defined omvver} 68 | Requires: openssl >= 3.0 69 | %else 70 | Requires: %{mklibname openssl3} 71 | %endif 72 | %endif 73 | BuildRequires: %{mklibname plist-devel} >= 2.0 74 | BuildRequires: %{mklibname avahi-compat-libdns_sd-devel} 75 | %if %{defined omvver} 76 | BuildRequires: %{mklibname gstreamer-devel} 77 | BuildRequires: %{mklibname gst-plugins-base1.0-devel} 78 | Requires: %{mklibname plist} >= 2.0 79 | %else 80 | BuildRequires: %{mklibname gstreamer1.0-devel} 81 | BuildRequires: %{mklibname gstreamer-plugins-base1.0-devel} 82 | Requires: %{mklibname plist2.0_3} 83 | %endif 84 | Requires: gstreamer1.0-plugins-base 85 | Requires: gstreamer1.0-plugins-good 86 | Requires: gstreamer1.0-plugins-bad 87 | Requires: gstreamer1.0-libav 88 | %endif 89 | 90 | %description 91 | An AirPlay2 Mirror and AirPlay2 Audio (but not Video) server that provides 92 | screen-mirroring (with audio) of iOS/MacOS clients in a display window on 93 | the server host (which can be shared using a screen-sharing application); 94 | Apple Lossless Audio (ALAC) (e.g.,iTunes) can be streamed from client to 95 | server in non-mirror mode 96 | 97 | %prep 98 | 99 | %autosetup -n UxPlay-%{version} 100 | 101 | # if you are building for a distribution, add cmake option -DNO_MARCH_NATIVE=ON 102 | %cmake -DCMAKE_INSTALL_DOCDIR=%{_docdir}/%{name} 103 | 104 | %build 105 | 106 | %if %{defined cmake_builddir} 107 | cd %{cmake_builddir} 108 | %else 109 | cd build 110 | %endif 111 | 112 | %make_build 113 | 114 | %install 115 | 116 | %if %{defined cmake_builddir} 117 | cd %{cmake_builddir} 118 | %else 119 | cd build 120 | %endif 121 | 122 | %make_install 123 | 124 | %files 125 | %{_bindir}/uxplay 126 | %{_mandir}/man1/uxplay.1* 127 | 128 | %doc 129 | %{_docdir}/%{name}/README.txt 130 | %{_docdir}/%{name}/README.html 131 | %{_docdir}/%{name}/README.md 132 | 133 | %license 134 | %{_docdir}/%{name}/LICENSE 135 | %{_docdir}/%{name}/llhttp/LICENSE-MIT 136 | 137 | %changelog 138 | * Fri Nov 15 2024 UxPlay maintainer 139 | Initial uxplay.spec: tested on Fedora 38, Rocky Linux 9.2, OpenSUSE 140 | Leap 15.5, Mageia 9, OpenMandriva ROME, PCLinuxOS 141 | - 142 | --------------------------------------------------------------------------------