Create WebSocket client transport.
702 {
703 if (!url) {
704 SET_ERRNO(ERROR_INVALID_PARAM, "url is required");
705 return NULL;
706 }
707
708
709
710 const char *protocol_end = strstr(url, "://");
711 if (!protocol_end) {
712 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid WebSocket URL format (missing ://)");
713 return NULL;
714 }
715
716 bool use_ssl = (strncmp(url, "wss://", 6) == 0);
717 const char *host_start = protocol_end + 3;
718
719
720 const char *port_start = strchr(host_start, ':');
721 const char *path_start = strchr(host_start, '/');
722
723 char host[256] = {0};
724 int port = use_ssl ? 443 : 27226;
725 char path[256] = "/";
726
727 if (port_start && (!path_start || port_start < path_start)) {
728
729 size_t host_len = port_start - host_start;
730 if (host_len >= sizeof(host)) {
731 SET_ERRNO(ERROR_INVALID_PARAM, "Host name too long");
732 return NULL;
733 }
734 memcpy(host, host_start, host_len);
735 host[host_len] = '\0';
736
737
738 port = atoi(port_start + 1);
739 if (port <= 0 || port > 65535) {
740 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid port number");
741 return NULL;
742 }
743 } else {
744
745 size_t host_len = path_start ? (size_t)(path_start - host_start) : strlen(host_start);
746 if (host_len >= sizeof(host)) {
747 SET_ERRNO(ERROR_INVALID_PARAM, "Host name too long");
748 return NULL;
749 }
750 memcpy(host, host_start, host_len);
751 host[host_len] = '\0';
752 }
753
754
755 if (path_start) {
756 SAFE_STRNCPY(path, path_start, sizeof(path) - 1);
757 path[sizeof(path) - 1] = '\0';
758 }
759
760 log_info("Connecting to WebSocket: %s (host=%s, port=%d, path=%s, ssl=%d)", url, host, port, path, use_ssl);
761
762
763 acip_transport_t *transport = SAFE_MALLOC(sizeof(acip_transport_t), acip_transport_t *);
764 if (!transport) {
765 SET_ERRNO(ERROR_MEMORY, "Failed to allocate WebSocket transport");
766 return NULL;
767 }
768
769
770 websocket_transport_data_t *ws_data =
771 SAFE_CALLOC(1, sizeof(websocket_transport_data_t), websocket_transport_data_t *);
772 if (!ws_data) {
773 SAFE_FREE(transport);
774 SET_ERRNO(ERROR_MEMORY, "Failed to allocate WebSocket transport data");
775 return NULL;
776 }
777
778
780 if (!ws_data->recv_queue) {
781 SAFE_FREE(ws_data);
782 SAFE_FREE(transport);
783 SET_ERRNO(ERROR_MEMORY, "Failed to create receive queue");
784 return NULL;
785 }
786
787
790 SAFE_FREE(ws_data);
791 SAFE_FREE(transport);
792 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize recv mutex");
793 return NULL;
794 }
795
796 if (cond_init(&ws_data->recv_cond) != 0) {
799 SAFE_FREE(ws_data);
800 SAFE_FREE(transport);
801 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize recv condition variable");
802 return NULL;
803 }
804
806 cond_destroy(&ws_data->recv_cond);
809 SAFE_FREE(ws_data);
810 SAFE_FREE(transport);
811 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize state mutex");
812 return NULL;
813 }
814
815
816 ws_data->send_buffer_capacity = LWS_PRE + 8192;
817 ws_data->send_buffer = SAFE_MALLOC(ws_data->send_buffer_capacity, uint8_t *);
818 if (!ws_data->send_buffer) {
820 cond_destroy(&ws_data->recv_cond);
823 SAFE_FREE(ws_data);
824 SAFE_FREE(transport);
825 SET_ERRNO(ERROR_MEMORY, "Failed to allocate send buffer");
826 return NULL;
827 }
828
829
830
831 static struct lws_protocols client_protocols[] = {
832 {"acip", websocket_callback, 0, 4096, 0, NULL, 0}, {NULL, NULL, 0, 0, 0, NULL, 0}
833 };
834
835 struct lws_context_creation_info info;
836 memset(&info, 0, sizeof(info));
837 info.port = CONTEXT_PORT_NO_LISTEN;
838 info.protocols = client_protocols;
839 info.gid = (gid_t)-1;
840 info.uid = (uid_t)-1;
841 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
842
843 ws_data->context = lws_create_context(&info);
844 if (!ws_data->context) {
845 SAFE_FREE(ws_data->send_buffer);
847 cond_destroy(&ws_data->recv_cond);
850 SAFE_FREE(ws_data);
851 SAFE_FREE(transport);
852 SET_ERRNO(ERROR_NETWORK, "Failed to create libwebsockets context");
853 return NULL;
854 }
855
856
857 log_debug("Initiating WebSocket connection to %s:%d%s", host, port, path);
858 struct lws_client_connect_info connect_info;
859 memset(&connect_info, 0, sizeof(connect_info));
860 connect_info.context = ws_data->context;
861 connect_info.address = host;
862 connect_info.port = port;
863 connect_info.path = path;
864 connect_info.host = host;
865 connect_info.origin = host;
866 connect_info.protocol = "acip";
867 connect_info.ssl_connection = use_ssl ? LCCSCF_USE_SSL : 0;
868 connect_info.userdata = ws_data;
869
870 log_debug("Calling lws_client_connect_via_info...");
871 ws_data->wsi = lws_client_connect_via_info(&connect_info);
872 log_debug("lws_client_connect_via_info returned: %p", (void *)ws_data->wsi);
873 if (!ws_data->wsi) {
874 lws_context_destroy(ws_data->context);
875 SAFE_FREE(ws_data->send_buffer);
877 cond_destroy(&ws_data->recv_cond);
880 SAFE_FREE(ws_data);
881 SAFE_FREE(transport);
882 SET_ERRNO(ERROR_NETWORK, "Failed to connect to WebSocket server");
883 return NULL;
884 }
885
886 ws_data->is_connected = false;
887 ws_data->owns_context = true;
888
889
890 transport->methods = &websocket_methods;
891 transport->crypto_ctx = crypto_ctx;
892 transport->impl_data = ws_data;
893
894
895
896 log_debug("Waiting for WebSocket connection to establish...");
897 int timeout_ms = 5000;
898 int elapsed_ms = 0;
899 while (!ws_data->is_connected && elapsed_ms < timeout_ms) {
900
901 int result = lws_service(ws_data->context, 50);
902 if (result < 0) {
903 log_error("lws_service error during connection: %d", result);
904 lws_context_destroy(ws_data->context);
905 SAFE_FREE(ws_data->send_buffer);
907 cond_destroy(&ws_data->recv_cond);
910 SAFE_FREE(ws_data);
911 SAFE_FREE(transport);
912 SET_ERRNO(ERROR_NETWORK, "WebSocket connection failed");
913 return NULL;
914 }
915 elapsed_ms += 50;
916 }
917
918 if (!ws_data->is_connected) {
919 log_error("WebSocket connection timeout after %d ms", elapsed_ms);
920 lws_context_destroy(ws_data->context);
921 SAFE_FREE(ws_data->send_buffer);
923 cond_destroy(&ws_data->recv_cond);
926 SAFE_FREE(ws_data);
927 SAFE_FREE(transport);
928 SET_ERRNO(ERROR_NETWORK, "WebSocket connection timeout");
929 return NULL;
930 }
931
932 log_info("WebSocket connection established (crypto: %s)", crypto_ctx ? "enabled" : "disabled");
933
934
935 ws_data->service_running = true;
937 log_error("Failed to create WebSocket service thread");
938 ws_data->service_running = false;
939 lws_context_destroy(ws_data->context);
940 SAFE_FREE(ws_data->send_buffer);
942 cond_destroy(&ws_data->recv_cond);
945 SAFE_FREE(ws_data);
946 SAFE_FREE(transport);
947 SET_ERRNO(ERROR_INTERNAL, "Failed to create service thread");
948 return NULL;
949 }
950
951 log_debug("WebSocket service thread started for client transport");
952
953 return transport;
954}
ringbuffer_t * ringbuffer_create(size_t element_size, size_t capacity)
void ringbuffer_destroy(ringbuffer_t *rb)
int mutex_init(mutex_t *mutex)
int asciichat_thread_create(asciichat_thread_t *thread, void *(*start_routine)(void *), void *arg)
int mutex_destroy(mutex_t *mutex)
#define WEBSOCKET_RECV_QUEUE_SIZE
Maximum receive queue size (messages buffered before recv())