File: ws-test-helper.c

package info (click to toggle)
libsoup3 3.6.5-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 7,304 kB
  • sloc: ansic: 61,963; python: 202; xml: 97; sh: 84; makefile: 33; javascript: 5
file content (163 lines) | stat: -rw-r--r-- 5,579 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 2024 Axis Communications AB, SWEDEN.
 */
/*

To use this WebSocket test helper with valgrind, follow these steps.

1. Start a ws server in a separate terminal

    docker run -it --rm \
            -p 9001:9001 \
            crossbario/autobahn-testsuite \
            wstest --mode echoserver --wsuri=ws://127.0.0.1:9001

2. Build this helper

    meson setup _build
    meson compile -C _build tests/ws-test-helper

3. Run this helper with valgrind in yet another terminal

    G_MESSAGES_DEBUG=all valgrind --leak-check=full --suppressions=tests/libsoup.supp _build/tests/ws-test-helper

4. In a third terminal, drop packets to and from the ws server:

    # Change ACTION to "--delete" and re-run to undo the effects of this
    ACTION=--append ; IP=127.0.0.1 ; PORT=9001
    sudo iptables \
        --table filter \
        $ACTION INPUT \
        --protocol tcp \
        --destination $IP \
        --destination-port $PORT \
        --jump DROP
    sudo iptables \
        --table filter \
        $ACTION OUTPUT \
        --protocol tcp \
        --source $IP \
        --source-port $PORT \
        --jump DROP

5. After waiting a few seconds you will see output similar to this:

    ==867041== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
    ** Message: 15:56:37.721: Connecting to ws://127.0.0.1:9001
    ** Message: 15:56:38.415: Connected
    (process:867041): libsoup-DEBUG: 15:56:41.308: ping libsoup-keepalive-1
    (process:867041): libsoup-DEBUG: 15:56:41.318: received keepalive pong
    ...
    (process:867041): libsoup-DEBUG: 15:58:23.306: ping libsoup-keepalive-35
    (process:867041): libsoup-DEBUG: 15:58:23.307: expected pong never arrived; connection probably lost
    ** Message: 15:58:23.311: Error: Did not receive keepalive pong within 3 seconds
    (process:867041): libsoup-DEBUG: 15:58:23.312: requesting close due to error
    ** Message: 15:58:28.319: Closed
    ==867041== LEAK SUMMARY:
    ==867041==    definitely lost: 0 bytes in 0 blocks
    ==867041==    indirectly lost: 0 bytes in 0 blocks
    ==867041==      possibly lost: 0 bytes in 0 blocks
    ==867041== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 3 from 3)

 */

#include <glib-unix.h>
#include <libsoup/soup.h>

typedef struct {
        SoupWebsocketConnection *connection;
        gboolean closed;
} AppState;

static gboolean
on_sigint (AppState *app_state)
{
        soup_websocket_connection_close (app_state->connection, SOUP_WEBSOCKET_CLOSE_NORMAL, NULL);

        return G_SOURCE_CONTINUE;
}

static void
on_error (SoupWebsocketConnection *connection,
          GError *err,
          AppState *app_state)
{
        g_message ("Error: %s", err->message);
}

static void
on_closed (SoupWebsocketConnection *connection,
           AppState *app_state)
{
        app_state->closed = TRUE;
        g_message ("Closed");
}

static void
on_connect (GObject *session,
            GAsyncResult *res,
            AppState *app_state)
{
        GError *error = NULL;
        app_state->connection = soup_session_websocket_connect_finish (SOUP_SESSION (session), res, &error);
        if (!app_state->connection) {
                g_message ("Connection failed: %s", error->message);
                g_error_free (error);
                app_state->closed = TRUE;
                return;
        }

        g_message ("Connected");

        g_signal_connect (app_state->connection, "error", G_CALLBACK (on_error), app_state);
        g_signal_connect (app_state->connection, "closed", G_CALLBACK (on_closed), app_state);

        soup_websocket_connection_set_keepalive_interval (app_state->connection, 3);
        soup_websocket_connection_set_keepalive_pong_timeout (app_state->connection, 3);
}

int
main (int argc, char *argv[])
{
        AppState *app_state = g_new0 (AppState, 1);

        // Connect
        char *uri = "ws://127.0.0.1:9001";
        SoupSession *session = soup_session_new ();
        soup_session_add_feature_by_type (session, SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER);
        SoupMessage *message = soup_message_new (SOUP_METHOD_GET, uri);
        g_message ("Connecting to %s", uri);
        soup_session_websocket_connect_async (
            session,
            message,
            NULL, NULL,
            G_PRIORITY_DEFAULT, NULL,
            (GAsyncReadyCallback)on_connect, app_state);

        // Setup a SIGINT handler for clean and valgrind friendly shutdown.
        GMainContext *async_context = g_main_context_ref_thread_default ();
        GSource *sigint_source = g_unix_signal_source_new (SIGINT);
        g_source_set_callback (sigint_source, (GSourceFunc)on_sigint, app_state, NULL);
        g_source_attach (sigint_source, async_context);

        // Run the main loop.
        while (!app_state->closed) {
                g_main_context_iteration (async_context, TRUE);
        }

        // Properly free stuff so valgrind can give relevant leak reports.
        g_source_destroy (sigint_source);
        g_source_unref (sigint_source);
        g_main_context_unref (async_context);
        if (app_state->connection) {
                g_signal_handlers_disconnect_by_func (app_state->connection, on_closed, app_state);
                g_signal_handlers_disconnect_by_func (app_state->connection, on_error, app_state);
                g_object_unref (app_state->connection);
        }
        g_object_unref (message);
        g_object_unref (session);
        g_free (app_state);

        return 0;
}